Skip to content

Commit b40c0bd

Browse files
authored
Merge pull request #10685 from itaiferber/codingerror-bridging-and-conveniences
EncodingError/DecodingError bridging fixes and conveniences
2 parents 4b88da2 + cf0ebc1 commit b40c0bd

File tree

5 files changed

+169
-75
lines changed

5 files changed

+169
-75
lines changed

stdlib/public/SDK/Foundation/Codable.swift

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,59 +14,9 @@
1414
// Errors
1515
//===----------------------------------------------------------------------===//
1616

17-
// Adding the following extensions to EncodingError and DecodingError allows them to bridge to NSErrors implicitly.
18-
19-
fileprivate let NSCodingPathErrorKey = "NSCodingPath"
20-
fileprivate let NSDebugDescriptionErrorKey = "NSDebugDescription"
21-
22-
extension EncodingError : CustomNSError {
23-
public static var errorDomain: String = NSCocoaErrorDomain
24-
25-
public var errorCode: Int {
26-
switch self {
27-
case .invalidValue(_, _): return CocoaError.coderInvalidValue.rawValue
28-
}
29-
}
30-
31-
public var errorUserInfo: [String : Any] {
32-
let context: Context
33-
switch self {
34-
case .invalidValue(_, let c): context = c
35-
}
36-
37-
return [NSCodingPathErrorKey: context.codingPath,
38-
NSDebugDescriptionErrorKey: context.debugDescription]
39-
}
40-
}
41-
42-
extension DecodingError : CustomNSError {
43-
public static var errorDomain: String = NSCocoaErrorDomain
44-
45-
public var errorCode: Int {
46-
switch self {
47-
case .valueNotFound(_, _): fallthrough
48-
case .keyNotFound(_, _):
49-
return CocoaError._coderValueNotFound.rawValue
50-
51-
case .typeMismatch(_, _): fallthrough
52-
case .dataCorrupted(_):
53-
return CocoaError._coderReadCorrupt.rawValue
54-
}
55-
}
56-
57-
public var errorUserInfo: [String : Any]? {
58-
let context: Context
59-
switch self {
60-
case .typeMismatch(_, let c): context = c
61-
case .valueNotFound(_, let c): context = c
62-
case .keyNotFound(_, let c): context = c
63-
case .dataCorrupted(let c): context = c
64-
}
65-
66-
return [NSCodingPathErrorKey: context.codingPath,
67-
NSDebugDescriptionErrorKey: context.debugDescription]
68-
}
69-
}
17+
// Both of these error types bridge to NSError, and through the entry points they use, no further work is needed to make them localized.
18+
extension EncodingError : LocalizedError {}
19+
extension DecodingError : LocalizedError {}
7020

7121
//===----------------------------------------------------------------------===//
7222
// Error Utilities

stdlib/public/SDK/Foundation/JSONEncoder.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ open class JSONEncoder {
145145
}
146146

147147
let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue)
148-
return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
148+
do {
149+
return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
150+
} catch {
151+
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
152+
}
149153
}
150154
}
151155

@@ -883,7 +887,13 @@ open class JSONDecoder {
883887
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
884888
/// - throws: An error if any value throws an error during decoding.
885889
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
886-
let topLevel = try JSONSerialization.jsonObject(with: data)
890+
let topLevel: Any
891+
do {
892+
topLevel = try JSONSerialization.jsonObject(with: data)
893+
} catch {
894+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
895+
}
896+
887897
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
888898
return try T(from: decoder)
889899
}

stdlib/public/SDK/Foundation/NSError.swift

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -850,19 +850,11 @@ extension CocoaError.Code {
850850

851851
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
852852
public static var coderReadCorrupt: CocoaError.Code {
853-
return _coderReadCorrupt
854-
}
855-
856-
internal static var _coderReadCorrupt: CocoaError.Code {
857853
return CocoaError.Code(rawValue: 4864)
858854
}
859855

860856
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
861857
public static var coderValueNotFound: CocoaError.Code {
862-
return _coderValueNotFound
863-
}
864-
865-
internal static var _coderValueNotFound: CocoaError.Code {
866858
return CocoaError.Code(rawValue: 4865)
867859
}
868860

@@ -1301,19 +1293,11 @@ extension CocoaError {
13011293

13021294
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
13031295
public static var coderReadCorrupt: CocoaError.Code {
1304-
return _coderReadCorrupt
1305-
}
1306-
1307-
public static var _coderReadCorrupt: CocoaError.Code {
13081296
return CocoaError.Code(rawValue: 4864)
13091297
}
13101298

13111299
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
13121300
public static var coderValueNotFound: CocoaError.Code {
1313-
return _coderValueNotFound
1314-
}
1315-
1316-
internal static var _coderValueNotFound: CocoaError.Code {
13171301
return CocoaError.Code(rawValue: 4865)
13181302
}
13191303

stdlib/public/SDK/Foundation/PlistEncoder.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ open class PropertyListEncoder {
7474
debugDescription: "Top-level \(Value.self) encoded as date property list fragment."))
7575
}
7676

77-
return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0)
77+
do {
78+
return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0)
79+
} catch {
80+
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value as a property list", underlyingError: error))
81+
}
7882
}
7983
}
8084

@@ -640,7 +644,12 @@ open class PropertyListDecoder {
640644
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not a valid property list.
641645
/// - throws: An error if any value throws an error during decoding.
642646
open func decode<T : Decodable>(_ type: T.Type, from data: Data, format: inout PropertyListSerialization.PropertyListFormat) throws -> T {
643-
let topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format)
647+
let topLevel: Any
648+
do {
649+
topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format)
650+
} catch {
651+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not a valid property list.", underlyingError: error))
652+
}
644653
let decoder = _PlistDecoder(referencing: topLevel, options: self.options)
645654
return try T(from: decoder)
646655
}

stdlib/public/core/Codable.swift

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,10 @@ public protocol UnkeyedDecodingContainer {
17401740
/// A container that can support the storage and direct encoding of a single
17411741
/// non-keyed value.
17421742
public protocol SingleValueEncodingContainer {
1743+
/// The path of coding keys taken to get to this point in encoding.
1744+
/// A `nil` value indicates an unkeyed container.
1745+
var codingPath: [CodingKey?] { get }
1746+
17431747
/// Encodes a null value.
17441748
///
17451749
/// - throws: `EncodingError.invalidValue` if a null value is invalid in the current context for this format.
@@ -1854,6 +1858,10 @@ public protocol SingleValueEncodingContainer {
18541858

18551859
/// A `SingleValueDecodingContainer` is a container which can support the storage and direct decoding of a single non-keyed value.
18561860
public protocol SingleValueDecodingContainer {
1861+
/// The path of coding keys taken to get to this point in encoding.
1862+
/// A `nil` value indicates an unkeyed container.
1863+
var codingPath: [CodingKey?] { get }
1864+
18571865
/// Decodes a null value.
18581866
///
18591867
/// - returns: Whether the encountered value was null.
@@ -2026,20 +2034,63 @@ public enum EncodingError : Error {
20262034
/// A description of what went wrong, for debugging purposes.
20272035
public let debugDescription: String
20282036

2037+
/// The underlying error which caused this error, if any.
2038+
public let underlyingError: Error?
2039+
20292040
/// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
20302041
///
20312042
/// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing encode call.
20322043
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
2033-
public init(codingPath: [CodingKey?], debugDescription: String) {
2044+
/// - parameter underlyingError: The underlying error which caused this error, if any.
2045+
public init(codingPath: [CodingKey?], debugDescription: String, underlyingError: Error? = nil) {
20342046
self.codingPath = codingPath
20352047
self.debugDescription = debugDescription
2048+
self.underlyingError = underlyingError
20362049
}
20372050
}
20382051

20392052
/// `.invalidValue` indicates that an `Encoder` or its containers could not encode the given value.
20402053
///
20412054
/// Contains the attempted value, along with context for debugging.
20422055
case invalidValue(Any, Context)
2056+
2057+
// MARK: - NSError Bridging
2058+
2059+
// CustomNSError bridging applies only when the CustomNSError conformance is applied in the same module as the declared error type.
2060+
// Since we cannot access CustomNSError (which is defined in Foundation) from here, we can use the "hidden" entry points.
2061+
2062+
public var _domain: String {
2063+
return "NSCocoaErrorDomain"
2064+
}
2065+
2066+
public var _code: Int {
2067+
switch self {
2068+
case .invalidValue(_, _): return 4866
2069+
}
2070+
}
2071+
2072+
public var _userInfo: AnyObject? {
2073+
// The error dictionary must be returned as an AnyObject. We can do this only on platforms with bridging, unfortunately.
2074+
#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
2075+
let context: Context
2076+
switch self {
2077+
case .invalidValue(_, let c): context = c
2078+
}
2079+
2080+
var userInfo: [String : Any] = [
2081+
"NSCodingPath": context.codingPath,
2082+
"NSDebugDescription": context.debugDescription
2083+
]
2084+
2085+
if let underlyingError = context.underlyingError {
2086+
userInfo["NSUnderlyingError"] = underlyingError
2087+
}
2088+
2089+
return userInfo as AnyObject
2090+
#else
2091+
return nil
2092+
#endif
2093+
}
20432094
}
20442095

20452096
/// An error that occurs during the decoding of a value.
@@ -2052,13 +2103,18 @@ public enum DecodingError : Error {
20522103
/// A description of what went wrong, for debugging purposes.
20532104
public let debugDescription: String
20542105

2106+
/// The underlying error which caused this error, if any.
2107+
public let underlyingError: Error?
2108+
20552109
/// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
20562110
///
20572111
/// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing decode call.
20582112
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
2059-
public init(codingPath: [CodingKey?], debugDescription: String) {
2113+
/// - parameter underlyingError: The underlying error which caused this error, if any.
2114+
public init(codingPath: [CodingKey?], debugDescription: String, underlyingError: Error? = nil) {
20602115
self.codingPath = codingPath
20612116
self.debugDescription = debugDescription
2117+
self.underlyingError = underlyingError
20622118
}
20632119
}
20642120

@@ -2081,6 +2137,91 @@ public enum DecodingError : Error {
20812137
///
20822138
/// Contains context for debugging.
20832139
case dataCorrupted(Context)
2140+
2141+
// MARK: - NSError Bridging
2142+
2143+
// CustomNSError bridging applies only when the CustomNSError conformance is applied in the same module as the declared error type.
2144+
// Since we cannot access CustomNSError (which is defined in Foundation) from here, we can use the "hidden" entry points.
2145+
2146+
public var _domain: String {
2147+
return "NSCocoaErrorDomain"
2148+
}
2149+
2150+
public var _code: Int {
2151+
switch self {
2152+
case .keyNotFound(_, _): fallthrough
2153+
case .valueNotFound(_, _): return 4865
2154+
case .typeMismatch(_, _): fallthrough
2155+
case .dataCorrupted(_): return 4864
2156+
}
2157+
}
2158+
2159+
public var _userInfo: AnyObject? {
2160+
// The error dictionary must be returned as an AnyObject. We can do this only on platforms with bridging, unfortunately.
2161+
#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
2162+
let context: Context
2163+
switch self {
2164+
case .keyNotFound(_, let c): context = c
2165+
case .valueNotFound(_, let c): context = c
2166+
case .typeMismatch(_, let c): context = c
2167+
case .dataCorrupted( let c): context = c
2168+
}
2169+
2170+
var userInfo: [String : Any] = [
2171+
"NSCodingPath": context.codingPath,
2172+
"NSDebugDescription": context.debugDescription
2173+
]
2174+
2175+
if let underlyingError = context.underlyingError {
2176+
userInfo["NSUnderlyingError"] = underlyingError
2177+
}
2178+
2179+
return userInfo as AnyObject
2180+
#else
2181+
return nil
2182+
#endif
2183+
}
2184+
}
2185+
2186+
// The following extensions allow for easier error construction.
2187+
2188+
public extension DecodingError {
2189+
/// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
2190+
///
2191+
/// Constructs a coding path by appending the given key to the given container's coding path.
2192+
///
2193+
/// - param key: The key which caused the failure.
2194+
/// - param container: The container in which the corrupted data was accessed.
2195+
/// - param debugDescription: A description of the error to aid in debugging.
2196+
static func dataCorruptedError<C : KeyedDecodingContainerProtocol>(forKey key: C.Key, in container: C, debugDescription: String) -> DecodingError {
2197+
let context = DecodingError.Context(codingPath: container.codingPath + [key],
2198+
debugDescription: debugDescription)
2199+
return .dataCorrupted(context)
2200+
}
2201+
2202+
/// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
2203+
///
2204+
/// Constructs a coding path by appending a nil key to the given container's coding path.
2205+
///
2206+
/// - param container: The container in which the corrupted data was accessed.
2207+
/// - param debugDescription: A description of the error to aid in debugging.
2208+
static func dataCorruptedError(in container: UnkeyedDecodingContainer, debugDescription: String) -> DecodingError {
2209+
let context = DecodingError.Context(codingPath: container.codingPath + [nil],
2210+
debugDescription: debugDescription)
2211+
return .dataCorrupted(context)
2212+
}
2213+
2214+
/// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
2215+
///
2216+
/// Uses the given container's coding path as the constructed path.
2217+
///
2218+
/// - param container: The container in which the corrupted data was accessed.
2219+
/// - param debugDescription: A description of the error to aid in debugging.
2220+
static func dataCorruptedError(in container: SingleValueDecodingContainer, debugDescription: String) -> DecodingError {
2221+
let context = DecodingError.Context(codingPath: container.codingPath,
2222+
debugDescription: debugDescription)
2223+
return .dataCorrupted(context)
2224+
}
20842225
}
20852226

20862227
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)