Skip to content

Commit 09775ff

Browse files
authored
Merge pull request swiftlang#9005 from itaiferber/foundation-encoders
Foundation Encoders
2 parents 0c61db5 + 7778fc2 commit 09775ff

File tree

8 files changed

+4719
-139
lines changed

8 files changed

+4719
-139
lines changed

stdlib/public/SDK/Foundation/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
66
Boxing.swift
77
Calendar.swift
88
CharacterSet.swift
9+
Codable.swift
910
Data.swift
1011
DataThunks.m
1112
Date.swift
@@ -17,6 +18,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
1718
Foundation.swift
1819
IndexPath.swift
1920
IndexSet.swift
21+
JSONEncoder.swift
2022
Locale.swift
2123
Measurement.swift
2224
Notification.swift
@@ -41,6 +43,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
4143
NSURL.swift
4244
NSValue.swift.gyb
4345
PersonNameComponents.swift
46+
PlistEncoder.swift
4447
ReferenceConvertible.swift
4548
String.swift
4649
TimeZone.swift
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
//===----------------------------------------------------------------------===//
14+
// Codable Extensions
15+
//===----------------------------------------------------------------------===//
16+
17+
extension Date : Codable {
18+
public init(from decoder: Decoder) throws {
19+
let timestamp = try decoder.singleValueContainer().decode(Double.self)
20+
self.init(timeIntervalSinceReferenceDate: timestamp)
21+
}
22+
23+
public func encode(to encoder: Encoder) throws {
24+
var container = encoder.singleValueContainer()
25+
try container.encode(self.timeIntervalSinceReferenceDate)
26+
}
27+
}
28+
29+
extension Data : Codable {
30+
private enum CodingKeys : Int, CodingKey {
31+
case length
32+
case bytes
33+
}
34+
35+
public init(from decoder: Decoder) throws {
36+
let container = try decoder.container(keyedBy: CodingKeys.self)
37+
let length = try container.decode(Int.self, forKey: .length)
38+
guard length >= 0 else {
39+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
40+
debugDescription: "Cannot decode a Data of negative length \(length)."))
41+
}
42+
43+
var bytesContainer = try container.nestedUnkeyedContainer(forKey: .bytes)
44+
45+
self.init(capacity: length)
46+
for i in 0 ..< length {
47+
let byte = try bytesContainer.decode(UInt8.self)
48+
self[i] = byte
49+
}
50+
}
51+
52+
public func encode(to encoder: Encoder) throws {
53+
var container = encoder.container(keyedBy: CodingKeys.self)
54+
try container.encode(self.count, forKey: .length)
55+
56+
var bytesContainer = container.nestedUnkeyedContainer(forKey: .bytes)
57+
58+
// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
59+
var caughtError: Error? = nil
60+
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
61+
do {
62+
try bytesContainer.encode(contentsOf: buffer)
63+
} catch {
64+
caughtError = error
65+
stop = true
66+
}
67+
}
68+
69+
if let error = caughtError {
70+
throw error
71+
}
72+
}
73+
}
74+
75+
//===----------------------------------------------------------------------===//
76+
// Errors
77+
//===----------------------------------------------------------------------===//
78+
79+
// Adding the following extensions to EncodingError and DecodingError allows them to bridge to NSErrors implicitly.
80+
81+
fileprivate let NSCodingPathErrorKey = "NSCodingPath"
82+
fileprivate let NSDebugDescriptionErrorKey = "NSDebugDescription"
83+
84+
extension EncodingError : CustomNSError {
85+
public static var errorDomain: String = NSCocoaErrorDomain
86+
87+
public var errorCode: Int {
88+
switch self {
89+
case .invalidValue(_, _): return CocoaError.coderInvalidValue.rawValue
90+
}
91+
}
92+
93+
public var errorUserInfo: [String : Any] {
94+
let context: Context
95+
switch self {
96+
case .invalidValue(_, let c): context = c
97+
}
98+
99+
return [NSCodingPathErrorKey: context.codingPath,
100+
NSDebugDescriptionErrorKey: context.debugDescription]
101+
}
102+
}
103+
104+
extension DecodingError : CustomNSError {
105+
public static var errorDomain: String = NSCocoaErrorDomain
106+
107+
public var errorCode: Int {
108+
switch self {
109+
case .valueNotFound(_, _): fallthrough
110+
case .keyNotFound(_, _):
111+
return CocoaError._coderValueNotFound.rawValue
112+
113+
case .typeMismatch(_, _): fallthrough
114+
case .dataCorrupted(_):
115+
return CocoaError._coderReadCorrupt.rawValue
116+
}
117+
}
118+
119+
public var errorUserInfo: [String : Any]? {
120+
let context: Context
121+
switch self {
122+
case .typeMismatch(_, let c): context = c
123+
case .valueNotFound(_, let c): context = c
124+
case .keyNotFound(_, let c): context = c
125+
case .dataCorrupted(let c): context = c
126+
}
127+
128+
return [NSCodingPathErrorKey: context.codingPath,
129+
NSDebugDescriptionErrorKey: context.debugDescription]
130+
}
131+
}
132+
133+
//===----------------------------------------------------------------------===//
134+
// Error Utilities
135+
//===----------------------------------------------------------------------===//
136+
137+
internal extension DecodingError {
138+
/// Returns a `.typeMismatch` error describing the expected type.
139+
///
140+
/// - parameter path: The path of `CodingKey`s taken to decode a value of this type.
141+
/// - parameter expectation: The type expected to be encountered.
142+
/// - parameter reality: The value that was encountered instead of the expected type.
143+
/// - returns: A `DecodingError` with the appropriate path and debug description.
144+
internal static func _typeMismatch(at path: [CodingKey?], expectation: Any.Type, reality: Any) -> DecodingError {
145+
let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead."
146+
return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description))
147+
}
148+
149+
/// Returns a description of the type of `value` appropriate for an error message.
150+
///
151+
/// - parameter value: The value whose type to describe.
152+
/// - returns: A string describing `value`.
153+
/// - precondition: `value` is one of the types below.
154+
fileprivate static func _typeDescription(of value: Any) -> String {
155+
if value is NSNull {
156+
return "a null value"
157+
} 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 */ {
158+
return "a number"
159+
} else if value is String {
160+
return "a string/data"
161+
} else if value is [Any] {
162+
return "an array"
163+
} else if value is [String : Any] {
164+
return "a dictionary"
165+
} else {
166+
// This should never happen -- we somehow have a non-JSON type here.
167+
preconditionFailure("Invalid storage type \(type(of: value)).")
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)