Merge pull request #10685 from itaiferber/codingerror-bridging-and-conveniences
EncodingError/DecodingError bridging fixes and conveniences
diff --git a/stdlib/public/SDK/Foundation/Codable.swift b/stdlib/public/SDK/Foundation/Codable.swift
index 4410933..200cbb1 100644
--- a/stdlib/public/SDK/Foundation/Codable.swift
+++ b/stdlib/public/SDK/Foundation/Codable.swift
@@ -14,59 +14,9 @@
// 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]
- }
-}
+// Both of these error types bridge to NSError, and through the entry points they use, no further work is needed to make them localized.
+extension EncodingError : LocalizedError {}
+extension DecodingError : LocalizedError {}
//===----------------------------------------------------------------------===//
// Error Utilities
diff --git a/stdlib/public/SDK/Foundation/JSONEncoder.swift b/stdlib/public/SDK/Foundation/JSONEncoder.swift
index 9dba0f0..c6367be 100644
--- a/stdlib/public/SDK/Foundation/JSONEncoder.swift
+++ b/stdlib/public/SDK/Foundation/JSONEncoder.swift
@@ -145,7 +145,11 @@
}
let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue)
- return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
+ do {
+ return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
+ } catch {
+ throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
+ }
}
}
@@ -883,7 +887,13 @@
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
/// - throws: An error if any value throws an error during decoding.
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
- let topLevel = try JSONSerialization.jsonObject(with: data)
+ let topLevel: Any
+ do {
+ topLevel = try JSONSerialization.jsonObject(with: data)
+ } catch {
+ throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
+ }
+
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
return try T(from: decoder)
}
diff --git a/stdlib/public/SDK/Foundation/NSError.swift b/stdlib/public/SDK/Foundation/NSError.swift
index 768fb26..3b3c585 100644
--- a/stdlib/public/SDK/Foundation/NSError.swift
+++ b/stdlib/public/SDK/Foundation/NSError.swift
@@ -850,19 +850,11 @@
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
public static var coderReadCorrupt: CocoaError.Code {
- return _coderReadCorrupt
- }
-
- internal static var _coderReadCorrupt: CocoaError.Code {
return CocoaError.Code(rawValue: 4864)
}
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
public static var coderValueNotFound: CocoaError.Code {
- return _coderValueNotFound
- }
-
- internal static var _coderValueNotFound: CocoaError.Code {
return CocoaError.Code(rawValue: 4865)
}
@@ -1301,19 +1293,11 @@
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
public static var coderReadCorrupt: CocoaError.Code {
- return _coderReadCorrupt
- }
-
- public static var _coderReadCorrupt: CocoaError.Code {
return CocoaError.Code(rawValue: 4864)
}
@available(OSX, introduced: 10.11) @available(iOS, introduced: 9.0)
public static var coderValueNotFound: CocoaError.Code {
- return _coderValueNotFound
- }
-
- internal static var _coderValueNotFound: CocoaError.Code {
return CocoaError.Code(rawValue: 4865)
}
diff --git a/stdlib/public/SDK/Foundation/PlistEncoder.swift b/stdlib/public/SDK/Foundation/PlistEncoder.swift
index df898d2..f5514ff 100644
--- a/stdlib/public/SDK/Foundation/PlistEncoder.swift
+++ b/stdlib/public/SDK/Foundation/PlistEncoder.swift
@@ -74,7 +74,11 @@
debugDescription: "Top-level \(Value.self) encoded as date property list fragment."))
}
- return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0)
+ do {
+ return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0)
+ } catch {
+ throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value as a property list", underlyingError: error))
+ }
}
}
@@ -640,7 +644,12 @@
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not a valid property list.
/// - throws: An error if any value throws an error during decoding.
open func decode<T : Decodable>(_ type: T.Type, from data: Data, format: inout PropertyListSerialization.PropertyListFormat) throws -> T {
- let topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format)
+ let topLevel: Any
+ do {
+ topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format)
+ } catch {
+ throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not a valid property list.", underlyingError: error))
+ }
let decoder = _PlistDecoder(referencing: topLevel, options: self.options)
return try T(from: decoder)
}
diff --git a/stdlib/public/core/Codable.swift b/stdlib/public/core/Codable.swift
index 5dc5d49..8f5b5a0 100644
--- a/stdlib/public/core/Codable.swift
+++ b/stdlib/public/core/Codable.swift
@@ -1740,6 +1740,10 @@
/// A container that can support the storage and direct encoding of a single
/// non-keyed value.
public protocol SingleValueEncodingContainer {
+ /// The path of coding keys taken to get to this point in encoding.
+ /// A `nil` value indicates an unkeyed container.
+ var codingPath: [CodingKey?] { get }
+
/// Encodes a null value.
///
/// - throws: `EncodingError.invalidValue` if a null value is invalid in the current context for this format.
@@ -1854,6 +1858,10 @@
/// A `SingleValueDecodingContainer` is a container which can support the storage and direct decoding of a single non-keyed value.
public protocol SingleValueDecodingContainer {
+ /// The path of coding keys taken to get to this point in encoding.
+ /// A `nil` value indicates an unkeyed container.
+ var codingPath: [CodingKey?] { get }
+
/// Decodes a null value.
///
/// - returns: Whether the encountered value was null.
@@ -2026,13 +2034,18 @@
/// A description of what went wrong, for debugging purposes.
public let debugDescription: String
+ /// The underlying error which caused this error, if any.
+ public let underlyingError: Error?
+
/// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
///
/// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing encode call.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
- public init(codingPath: [CodingKey?], debugDescription: String) {
+ /// - parameter underlyingError: The underlying error which caused this error, if any.
+ public init(codingPath: [CodingKey?], debugDescription: String, underlyingError: Error? = nil) {
self.codingPath = codingPath
self.debugDescription = debugDescription
+ self.underlyingError = underlyingError
}
}
@@ -2040,6 +2053,44 @@
///
/// Contains the attempted value, along with context for debugging.
case invalidValue(Any, Context)
+
+ // MARK: - NSError Bridging
+
+ // CustomNSError bridging applies only when the CustomNSError conformance is applied in the same module as the declared error type.
+ // Since we cannot access CustomNSError (which is defined in Foundation) from here, we can use the "hidden" entry points.
+
+ public var _domain: String {
+ return "NSCocoaErrorDomain"
+ }
+
+ public var _code: Int {
+ switch self {
+ case .invalidValue(_, _): return 4866
+ }
+ }
+
+ public var _userInfo: AnyObject? {
+ // The error dictionary must be returned as an AnyObject. We can do this only on platforms with bridging, unfortunately.
+ #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+ let context: Context
+ switch self {
+ case .invalidValue(_, let c): context = c
+ }
+
+ var userInfo: [String : Any] = [
+ "NSCodingPath": context.codingPath,
+ "NSDebugDescription": context.debugDescription
+ ]
+
+ if let underlyingError = context.underlyingError {
+ userInfo["NSUnderlyingError"] = underlyingError
+ }
+
+ return userInfo as AnyObject
+ #else
+ return nil
+ #endif
+ }
}
/// An error that occurs during the decoding of a value.
@@ -2052,13 +2103,18 @@
/// A description of what went wrong, for debugging purposes.
public let debugDescription: String
+ /// The underlying error which caused this error, if any.
+ public let underlyingError: Error?
+
/// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
///
/// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing decode call.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
- public init(codingPath: [CodingKey?], debugDescription: String) {
+ /// - parameter underlyingError: The underlying error which caused this error, if any.
+ public init(codingPath: [CodingKey?], debugDescription: String, underlyingError: Error? = nil) {
self.codingPath = codingPath
self.debugDescription = debugDescription
+ self.underlyingError = underlyingError
}
}
@@ -2081,6 +2137,91 @@
///
/// Contains context for debugging.
case dataCorrupted(Context)
+
+ // MARK: - NSError Bridging
+
+ // CustomNSError bridging applies only when the CustomNSError conformance is applied in the same module as the declared error type.
+ // Since we cannot access CustomNSError (which is defined in Foundation) from here, we can use the "hidden" entry points.
+
+ public var _domain: String {
+ return "NSCocoaErrorDomain"
+ }
+
+ public var _code: Int {
+ switch self {
+ case .keyNotFound(_, _): fallthrough
+ case .valueNotFound(_, _): return 4865
+ case .typeMismatch(_, _): fallthrough
+ case .dataCorrupted(_): return 4864
+ }
+ }
+
+ public var _userInfo: AnyObject? {
+ // The error dictionary must be returned as an AnyObject. We can do this only on platforms with bridging, unfortunately.
+ #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+ let context: Context
+ switch self {
+ case .keyNotFound(_, let c): context = c
+ case .valueNotFound(_, let c): context = c
+ case .typeMismatch(_, let c): context = c
+ case .dataCorrupted( let c): context = c
+ }
+
+ var userInfo: [String : Any] = [
+ "NSCodingPath": context.codingPath,
+ "NSDebugDescription": context.debugDescription
+ ]
+
+ if let underlyingError = context.underlyingError {
+ userInfo["NSUnderlyingError"] = underlyingError
+ }
+
+ return userInfo as AnyObject
+ #else
+ return nil
+ #endif
+ }
+}
+
+// The following extensions allow for easier error construction.
+
+public extension DecodingError {
+ /// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
+ ///
+ /// Constructs a coding path by appending the given key to the given container's coding path.
+ ///
+ /// - param key: The key which caused the failure.
+ /// - param container: The container in which the corrupted data was accessed.
+ /// - param debugDescription: A description of the error to aid in debugging.
+ static func dataCorruptedError<C : KeyedDecodingContainerProtocol>(forKey key: C.Key, in container: C, debugDescription: String) -> DecodingError {
+ let context = DecodingError.Context(codingPath: container.codingPath + [key],
+ debugDescription: debugDescription)
+ return .dataCorrupted(context)
+ }
+
+ /// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
+ ///
+ /// Constructs a coding path by appending a nil key to the given container's coding path.
+ ///
+ /// - param container: The container in which the corrupted data was accessed.
+ /// - param debugDescription: A description of the error to aid in debugging.
+ static func dataCorruptedError(in container: UnkeyedDecodingContainer, debugDescription: String) -> DecodingError {
+ let context = DecodingError.Context(codingPath: container.codingPath + [nil],
+ debugDescription: debugDescription)
+ return .dataCorrupted(context)
+ }
+
+ /// A convenience method which creates a new .dataCorrupted error using a constructed coding path and the given debug description.
+ ///
+ /// Uses the given container's coding path as the constructed path.
+ ///
+ /// - param container: The container in which the corrupted data was accessed.
+ /// - param debugDescription: A description of the error to aid in debugging.
+ static func dataCorruptedError(in container: SingleValueDecodingContainer, debugDescription: String) -> DecodingError {
+ let context = DecodingError.Context(codingPath: container.codingPath,
+ debugDescription: debugDescription)
+ return .dataCorrupted(context)
+ }
}
//===----------------------------------------------------------------------===//