Hi all! In the acceptance post for SE-0489: Improve EncodingError and DecodingError's printed descriptions, @xwu requested:
To that end, here's a thread where we can talk about it! If you're coming from the original review thread, please keep reading for the summary of changes I've made since then:
Changes to the implementation pull request:
- Changed the debug description to one line so we don't interfere with existing logging systems that expect
debugDescriptionto produce single-line output. This is not a hard-and-fast contract, but it was decided to be the most pragmatic approach to avoid breaking things. - Included the exact name of the type (
EncodingErrorandDecodingError) and and case that is generating the description.
In Scope for Discussion
Please weigh in with your opinions on the following!
- How should we format paths?
they/are/[0]/currently/[3]/like/this, but folks had other thoughts in the review thread, such as using different delimiters or different formatting for integer keys. Which should we use, and why? - How much do we care about keys containing special characters, whitespace, the empty string, and the like? I don't think we should spend too much time on them, but if there are things we can do to make the output both clear and compact, it may be worth it.
- What order should the parts of the error message be printed in, and should we omit empty portions of the description, such as the path, to prefer compactness and readability over uniformity?
- What should delimit the different parts of the debug description? I opted for
.(period-space) as the delimiter, with no period at the end, but that can lead to weirdness like..or!.depending on what is included in the different portions. - What kinds of escaping might we need to consider for characters inside the error messages?
Out of scope
Please keep this thread on-topic by avoiding these topics, which would have to happen in a separate Foundation proposal:
- We can't change the default
descriptionofCodingKeythat Foundation is using to construct the errors, so we have to live with them for now. We can consider improvements to Foundation in a future proposal. - We can't change the behavior of the encoders/decoders to include more surrounding context. We're just changing the formatting of the error types that we already have.
- The output format is intended to help humans debug errors, and especially to make the existing errors more readable. It is not intended to make a machine-readable, parseable format! If you need that, you can interact with the error values directly.
For more detail on the above, please refer to the "Future Directions" section of SE-0489.
Discussion Format
I don't know the best way to talk about this formatting. As a starting point, I have included examples of the debugDescription of the errors in the description of the implementation pull request. I've been constructing those manually every time I update the pull request via some multi-cursor text editing magic. But I'm open to meta-suggestions for how to talk about formatting suggestions!
First Draft
Here are my proposed debugDescription formats from the pull request. I intend to keep the pull request updated, but the following can serve as a snapshot of where the discussion started:
👀👀👀 Click to expand 👀👀👀
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")
![]()
The bike shed is open for business!