Skip to content

Swift Archival & Serialization API #8124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions stdlib/public/SDK/Foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,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 @@ -14,6 +15,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 @@ -38,6 +40,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