@@ -89,6 +89,58 @@ extension CodingKey {
8989 public var debugDescription : String {
9090 return description
9191 }
92+
93+ /// A simplified description: the int value, if present, in square brackets.
94+ /// Otherwise, the string value by itself. Used when concatenating coding keys
95+ /// to form a path when printing debug information.
96+ /// - parameter isFirst: Whether this is the first key in a coding path, in
97+ /// which case we will omit the prepended '.' delimiter from string keys.
98+ fileprivate func _errorPresentationDescription( isFirstInCodingPath isFirst: Bool = true ) -> String {
99+ if let intValue {
100+ return " [ \( intValue) ] "
101+ } else {
102+ let delimiter = isFirst ? " " : " . "
103+ return " \( delimiter) \( stringValue. _escapedForCodingKeyErrorPresentationDescription) "
104+ }
105+ }
106+ }
107+
108+ extension [ any CodingKey ] {
109+ /// Concatenates the elements of an array of coding keys and joins them with
110+ /// "/" separators to make them read like a path.
111+ fileprivate func _errorPresentationDescription( ) -> String {
112+ return (
113+ self . prefix ( 1 ) . map { $0. _errorPresentationDescription ( isFirstInCodingPath: true ) }
114+ + self . dropFirst ( 1 ) . map { $0. _errorPresentationDescription ( isFirstInCodingPath: false ) }
115+ ) . joined ( separator: " " )
116+ }
117+ }
118+
119+ extension String {
120+ /// When printing coding paths, delimit string keys with a '.' (period). If
121+ /// the key contains a period, escape it with backticks so that it can be
122+ /// distinguished from the delimiter. Also escape backslashes and backticks
123+ /// (but *not* periods) to avoid confusion with delimiters.
124+ internal var _escapedForCodingKeyErrorPresentationDescription : String {
125+ let charactersThatNeedBackticks : Set < Character > = [ " . " , " ` " , " \\ " ]
126+ let charactersThatNeedEscaping : Set < Character > = [ " ` " , " \\ " ]
127+ assert (
128+ charactersThatNeedEscaping. isSubset ( of: charactersThatNeedBackticks) ,
129+ " Only some characters in backticks will require further escaping to disambiguate them from the backticks "
130+ )
131+
132+ var escaped = self
133+ var needsBackticks = false
134+ for (character, index) in zip ( self , indices) . reversed ( ) {
135+ if charactersThatNeedBackticks. contains ( character) {
136+ needsBackticks = true
137+ if charactersThatNeedEscaping. contains ( character) {
138+ escaped. insert ( " \\ " , at: index)
139+ }
140+ }
141+ }
142+ return needsBackticks ? " ` \( escaped) ` " : self
143+ }
92144}
93145
94146//===----------------------------------------------------------------------===//
@@ -3724,6 +3776,80 @@ public enum DecodingError: Error {
37243776 }
37253777}
37263778
3779+ @available ( SwiftStdlib 6 . 3 , * )
3780+ extension EncodingError : CustomDebugStringConvertible {
3781+ /// A textual representation of this encoding error, intended for debugging.
3782+ ///
3783+ /// - Important: The contents of the returned string are not guaranteed to
3784+ /// remain stable: they may arbitrarily change in any Swift release.
3785+ @available ( SwiftStdlib 6 . 3 , * )
3786+ public var debugDescription : String {
3787+ let ( message, context) = switch self {
3788+ case . invalidValue( let value, let context) :
3789+ (
3790+ " EncodingError.invalidValue: \( String ( reflecting: value) ) ( \( type ( of: value) ) ) " ,
3791+ context
3792+ )
3793+ }
3794+
3795+ var output = message
3796+
3797+ let contextDebugDescription = context. debugDescription
3798+
3799+ if !context. codingPath. isEmpty {
3800+ output. append ( " . Path: \( context. codingPath. _errorPresentationDescription ( ) ) " )
3801+ }
3802+
3803+ if !contextDebugDescription. isEmpty {
3804+ output. append ( " . Debug description: \( context. debugDescription) " )
3805+ }
3806+
3807+ if let underlyingError = context. underlyingError {
3808+ output. append ( " . Underlying error: \( underlyingError) " )
3809+ }
3810+
3811+ return output
3812+ }
3813+ }
3814+
3815+ @available ( SwiftStdlib 6 . 3 , * )
3816+ extension DecodingError : CustomDebugStringConvertible {
3817+ /// A textual representation of this decoding error, intended for debugging.
3818+ ///
3819+ /// - Important: The contents of the returned string are not guaranteed to
3820+ /// remain stable: they may arbitrarily change in any Swift release.
3821+ @available ( SwiftStdlib 6 . 3 , * )
3822+ public var debugDescription : String {
3823+ let ( message, context) = switch self {
3824+ case . typeMismatch( let expectedType, let context) :
3825+ ( " DecodingError.typeMismatch: expected value of type \( expectedType) " , context)
3826+ case . valueNotFound( let expectedType, let context) :
3827+ ( " DecodingError.valueNotFound: Expected value of type \( expectedType) but found null instead " , context)
3828+ case . keyNotFound( let expectedKey, let context) :
3829+ ( " DecodingError.keyNotFound: Key ' \( expectedKey. _errorPresentationDescription ( ) ) ' not found in keyed decoding container " , context)
3830+ case . dataCorrupted( let context) :
3831+ ( " DecodingError.dataCorrupted: Data was corrupted " , context)
3832+ }
3833+
3834+ var output = message
3835+
3836+ if !context. codingPath. isEmpty {
3837+ output. append ( " . Path: \( context. codingPath. _errorPresentationDescription ( ) ) " )
3838+ }
3839+
3840+ let contextDebugDescription = context. debugDescription
3841+ if !contextDebugDescription. isEmpty {
3842+ output. append ( " . Debug description: \( contextDebugDescription) " )
3843+ }
3844+
3845+ if let underlyingError = context. underlyingError {
3846+ output. append ( " . Underlying error: \( underlyingError) " )
3847+ }
3848+
3849+ return output
3850+ }
3851+ }
3852+
37273853// The following extensions allow for easier error construction.
37283854
37293855internal struct _GenericIndexKey : CodingKey , Sendable {
0 commit comments