Skip to content

Commit

Permalink
Add JSON and property list encoders and decoders
Browse files Browse the repository at this point in the history
* Integrate {JSON,PropertyList}{Encoder,Decoder} types to facilitate
  encoding types in JSON and property list formats
* Adds Foundation-specific extensions to allow errors exposed from the
  stdlib to bridge to NSErrors
  • Loading branch information
Itai Ferber committed Apr 29, 2017
1 parent 0ecf8a9 commit 2ce58c1
Show file tree
Hide file tree
Showing 5 changed files with 3,555 additions and 0 deletions.
3 changes: 3 additions & 0 deletions stdlib/public/SDK/Foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
Boxing.swift
Calendar.swift
CharacterSet.swift
Codable.swift
Data.swift
DataThunks.m
Date.swift
Expand All @@ -17,6 +18,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
Foundation.swift
IndexPath.swift
IndexSet.swift
JSONEncoder.swift
Locale.swift
Measurement.swift
Notification.swift
Expand All @@ -41,6 +43,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
NSURL.swift
NSValue.swift.gyb
PersonNameComponents.swift
PlistEncoder.swift
ReferenceConvertible.swift
String.swift
TimeZone.swift
Expand Down
200 changes: 200 additions & 0 deletions stdlib/public/SDK/Foundation/Codable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// Codable Extensions
//===----------------------------------------------------------------------===//

extension Date : Codable {
public init(from decoder: Decoder) throws {
let timestamp = try decoder.singleValueContainer().decode(Double.self)
self.init(timeIntervalSince1970: timestamp)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.timeIntervalSince1970)
}
}

extension Data : Codable {
private enum CodingKeys : Int, CodingKey {
case length
case bytes

// TODO: Remove these when derived conformance is merged.
var stringValue: String {
switch self {
case .length: return "length"
case .bytes: return "bytes"
}
}

init?(stringValue: String) {
switch stringValue {
case "length": self = .length
case "bytes": self = .bytes
default: return nil
}
}

var intValue: Int? {
return self.rawValue
}

init?(intValue: Int) {
switch intValue {
case 0: self = .length
case 1: self = .bytes
default: return nil
}
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let length = try container.decode(Int.self, forKey: .length)
guard length >= 0 else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Cannot decode a Data of negative length \(length)."))
}

var bytesContainer = try container.nestedUnkeyedContainer(forKey: .bytes)

self.init(capacity: length)
for i in 0 ..< length {
let byte = try bytesContainer.decode(UInt8.self)
self[i] = byte
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.count, forKey: .length)

var bytesContainer = container.nestedUnkeyedContainer(forKey: .bytes)

// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
var caughtError: Error? = nil
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
do {
for byte in buffer {
try bytesContainer.encode(byte)
}
} catch {
caughtError = error
stop = true
}
}

if let error = caughtError {
throw error
}
}
}

//===----------------------------------------------------------------------===//
// Errors
//===----------------------------------------------------------------------===//

// Adding the following extensions to EncodingError and DecodingError allows them to bridge to NSErrors implicitly.

fileprivate let NSCodingPathErrorKey = "NSCodingPath"
fileprivate let NSDebugDescriptionErrorKey = "NSDebugDescription"

extension EncodingError : CustomNSError {
public static var errorDomain: String = NSCocoaErrorDomain

public var errorCode: Int {
switch self {
case .invalidValue(_, _): return CocoaError.coderInvalidValue.rawValue
}
}

public var errorUserInfo: [String : Any] {
let context: Context
switch self {
case .invalidValue(_, let c): context = c
}

return [NSCodingPathErrorKey: context.codingPath,
NSDebugDescriptionErrorKey: context.debugDescription]
}
}

extension DecodingError : CustomNSError {
public static var errorDomain: String = NSCocoaErrorDomain

public var errorCode: Int {
switch self {
case .valueNotFound(_, _): fallthrough
case .keyNotFound(_, _):
return CocoaError._coderValueNotFound.rawValue

case .typeMismatch(_, _): fallthrough
case .dataCorrupted(_):
return CocoaError._coderReadCorrupt.rawValue
}
}

public var errorUserInfo: [String : Any]? {
let context: Context
switch self {
case .typeMismatch(_, let c): context = c
case .valueNotFound(_, let c): context = c
case .keyNotFound(_, let c): context = c
case .dataCorrupted(let c): context = c
}

return [NSCodingPathErrorKey: context.codingPath,
NSDebugDescriptionErrorKey: context.debugDescription]
}
}

//===----------------------------------------------------------------------===//
// Error Utilities
//===----------------------------------------------------------------------===//

internal extension DecodingError {
/// Returns a `.typeMismatch` error describing the expected type.
///
/// - parameter path: The path of `CodingKey`s taken to decode a value of this type.
/// - parameter expectation: The type expected to be encountered.
/// - parameter reality: The value that was encountered instead of the expected type.
/// - returns: A `DecodingError` with the appropriate path and debug description.
internal static func _typeMismatch(at path: [CodingKey?], expectation: Any.Type, reality: Any) -> DecodingError {
let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead."
return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description))
}

/// Returns a description of the type of `value` appropriate for an error message.
///
/// - parameter value: The value whose type to describe.
/// - returns: A string describing `value`.
/// - precondition: `value` is one of the types below.
fileprivate static func _typeDescription(of value: Any) -> String {
if value is NSNull {
return "a null value"
} else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ {
return "a number"
} else if value is String {
return "a string/data"
} else if value is [Any] {
return "an array"
} else if value is [String : Any] {
return "a dictionary"
} else {
// This should never happen -- we somehow have a non-JSON type here.
preconditionFailure("Invalid storage type \(type(of: value)).")
}
}
}
Loading

0 comments on commit 2ce58c1

Please sign in to comment.