Skip to content

Commit

Permalink
Provide codingPath when decoding fails due to corrupt data
Browse files Browse the repository at this point in the history
  • Loading branch information
mrackwitz committed Jan 11, 2024
1 parent 5ecd636 commit 564cbb0
Show file tree
Hide file tree
Showing 4 changed files with 463 additions and 35 deletions.
35 changes: 35 additions & 0 deletions Sources/BinaryCodable/Common/AnyCodingKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

/// Helpful for appending mixed coding keys.
struct AnyCodingKey: CodingKey {
var stringValue: String
var intValue: Int?

init(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}

init(stringValue: String) {
self.intValue = nil
self.stringValue = stringValue
}
}

extension AnyCodingKey {
init<T: CodingKey>(_ key: T) {
self.stringValue = key.stringValue
self.intValue = key.intValue
}
}

extension AnyCodingKey: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.init(stringValue: value)
}
}

extension AnyCodingKey: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self.init(intValue: value)
}
}
78 changes: 59 additions & 19 deletions Sources/BinaryCodable/Decoding/KeyedDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,33 @@ final class KeyedDecoder<Key>: AbstractDecodingNode, KeyedDecodingContainerProto
while decoder.hasMoreBytes {
let (key, dataType) = try DecodingKey.decode(from: decoder, path: path)

let data = try decoder.getData(for: dataType, path: path)

guard content[key] != nil else {
content[key] = [data]
continue
do {
let data = try decoder.getData(for: dataType, path: path)
guard content[key] != nil else {
content[key] = [data]
continue
}
} catch DecodingError.dataCorrupted(let context) {
let codingKey = {
switch key {
case .stringKey(let stringValue):
return Key(stringValue: stringValue)
case .intKey(let intValue):
return Key(intValue: intValue)
}
}()
var newCodingPath = path
if let codingKey {
newCodingPath += [codingKey]
}
let newContext = DecodingError.Context(
codingPath: newCodingPath,
debugDescription: context.debugDescription,
underlyingError: context.underlyingError
)
throw DecodingError.dataCorrupted(newContext)
}

throw DecodingError.multipleValuesForKey(path, key)
}
self.content = content.mapValues { parts in
Expand Down Expand Up @@ -60,27 +81,33 @@ final class KeyedDecoder<Key>: AbstractDecodingNode, KeyedDecodingContainerProto
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
let data = try getData(forKey: key)
if type is AnyOptional.Type {
let node = DecodingNode(data: data, isOptional: true, path: codingPath, info: userInfo)
return try T.init(from: node)
} else if let Primitive = type as? DecodablePrimitive.Type {
return try Primitive.init(decodeFrom: data, path: codingPath + [key]) as! T
} else {
let node = DecodingNode(data: data, path: codingPath, info: userInfo)
return try T.init(from: node)
try wrapError(forKey: key) {
let data = try getData(forKey: key)
if type is AnyOptional.Type {
let node = DecodingNode(data: data, isOptional: true, path: codingPath, info: userInfo)
return try T.init(from: node)
} else if let Primitive = type as? DecodablePrimitive.Type {
return try Primitive.init(decodeFrom: data, path: codingPath + [key]) as! T
} else {
let node = DecodingNode(data: data, path: codingPath, info: userInfo)
return try T.init(from: node)
}
}
}

func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
let data = try getData(forKey: key)
let container = try KeyedDecoder<NestedKey>(data: data, path: codingPath, info: userInfo)
return KeyedDecodingContainer(container)
try wrapError(forKey: key) {
let data = try getData(forKey: key)
let container = try KeyedDecoder<NestedKey>(data: data, path: codingPath, info: userInfo)
return KeyedDecodingContainer(container)
}
}

func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
let data = try getData(forKey: key)
return try UnkeyedDecoder(data: data, path: codingPath, info: userInfo)
try wrapError(forKey: key) {
let data = try getData(forKey: key)
return try UnkeyedDecoder(data: data, path: codingPath, info: userInfo)
}
}

func superDecoder() throws -> Decoder {
Expand All @@ -92,4 +119,17 @@ final class KeyedDecoder<Key>: AbstractDecodingNode, KeyedDecodingContainerProto
let data = try getData(forKey: key)
return DecodingNode(data: data, path: codingPath, info: userInfo)
}

private func wrapError<T>(forKey key: Key, _ block: () throws -> T) throws -> T {
do {
return try block()
} catch DecodingError.dataCorrupted(let context) {
let newContext = DecodingError.Context(
codingPath: codingPath + [key] + context.codingPath,
debugDescription: context.debugDescription,
underlyingError: context.underlyingError
)
throw DecodingError.dataCorrupted(newContext)
}
}
}
46 changes: 30 additions & 16 deletions Sources/BinaryCodable/Decoding/UnkeyedDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,30 +50,36 @@ final class UnkeyedDecoder: AbstractDecodingNode, UnkeyedDecodingContainer {

func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
defer { currentIndex += 1 }
if type is AnyOptional.Type {
let node = DecodingNode(decoder: decoder, isOptional: true, path: codingPath, info: userInfo, isInUnkeyedContainer: true)
return try T.init(from: node)
} else if let Primitive = type as? DecodablePrimitive.Type {
let dataType = Primitive.dataType
let data = try decoder.getData(for: dataType, path: codingPath)
return try Primitive.init(decodeFrom: data, path: codingPath) as! T
} else {
let node = DecodingNode(decoder: decoder, path: codingPath, info: userInfo, isInUnkeyedContainer: true)
return try T.init(from: node)
return try wrapError {
if type is AnyOptional.Type {
let node = DecodingNode(decoder: decoder, isOptional: true, path: codingPath, info: userInfo, isInUnkeyedContainer: true)
return try T.init(from: node)
} else if let Primitive = type as? DecodablePrimitive.Type {
let dataType = Primitive.dataType
let data = try decoder.getData(for: dataType, path: codingPath)
return try Primitive.init(decodeFrom: data, path: codingPath) as! T
} else {
let node = DecodingNode(decoder: decoder, path: codingPath, info: userInfo, isInUnkeyedContainer: true)
return try T.init(from: node)
}
}
}

func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
currentIndex += 1
let data = try decoder.getData(for: .variableLength, path: codingPath)
let container = try KeyedDecoder<NestedKey>(data: data, path: codingPath, info: userInfo)
return KeyedDecodingContainer(container)
return try wrapError {
let data = try decoder.getData(for: .variableLength, path: codingPath)
let container = try KeyedDecoder<NestedKey>(data: data, path: codingPath, info: userInfo)
return KeyedDecodingContainer(container)
}
}

func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
currentIndex += 1
let data = try decoder.getData(for: .variableLength, path: codingPath)
return try UnkeyedDecoder(data: data, path: codingPath, info: userInfo)
return try wrapError {
let data = try decoder.getData(for: .variableLength, path: codingPath)
return try UnkeyedDecoder(data: data, path: codingPath, info: userInfo)
}
}

func superDecoder() throws -> Decoder {
Expand All @@ -85,7 +91,15 @@ final class UnkeyedDecoder: AbstractDecodingNode, UnkeyedDecodingContainer {
do {
return try block()
} catch DecodingError.dataCorrupted(let context) {
throw DecodingError.dataCorruptedError(in: self, debugDescription: context.debugDescription)
var codingPath = codingPath
codingPath.append(AnyCodingKey(intValue: currentIndex))
codingPath.append(contentsOf: context.codingPath)
let newContext = DecodingError.Context(
codingPath: codingPath,
debugDescription: context.debugDescription,
underlyingError: context.underlyingError
)
throw DecodingError.dataCorrupted(newContext)
}
}
}
Loading

0 comments on commit 564cbb0

Please sign in to comment.