@@ -18,10 +18,11 @@ extension ShadowDecoder {
1818 init ( decoder: ShadowDecoder ) throws {
1919 switch decoder. codingPath. count {
2020 case 2 : let key = ( row: decoder. codingPath [ 0 ] , field: decoder. codingPath [ 1 ] )
21- let r = try key. row. intValue ?! DecodingError . invalidRowKey ( codingPath: decoder. codingPath)
21+ let r = try key. row. intValue ?! DecodingError . invalidKey ( forRow : key . row , codingPath: decoder. codingPath)
2222 let f = try decoder. source. fieldIndex ( forKey: key. field, codingPath: decoder. codingPath)
2323 self . focus = . field( r, f)
24- case 1 : let r = try decoder. codingPath [ 0 ] . intValue ?! DecodingError . invalidRowKey ( codingPath: decoder. codingPath)
24+ case 1 : let key = decoder. codingPath [ 0 ]
25+ let r = try key. intValue ?! DecodingError . invalidKey ( forRow: key, codingPath: decoder. codingPath)
2526 self . focus = . row( r)
2627 case 0 : self . focus = . file
2728 default : throw DecodingError . invalidContainerRequest ( codingPath: decoder. codingPath)
@@ -165,19 +166,19 @@ extension ShadowDecoder.SingleValueContainer {
165166 private func lowlevelDecode< T> ( transform: ( String ) -> T ? ) throws -> T {
166167 switch self . focus {
167168 case . field( let rowIndex, let fieldIndex) :
168- let value = try self . decoder. source. field ( at: rowIndex, fieldIndex)
169- return try transform ( value ) ?! DecodingError . typeMismatch ( T . self, . invalidTransformation ( value : value , codingPath: self . codingPath) )
169+ let string = try self . decoder. source. field ( at: rowIndex, fieldIndex)
170+ return try transform ( string ) ?! DecodingError . invalid ( type : T . self, string : string , codingPath: self . codingPath)
170171 case . row( let rowIndex) :
171172 // Values are only allowed to be decoded directly from a single value container in "row level" if the CSV rows have a single column.
172173 guard self . decoder. source. numFields == 1 else { throw DecodingError . invalidNestedRequired ( codingPath: self . codingPath) }
173- let value = try self . decoder. source. field ( at: rowIndex, 0 )
174- return try transform ( value ) ?! DecodingError . typeMismatch ( T . self, . invalidTransformation ( value : value , codingPath: self . codingPath + [ DecodingKey ( 0 ) ] ) )
174+ let string = try self . decoder. source. field ( at: rowIndex, 0 )
175+ return try transform ( string ) ?! DecodingError . invalid ( type : T . self, string : string , codingPath: self . codingPath + [ DecodingKey ( 0 ) ] )
175176 case . file:
176177 let source = self . decoder. source
177178 // Values are only allowed to be decoded directly from a single value container in "file level" if the CSV file has a single row with a single column.
178179 if source. isRowAtEnd ( index: 1 ) , source. numFields == 1 {
179- let value = try self . decoder. source. field ( at: 0 , 0 )
180- return try transform ( value ) ?! DecodingError . typeMismatch ( T . self, . invalidTransformation ( value : value , codingPath: self . codingPath + [ DecodingKey ( 0 ) , DecodingKey ( 0 ) ] ) )
180+ let string = try self . decoder. source. field ( at: 0 , 0 )
181+ return try transform ( string ) ?! DecodingError . invalid ( type : T . self, string : string , codingPath: self . codingPath + [ DecodingKey ( 0 ) , DecodingKey ( 0 ) ] )
181182 } else {
182183 throw DecodingError . invalidNestedRequired ( codingPath: self . codingPath)
183184 }
@@ -199,14 +200,10 @@ extension ShadowDecoder.SingleValueContainer {
199200 return Foundation . Date ( timeIntervalSince1970: number / 1000.0 )
200201 case . iso8601:
201202 let string = try self . decode ( String . self)
202- return try DateFormatter . iso8601. date ( from: string) ?! DecodingError . dataCorrupted ( . init(
203- codingPath: self . codingPath,
204- debugDescription: " The string ' \( string) ' couldn't be transformed into a Date using the '.iso8601' strategy. " ) )
203+ return try DateFormatter . iso8601. date ( from: string) ?! DecodingError . invalidDateISO ( string: string, codingPath: self . codingPath)
205204 case . formatted( let formatter) :
206205 let string = try self . decode ( String . self)
207- return try formatter. date ( from: string) ?! DecodingError . dataCorrupted ( . init(
208- codingPath: self . codingPath,
209- debugDescription: " The string ' \( string) ' couldn't be transformed into a Date using the '.formatted' strategy. " ) )
206+ return try formatter. date ( from: string) ?! DecodingError . invalidDateFormatted ( string: string, codingPath: self . codingPath)
210207 case . custom( let closure) :
211208 return try closure ( self . decoder)
212209 }
@@ -221,7 +218,7 @@ extension ShadowDecoder.SingleValueContainer {
221218 return try Data ( from: self . decoder)
222219 case . base64:
223220 let string = try self . decode ( String . self)
224- return try Data ( base64Encoded: string) ?! DecodingError . dataCorrupted ( . init ( codingPath : decoder . codingPath , debugDescription : " The following string is not valid Base64: \n ' \( string ) ' " ) )
221+ return try Data ( base64Encoded: string) ?! DecodingError . invalidData64 ( string : string , codingPath : self . codingPath )
225222 case . custom( let closure) :
226223 return try closure ( self . decoder)
227224 }
@@ -234,9 +231,7 @@ extension ShadowDecoder.SingleValueContainer {
234231 switch self . decoder. source. configuration. decimalStrategy {
235232 case . locale( let locale) :
236233 let string = try self . decode ( String . self)
237- return try Decimal ( string: string, locale: locale) ?! DecodingError . dataCorrupted ( . init(
238- codingPath: self . codingPath,
239- debugDescription: " The string ' \( string) ' couldn't be transformed into a Decimal using the '.locale' strategy " ) )
234+ return try Decimal ( string: string, locale: locale) ?! DecodingError . invalidDecimal ( string: string, locale: locale, codingPath: self . codingPath)
240235 case . custom( let closure) :
241236 return try closure ( self . decoder)
242237 }
@@ -249,3 +244,63 @@ extension ShadowDecoder.SingleValueContainer {
249244 try self . lowlevelDecode { URL ( string: $0) }
250245 }
251246}
247+
248+ fileprivate extension DecodingError {
249+ /// Error raised when a coding key representing a row within the CSV file cannot be transformed into an integer value.
250+ /// - parameter codingPath: The whole coding path, including the invalid row key.
251+ static func invalidKey( forRow key: CodingKey , codingPath: [ CodingKey ] ) -> DecodingError {
252+ DecodingError . keyNotFound ( key, . init(
253+ codingPath: codingPath,
254+ debugDescription: " The coding key identifying a CSV row couldn't be transformed into an integer value. " ) )
255+ }
256+ /// Error raised when a single value container is requested on an invalid coding path.
257+ /// - parameter codingPath: The full chain of containers which generated this error.
258+ static func invalidContainerRequest( codingPath: [ CodingKey ] ) -> DecodingError {
259+ DecodingError . dataCorrupted (
260+ Context ( codingPath: codingPath,
261+ debugDescription: " CSV doesn't support more than two nested decoding container. " )
262+ )
263+ }
264+ /// Error raised when a value is decoded, but a container was expected by the decoder.
265+ static func invalidNestedRequired( codingPath: [ CodingKey ] ) -> DecodingError {
266+ DecodingError . dataCorrupted ( . init(
267+ codingPath: codingPath,
268+ debugDescription: " A nested container is needed to decode CSV row values " ) )
269+ }
270+ /// Error raised when transforming a `String` value into another type.
271+ /// - parameter value: The `String` value, which couldn't be transformed.
272+ /// - parameter codingPath: The full chain of containers when this error was generated.
273+ static func invalid< T> ( type: T . Type , string: String , codingPath: [ CodingKey ] ) -> DecodingError {
274+ DecodingError . typeMismatch ( type,
275+ Context ( codingPath: codingPath,
276+ debugDescription: " The field ' \( string) ' was not of the expected type ' \( type) '. " )
277+ )
278+ }
279+ /// Error raised when a string value cannot be transformed into a `Date` using the ISO 8601 format.
280+ static func invalidDateISO( string: String , codingPath: [ CodingKey ] ) -> DecodingError {
281+ DecodingError . dataCorrupted (
282+ Context ( codingPath: codingPath,
283+ debugDescription: " The field ' \( string) ' couldn't be transformed into a Date using the '.iso8601' strategy. " )
284+ )
285+ }
286+ /// Error raised when a string value cannot be transformed into a `Date` using the ISO 8601 format.
287+ static func invalidDateFormatted( string: String , codingPath: [ CodingKey ] ) -> DecodingError {
288+ DecodingError . dataCorrupted (
289+ Context ( codingPath: codingPath,
290+ debugDescription: " The field ' \( string) ' couldn't be transformed into a Date using the '.formatted' strategy. " )
291+ )
292+ }
293+ /// Error raised when a string value cannot be transformed into a Base64 data blob.
294+ static func invalidData64( string: String , codingPath: [ CodingKey ] ) -> DecodingError {
295+ DecodingError . dataCorrupted (
296+ Context ( codingPath: codingPath,
297+ debugDescription: " The field ' \( string) ' couldn't be transformed into a Base64 data blob. " )
298+ )
299+ }
300+ /// Error raised when a string value cannot be transformed into a decimal number.
301+ static func invalidDecimal( string: String , locale: Locale ? , codingPath: [ CodingKey ] ) -> DecodingError {
302+ var description = " The string ' \( string) ' couldn't be transformed into a Decimal using the '.locale' strategy. "
303+ if let l = locale { description. append ( " with locale ' \( l) ' " ) }
304+ return DecodingError . dataCorrupted ( Context ( codingPath: codingPath, debugDescription: description) )
305+ }
306+ }
0 commit comments