mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[SE-0489] Better debugDescription for EncodingError and DecodingError (#80941)
Now accepted as [SE-0489](https://github.com/ZevEisenberg/swift-evolution/blob/main/proposals/0489-codable-error-printing.md). # To Do - [x] confirm which version of Swift to use for the availability annotations. Probably 6.3 at time of writing. # Context Re: [Swift forum post](https://forums.swift.org/t/the-future-of-serialization-deserialization-apis/78585/77), where a discussion about future serialization tools in Swift prompted Kevin Perry to suggest that some proposed changes could actually be made in today's stdlib. # Summary of Changes Conforms `EncodingError` and `DecodingError` to `CustomDebugStringConvertible` and adds a more-readable `debugDescription`. # Future Directions This is a pared-down version of some experiments I did in [UsefulDecode](https://github.com/ZevEisenberg/UsefulDecode). The changes in this PR are the best I could do without changing the public interface of `DecodingError` and `EncodingError`, and without modifying the way the `JSON`/`PropertyList` `Encoder`/`Decoder` in Foundation generate their errors' debug descriptions. In the above-linked [UsefulDecode](https://github.com/ZevEisenberg/UsefulDecode) repo, when JSON decoding fails, I go back and re-decode the JSON using `JSONSerialization` in order to provide more context about what failed, and why. I didn't attempt to make such a change here, but I'd like to discuss what may be possible. # Examples To illustrate the effect of the changes in this PR, I removed my changes to stdlib/public/core/Codable.swift and ran my new test cases again. Here are the resulting diffs. ## `test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError` ### Before `invalidValue(234, Swift.EncodingError.Context(codingPath: [GenericCodingKey(stringValue: "first", intValue: nil), GenericCodingKey(stringValue: "second", intValue: nil), GenericCodingKey(stringValue: "2", intValue: 2)], debugDescription: "You cannot do that!", underlyingError: nil))` ### After `EncodingError.invalidValue: 234 (Int). Path: first.second[2]. Debug description: You cannot do that!` ## `test_decodingError_valueNotFound_nilUnderlyingError` ### Before `valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "0", intValue: 0), GenericCodingKey(stringValue: "firstName", intValue: nil)], debugDescription: "Description for debugging purposes", underlyingError: nil))` ### After `DecodingError.valueNotFound: Expected value of type String but found null instead. Path: [0].firstName. Debug description: Description for debugging purposes` ## `test_decodingError_keyNotFound_nonNilUnderlyingError` ### Before `keyNotFound(GenericCodingKey(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "0", intValue: 0), GenericCodingKey(stringValue: "address", intValue: nil), GenericCodingKey(stringValue: "city", intValue: nil)], debugDescription: "Just some info to help you out", underlyingError: Optional(main.GenericError(name: "hey, who turned out the lights?"))))` ### After `DecodingError.keyNotFound: Key \'name\' not found in keyed decoding container. Path: [0].address.city. Debug description: Just some info to help you out. Underlying error: GenericError(name: "hey, who turned out the lights?")` ## `test_decodingError_typeMismatch_nilUnderlyingError` ### Before `typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "0", intValue: 0), GenericCodingKey(stringValue: "address", intValue: nil), GenericCodingKey(stringValue: "city", intValue: nil), GenericCodingKey(stringValue: "birds", intValue: nil), GenericCodingKey(stringValue: "1", intValue: 1), GenericCodingKey(stringValue: "name", intValue: nil)], debugDescription: "This is where the debug description goes", underlyingError: nil))` ### After `DecodingError.typeMismatch: expected value of type String. Path: [0].address.city.birds[1].name. Debug description: This is where the debug description goes` ## `test_decodingError_dataCorrupted_nonEmptyCodingPath` ### Before `dataCorrupted(Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "first", intValue: nil), GenericCodingKey(stringValue: "second", intValue: nil), GenericCodingKey(stringValue: "2", intValue: 2)], debugDescription: "There was apparently some data corruption!", underlyingError: Optional(main.GenericError(name: "This data corruption is getting out of hand"))))` ### After `DecodingError.dataCorrupted: Data was corrupted. Path: first.second[2]. Debug description: There was apparently some data corruption!. Underlying error: GenericError(name: "This data corruption is getting out of hand")` ## `test_decodingError_valueNotFound_nonNilUnderlyingError` ### Before `valueNotFound(Swift.Int, Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "0", intValue: 0), GenericCodingKey(stringValue: "population", intValue: nil)], debugDescription: "Here is the debug description for value-not-found", underlyingError: Optional(main.GenericError(name: "these aren\\\'t the droids you\\\'re looking for"))))` ### After `DecodingError.valueNotFound: Expected value of type Int but found null instead. Path: [0].population. Debug description: Here is the debug description for value-not-found. Underlying error: GenericError(name: "these aren\\\'t the droids you\\\'re looking for")` ## `test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError` ### Before `invalidValue(345, Swift.EncodingError.Context(codingPath: [], debugDescription: "You cannot do that!", underlyingError: Optional(main.GenericError(name: "You really cannot do that"))))` ### After `EncodingError.invalidValue: 345 (Int). Debug description: You cannot do that!. Underlying error: GenericError(name: "You really cannot do that")` ## `test_decodingError_typeMismatch_nonNilUnderlyingError` ### Before `typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "0", intValue: 0), GenericCodingKey(stringValue: "address", intValue: nil), GenericCodingKey(stringValue: "1", intValue: 1), GenericCodingKey(stringValue: "street", intValue: nil)], debugDescription: "Some debug description", underlyingError: Optional(main.GenericError(name: "some generic error goes here"))))` ### After `DecodingError.typeMismatch: expected value of type String. Path: [0].address[1].street. Debug description: Some debug description. Underlying error: GenericError(name: "some generic error goes here")` ## `test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError` ### Before `invalidValue(123, Swift.EncodingError.Context(codingPath: [], debugDescription: "You cannot do that!", underlyingError: nil))` ### After `EncodingError.invalidValue: 123 (Int). Debug description: You cannot do that!` ## `test_decodingError_keyNotFound_nilUnderlyingError` ### Before `keyNotFound(GenericCodingKey(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [GenericCodingKey(stringValue: "0", intValue: 0), GenericCodingKey(stringValue: "address", intValue: nil), GenericCodingKey(stringValue: "city", intValue: nil)], debugDescription: "How would you describe your relationship with your debugger?", underlyingError: nil))` ### After `DecodingError.keyNotFound: Key \'name\' not found in keyed decoding container. Path: [0]address.city. Debug description: How would you describe your relationship with your debugger?` ## `test_decodingError_dataCorrupted_emptyCodingPath` ### Before `dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON", underlyingError: Optional(main.GenericError(name: "just some data corruption"))))` ### After `DecodingError.dataCorrupted: Data was corrupted. Debug description: The given data was not valid JSON. Underlying error: GenericError(name: "just some data corruption")` ## `test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError` ### Before `invalidValue(456, Swift.EncodingError.Context(codingPath: [GenericCodingKey(stringValue: "first", intValue: nil), GenericCodingKey(stringValue: "second", intValue: nil), GenericCodingKey(stringValue: "2", intValue: 2)], debugDescription: "You cannot do that!", underlyingError: Optional(main.GenericError(name: "You really cannot do that"))))` ### After `EncodingError.invalidValue: 456 (Int). Path: first.second[2]. Debug description: You cannot do that!. Underlying error: GenericError(name: "You really cannot do that")`
This commit is contained in:
@@ -89,6 +89,58 @@ extension CodingKey {
|
||||
public var debugDescription: String {
|
||||
return description
|
||||
}
|
||||
|
||||
/// A simplified description: the int value, if present, in square brackets.
|
||||
/// Otherwise, the string value by itself. Used when concatenating coding keys
|
||||
/// to form a path when printing debug information.
|
||||
/// - parameter isFirst: Whether this is the first key in a coding path, in
|
||||
/// which case we will omit the prepended '.' delimiter from string keys.
|
||||
fileprivate func _errorPresentationDescription(isFirstInCodingPath isFirst: Bool = true) -> String {
|
||||
if let intValue {
|
||||
return "[\(intValue)]"
|
||||
} else {
|
||||
let delimiter = isFirst ? "" : "."
|
||||
return "\(delimiter)\(stringValue._escapedForCodingKeyErrorPresentationDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension [any CodingKey] {
|
||||
/// Concatenates the elements of an array of coding keys and joins them with
|
||||
/// "/" separators to make them read like a path.
|
||||
fileprivate func _errorPresentationDescription() -> String {
|
||||
return (
|
||||
self.prefix(1).map { $0._errorPresentationDescription(isFirstInCodingPath: true) }
|
||||
+ self.dropFirst(1).map { $0._errorPresentationDescription(isFirstInCodingPath: false) }
|
||||
).joined(separator: "")
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
/// When printing coding paths, delimit string keys with a '.' (period). If
|
||||
/// the key contains a period, escape it with backticks so that it can be
|
||||
/// distinguished from the delimiter. Also escape backslashes and backticks
|
||||
/// (but *not* periods) to avoid confusion with delimiters.
|
||||
internal var _escapedForCodingKeyErrorPresentationDescription: String {
|
||||
let charactersThatNeedBackticks: Set<Character> = [".", "`", "\\"]
|
||||
let charactersThatNeedEscaping: Set<Character> = ["`", "\\"]
|
||||
assert(
|
||||
charactersThatNeedEscaping.isSubset(of: charactersThatNeedBackticks),
|
||||
"Only some characters in backticks will require further escaping to disambiguate them from the backticks"
|
||||
)
|
||||
|
||||
var escaped = self
|
||||
var needsBackticks = false
|
||||
for (character, index) in zip(self, indices).reversed() {
|
||||
if charactersThatNeedBackticks.contains(character) {
|
||||
needsBackticks = true
|
||||
if charactersThatNeedEscaping.contains(character) {
|
||||
escaped.insert("\\", at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
return needsBackticks ? "`\(escaped)`" : self
|
||||
}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
@@ -3724,6 +3776,80 @@ public enum DecodingError: Error {
|
||||
}
|
||||
}
|
||||
|
||||
@available(SwiftStdlib 6.3, *)
|
||||
extension EncodingError: CustomDebugStringConvertible {
|
||||
/// A textual representation of this encoding error, intended for debugging.
|
||||
///
|
||||
/// - Important: The contents of the returned string are not guaranteed to
|
||||
/// remain stable: they may arbitrarily change in any Swift release.
|
||||
@available(SwiftStdlib 6.3, *)
|
||||
public var debugDescription: String {
|
||||
let (message, context) = switch self {
|
||||
case .invalidValue(let value, let context):
|
||||
(
|
||||
"EncodingError.invalidValue: \(String(reflecting: value)) (\(type(of: value)))",
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
var output = message
|
||||
|
||||
let contextDebugDescription = context.debugDescription
|
||||
|
||||
if !context.codingPath.isEmpty {
|
||||
output.append(". Path: \(context.codingPath._errorPresentationDescription())")
|
||||
}
|
||||
|
||||
if !contextDebugDescription.isEmpty {
|
||||
output.append(". Debug description: \(context.debugDescription)")
|
||||
}
|
||||
|
||||
if let underlyingError = context.underlyingError {
|
||||
output.append(". Underlying error: \(underlyingError)")
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
@available(SwiftStdlib 6.3, *)
|
||||
extension DecodingError: CustomDebugStringConvertible {
|
||||
/// A textual representation of this decoding error, intended for debugging.
|
||||
///
|
||||
/// - Important: The contents of the returned string are not guaranteed to
|
||||
/// remain stable: they may arbitrarily change in any Swift release.
|
||||
@available(SwiftStdlib 6.3, *)
|
||||
public var debugDescription: String {
|
||||
let (message, context) = switch self {
|
||||
case .typeMismatch(let expectedType, let context):
|
||||
("DecodingError.typeMismatch: expected value of type \(expectedType)", context)
|
||||
case .valueNotFound(let expectedType, let context):
|
||||
("DecodingError.valueNotFound: Expected value of type \(expectedType) but found null instead", context)
|
||||
case .keyNotFound(let expectedKey, let context):
|
||||
("DecodingError.keyNotFound: Key '\(expectedKey._errorPresentationDescription())' not found in keyed decoding container", context)
|
||||
case .dataCorrupted(let context):
|
||||
("DecodingError.dataCorrupted: Data was corrupted", context)
|
||||
}
|
||||
|
||||
var output = message
|
||||
|
||||
if !context.codingPath.isEmpty {
|
||||
output.append(". Path: \(context.codingPath._errorPresentationDescription())")
|
||||
}
|
||||
|
||||
let contextDebugDescription = context.debugDescription
|
||||
if !contextDebugDescription.isEmpty {
|
||||
output.append(". Debug description: \(contextDebugDescription)")
|
||||
}
|
||||
|
||||
if let underlyingError = context.underlyingError {
|
||||
output.append(". Underlying error: \(underlyingError)")
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
// The following extensions allow for easier error construction.
|
||||
|
||||
internal struct _GenericIndexKey: CodingKey, Sendable {
|
||||
|
||||
@@ -37,7 +37,7 @@ func test() {
|
||||
// FIXME: -module-abi-name ABI name is leaking.
|
||||
|
||||
let _: String = #fooMacro(1)
|
||||
// expected-error @-1 {{typeMismatch(_CompilerSwiftCompilerPluginMessageHandling.PluginToHostMessage}}
|
||||
// expected-error @-1 {{typeMismatch}}
|
||||
let _: String = #fooMacro(2)
|
||||
// expected-error @-1 {{failed to receive result from plugin (from macro 'fooMacro')}}
|
||||
let _: String = #fooMacro(3)
|
||||
|
||||
@@ -1151,3 +1151,13 @@ Added: _swift_retain_preservemost
|
||||
|
||||
// New debug environment variable for the concurrency runtime.
|
||||
Added: _concurrencyEnableTaskSlabAllocator
|
||||
|
||||
// SE-0489 Better debugDescription for EncodingError and DecodingError
|
||||
Added: _$ss13DecodingErrorO16debugDescriptionSSvg
|
||||
Added: _$ss13DecodingErrorO16debugDescriptionSSvpMV
|
||||
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesMc
|
||||
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesWP
|
||||
Added: _$ss13EncodingErrorO16debugDescriptionSSvg
|
||||
Added: _$ss13EncodingErrorO16debugDescriptionSSvpMV
|
||||
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesMc
|
||||
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesWP
|
||||
|
||||
@@ -1146,3 +1146,13 @@ Added: __swift_debug_metadataAllocatorPageSize
|
||||
|
||||
// New debug environment variable for the concurrency runtime.
|
||||
Added: _concurrencyEnableTaskSlabAllocator
|
||||
|
||||
// SE-0489 Better debugDescription for EncodingError and DecodingError
|
||||
Added: _$ss13DecodingErrorO16debugDescriptionSSvg
|
||||
Added: _$ss13DecodingErrorO16debugDescriptionSSvpMV
|
||||
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesMc
|
||||
Added: _$ss13DecodingErrorOs28CustomDebugStringConvertiblesWP
|
||||
Added: _$ss13EncodingErrorO16debugDescriptionSSvg
|
||||
Added: _$ss13EncodingErrorO16debugDescriptionSSvpMV
|
||||
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesMc
|
||||
Added: _$ss13EncodingErrorOs28CustomDebugStringConvertiblesWP
|
||||
|
||||
@@ -879,4 +879,8 @@ Func _float64ToStringImpl(_:_:_:_:) is a new API without '@available'
|
||||
Func _int64ToStringImpl(_:_:_:_:_:) is a new API without '@available'
|
||||
Func _uint64ToStringImpl(_:_:_:_:_:) is a new API without '@available'
|
||||
|
||||
// New conformances from SE-0489: Better debugDescription for EncodingError and DecodingError
|
||||
Enum DecodingError has added a conformance to an existing protocol CustomDebugStringConvertible
|
||||
Enum EncodingError has added a conformance to an existing protocol CustomDebugStringConvertible
|
||||
|
||||
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)
|
||||
|
||||
@@ -1100,6 +1100,248 @@ class TestCodable : TestCodableSuper {
|
||||
expectRoundTripEqualityThroughPlist(for: UUIDCodingWrapper(uuid), lineNumber: testLine)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DecodingError
|
||||
func expectErrorDescription(
|
||||
_ expectedErrorDescription: String,
|
||||
fromDecodingError error: DecodingError,
|
||||
lineNumber: UInt = #line
|
||||
) {
|
||||
expectEqual(String(describing: error), expectedErrorDescription, "Unexpectedly wrong error: \(error)", line: lineNumber)
|
||||
}
|
||||
|
||||
func test_decodingError_typeMismatch_nilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.typeMismatch: expected value of type String. Path: [0].address.city.birds[1].name. Debug description: This is where the debug description goes
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.typeMismatch(
|
||||
String.self,
|
||||
DecodingError.Context(
|
||||
codingPath: [0, "address", "city", "birds", 1, "name"] as [GenericCodingKey],
|
||||
debugDescription: "This is where the debug description goes"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_typeMismatch_nonNilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.typeMismatch: expected value of type String. Path: [0].address[1].street. Debug description: Some debug description. Underlying error: GenericError(name: "some generic error goes here")
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.typeMismatch(
|
||||
String.self,
|
||||
DecodingError.Context(
|
||||
codingPath: [0, "address", 1, "street"] as [GenericCodingKey],
|
||||
debugDescription: "Some debug description",
|
||||
underlyingError: GenericError(name: "some generic error goes here")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_valueNotFound_nilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.valueNotFound: Expected value of type String but found null instead. Path: [0].firstName. Debug description: Description for debugging purposes
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.valueNotFound(
|
||||
String.self,
|
||||
DecodingError.Context(
|
||||
codingPath: [0, "firstName"] as [GenericCodingKey],
|
||||
debugDescription: "Description for debugging purposes"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_valueNotFound_nonNilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.valueNotFound: Expected value of type Int but found null instead. Path: [0].population. Debug description: Here is the debug description for value-not-found. Underlying error: GenericError(name: "these aren\'t the droids you\'re looking for")
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.valueNotFound(
|
||||
Int.self,
|
||||
DecodingError.Context(
|
||||
codingPath: [0, "population"] as [GenericCodingKey],
|
||||
debugDescription: "Here is the debug description for value-not-found",
|
||||
underlyingError: GenericError(name: "these aren't the droids you're looking for")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_keyNotFound_nilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.keyNotFound: Key 'name' not found in keyed decoding container. Path: [0].address.city. Debug description: How would you describe your relationship with your debugger?
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.keyNotFound(
|
||||
GenericCodingKey(stringValue: "name"),
|
||||
DecodingError.Context(
|
||||
codingPath: [0, "address", "city"] as [GenericCodingKey],
|
||||
debugDescription: "How would you describe your relationship with your debugger?"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_keyNotFound_nonNilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.keyNotFound: Key 'name' not found in keyed decoding container. Path: [0].address.city. Debug description: Just some info to help you out. Underlying error: GenericError(name: "hey, who turned out the lights?")
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.keyNotFound(
|
||||
GenericCodingKey(stringValue: "name"),
|
||||
DecodingError.Context(
|
||||
codingPath: [0, "address", "city"] as [GenericCodingKey],
|
||||
debugDescription: "Just some info to help you out",
|
||||
underlyingError: GenericError(name: "hey, who turned out the lights?")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_dataCorrupted_emptyCodingPath() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.dataCorrupted: Data was corrupted. Debug description: The given data was not valid JSON. Underlying error: GenericError(name: "just some data corruption")
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: [] as [GenericCodingKey], // sometimes empty when generated by JSONDecoder
|
||||
debugDescription: "The given data was not valid JSON",
|
||||
underlyingError: GenericError(name: "just some data corruption")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_decodingError_dataCorrupted_nonEmptyCodingPath() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
DecodingError.dataCorrupted: Data was corrupted. Path: first.second[2]. Debug description: There was apparently some data corruption!. Underlying error: GenericError(name: "This data corruption is getting out of hand")
|
||||
"""#,
|
||||
fromDecodingError: DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: ["first", "second", 2] as [GenericCodingKey],
|
||||
debugDescription: "There was apparently some data corruption!",
|
||||
underlyingError: GenericError(name: "This data corruption is getting out of hand")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - EncodingError
|
||||
func expectErrorDescription(
|
||||
_ expectedErrorDescription: String,
|
||||
fromEncodingError error: EncodingError,
|
||||
lineNumber: UInt = #line
|
||||
) {
|
||||
expectEqual(String(describing: error), expectedErrorDescription, "Unexpectedly wrong error: \(error)", line: lineNumber)
|
||||
}
|
||||
|
||||
func test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 123 (Int). Debug description: You cannot do that!
|
||||
"""#,
|
||||
fromEncodingError: EncodingError.invalidValue(
|
||||
123 as Int,
|
||||
EncodingError.Context(
|
||||
codingPath: [] as [GenericCodingKey],
|
||||
debugDescription: "You cannot do that!"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 234 (Int). Path: first.second[2]. Debug description: You cannot do that!
|
||||
"""#,
|
||||
fromEncodingError: EncodingError.invalidValue(
|
||||
234 as Int,
|
||||
EncodingError.Context(
|
||||
codingPath: ["first", "second", 2] as [GenericCodingKey],
|
||||
debugDescription: "You cannot do that!"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 345 (Int). Debug description: You cannot do that!. Underlying error: GenericError(name: "You really cannot do that")
|
||||
"""#,
|
||||
fromEncodingError: EncodingError.invalidValue(
|
||||
345 as Int,
|
||||
EncodingError.Context(
|
||||
codingPath: [] as [GenericCodingKey],
|
||||
debugDescription: "You cannot do that!",
|
||||
underlyingError: GenericError(name: "You really cannot do that")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError() {
|
||||
expectErrorDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 456 (Int). Path: first.second[2]. Debug description: You cannot do that!. Underlying error: GenericError(name: "You really cannot do that")
|
||||
"""#,
|
||||
fromEncodingError: EncodingError.invalidValue(
|
||||
456 as Int,
|
||||
EncodingError.Context(
|
||||
codingPath: ["first", "second", 2] as [GenericCodingKey],
|
||||
debugDescription: "You cannot do that!",
|
||||
underlyingError: GenericError(name: "You really cannot do that")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func test_encodingError_pathEscaping() {
|
||||
func expectCodingPathDescription(
|
||||
_ expectedErrorDescription: String,
|
||||
fromCodingPath path: [GenericCodingKey],
|
||||
lineNumber: UInt = #line
|
||||
) {
|
||||
let error = EncodingError.invalidValue(234 as Int, EncodingError.Context(codingPath: path, debugDescription: ""))
|
||||
expectEqual(String(describing: error), expectedErrorDescription, "Unexpectedly wrong error for path \(path): \(error)", line: lineNumber)
|
||||
}
|
||||
|
||||
expectCodingPathDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 234 (Int). Path: `first.second`[3]
|
||||
"""#,
|
||||
fromCodingPath: ["first.second", 3]
|
||||
)
|
||||
|
||||
expectCodingPathDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 234 (Int). Path: [1].`second\`third`
|
||||
"""#,
|
||||
fromCodingPath: [1, "second`third"]
|
||||
)
|
||||
|
||||
expectCodingPathDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 234 (Int). Path: [1].`second\\third`
|
||||
"""#,
|
||||
fromCodingPath: [1, "second\\third"]
|
||||
)
|
||||
|
||||
expectCodingPathDescription(
|
||||
#"""
|
||||
EncodingError.invalidValue: 234 (Int). Path: [1].`two.three\\four\`five.six..seven\`\`\`eight`[9][10]
|
||||
"""#,
|
||||
fromCodingPath: [1, "two.three\\four`five.six..seven```eight", 9, 10]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper Types
|
||||
@@ -1118,6 +1360,18 @@ struct GenericCodingKey: CodingKey {
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericCodingKey: ExpressibleByStringLiteral {
|
||||
init(stringLiteral: String) {
|
||||
self.init(stringValue: stringLiteral)
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericCodingKey: ExpressibleByIntegerLiteral {
|
||||
init(integerLiteral: Int) {
|
||||
self.init(intValue: integerLiteral)
|
||||
}
|
||||
}
|
||||
|
||||
struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
|
||||
let value: T
|
||||
|
||||
@@ -1130,6 +1384,10 @@ struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable
|
||||
}
|
||||
}
|
||||
|
||||
struct GenericError: Error {
|
||||
let name: String
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
|
||||
#if !FOUNDATION_XCTEST
|
||||
@@ -1183,6 +1441,19 @@ var tests = [
|
||||
"test_URL_Plist" : TestCodable.test_URL_Plist,
|
||||
"test_UUID_JSON" : TestCodable.test_UUID_JSON,
|
||||
"test_UUID_Plist" : TestCodable.test_UUID_Plist,
|
||||
"test_decodingError_typeMismatch_nilUnderlyingError" : TestCodable.test_decodingError_typeMismatch_nilUnderlyingError,
|
||||
"test_decodingError_typeMismatch_nonNilUnderlyingError" : TestCodable.test_decodingError_typeMismatch_nonNilUnderlyingError,
|
||||
"test_decodingError_valueNotFound_nilUnderlyingError" : TestCodable.test_decodingError_valueNotFound_nilUnderlyingError,
|
||||
"test_decodingError_valueNotFound_nonNilUnderlyingError" : TestCodable.test_decodingError_valueNotFound_nonNilUnderlyingError,
|
||||
"test_decodingError_keyNotFound_nilUnderlyingError" : TestCodable.test_decodingError_keyNotFound_nilUnderlyingError,
|
||||
"test_decodingError_keyNotFound_nonNilUnderlyingError" : TestCodable.test_decodingError_keyNotFound_nonNilUnderlyingError,
|
||||
"test_decodingError_dataCorrupted_emptyCodingPath" : TestCodable.test_decodingError_dataCorrupted_emptyCodingPath,
|
||||
"test_decodingError_dataCorrupted_nonEmptyCodingPath" : TestCodable.test_decodingError_dataCorrupted_nonEmptyCodingPath,
|
||||
"test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError": TestCodable.test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError,
|
||||
"test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError": TestCodable.test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError,
|
||||
"test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError": TestCodable.test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError,
|
||||
"test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError": TestCodable.test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError,
|
||||
"test_encodingError_pathEscaping": TestCodable.test_encodingError_pathEscaping,
|
||||
]
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
Reference in New Issue
Block a user