From a534dd8b0e8a675e410ee4121183714ec772f364 Mon Sep 17 00:00:00 2001 From: cryptoAlgorithm Date: Fri, 10 Jun 2022 23:38:38 +0800 Subject: [PATCH] Add UnkeyedDecodingContainer * Handle arrays in KeyedDecodingContainer and ETFDecoder * Remove redundent doc comments --- Sources/ETFKit/Decoder/ETFDecoder.swift | 21 +- .../Decoder/KeyedDecodingContainer.swift | 289 ++---------------- .../Decoder/UnkeyedDecodingContainer.swift | 277 +++++++++++++++++ Sources/ETFKit/Encoder/PackingData+.swift | 5 +- Sources/ETFKit/Structs/ETFKey.swift | 35 +++ Tests/ETFKitTests/Decoder/CodableDecode.swift | 23 +- 6 files changed, 374 insertions(+), 276 deletions(-) create mode 100644 Sources/ETFKit/Decoder/UnkeyedDecodingContainer.swift create mode 100644 Sources/ETFKit/Structs/ETFKey.swift diff --git a/Sources/ETFKit/Decoder/ETFDecoder.swift b/Sources/ETFKit/Decoder/ETFDecoder.swift index 2ce4aea..5f986d9 100644 --- a/Sources/ETFKit/Decoder/ETFDecoder.swift +++ b/Sources/ETFKit/Decoder/ETFDecoder.swift @@ -22,32 +22,39 @@ open class ETFDecoder { } internal class _ETFDecoder: Decoder { - internal(set) public var codingPath: [CodingKey] = [] + internal(set) public var codingPath: [CodingKey] /// Contextual user-provided information for use during encoding. public var userInfo: [CodingUserInfoKey : Any] = [:] - + internal let decoded: Any? func container(keyedBy: Key.Type) throws -> KeyedDecodingContainer { guard let decoded = decoded as? [String : Any?] else { throw DecodingError.typeMismatch( - [String : Any].self, - .init(codingPath: codingPath, debugDescription: "ETF data top level is not a map") + [String : Any?].self, + .init(codingPath: codingPath, debugDescription: "Top level data type is not a map") ) } return KeyedDecodingContainer(_ETFKeyedDecodingContainer(with: decoded, referencing: self)) } func singleValueContainer() throws -> SingleValueDecodingContainer { - return self + self } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - fatalError() + guard let decoded = decoded as? [Any?] else { + throw DecodingError.typeMismatch( + [Any?].self, + .init(codingPath: codingPath, debugDescription: "Top level data type is not a list") + ) + } + return _ETFUnkeyedDecodingContainer(with: decoded, referencing: self) } - init(with decoded: Any?) { + init(with decoded: Any?, at codingPath: [CodingKey] = []) { self.decoded = decoded + self.codingPath = codingPath } } diff --git a/Sources/ETFKit/Decoder/KeyedDecodingContainer.swift b/Sources/ETFKit/Decoder/KeyedDecodingContainer.swift index aeac068..4b46955 100644 --- a/Sources/ETFKit/Decoder/KeyedDecodingContainer.swift +++ b/Sources/ETFKit/Decoder/KeyedDecodingContainer.swift @@ -24,7 +24,10 @@ internal struct _ETFKeyedDecodingContainer : KeyedDecodingContain private func ensureExists(forKey key: Key, type: T.Type) throws { guard !(try decodeNil(forKey: key)) else { - throw DecodingError.valueNotFound(type, .init(codingPath: codingPath, debugDescription: "")) + throw DecodingError.valueNotFound( + type, + .init(codingPath: decoder.codingPath, debugDescription: "Expected type \(type), found nil instead") + ) } } @@ -62,175 +65,55 @@ internal struct _ETFKeyedDecodingContainer : KeyedDecodingContain } else { throw DecodingError.keyNotFound( key, - .init(codingPath: codingPath, debugDescription: "Dict does not have key: \(key)") + .init(codingPath: decoder.codingPath, debugDescription: "Key '\(key)' not found in map") ) } } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: Float.Type, forKey key: Self.Key) throws -> Float { Float(try decode(Double.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: Int8.Type, forKey key: Self.Key) throws -> Int8 { Int8(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: Int16.Type, forKey key: Self.Key) throws -> Int16 { Int16(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: Int32.Type, forKey key: Self.Key) throws -> Int32 { Int32(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: Int64.Type, forKey key: Self.Key) throws -> Int64 { // Int64(try decode(Int.self, forKey: key)) throw DecodingError.typeMismatch( type, - .init(codingPath: [], debugDescription: "Int64 decode is not supported") + .init(codingPath: decoder.codingPath, debugDescription: "Int64 decode is not supported") ) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: UInt.Type, forKey key: Self.Key) throws -> UInt { UInt(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: UInt8.Type, forKey key: Self.Key) throws -> UInt8 { UInt8(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: UInt16.Type, forKey key: Self.Key) throws -> UInt16 { UInt16(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: UInt32.Type, forKey key: Self.Key) throws -> UInt32 { UInt32(try decode(Int.self, forKey: key)) } - /// Decodes a value of the given type for the given key. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A value of the requested type, if present for the given key - /// and convertible to the requested type. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. - /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry - /// for the given key. - /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for - /// the given key. func decode(_ type: UInt64.Type, forKey key: Self.Key) throws -> UInt64 { throw DecodingError.typeMismatch( type, - .init(codingPath: [], debugDescription: "UInt64 decode is not supported") + .init(codingPath: decoder.codingPath, debugDescription: "UInt64 decode is not supported") ) } @@ -247,206 +130,75 @@ internal struct _ETFKeyedDecodingContainer : KeyedDecodingContain /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for /// the given key. func decode(_ type: T.Type, forKey key: Self.Key) throws -> T where T : Decodable { - try ensureExists(forKey: key, type: type) - decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } + try ensureExists(forKey: key, type: type) guard let val = decoded[key.stringValue] as? T else { - throw DecodingError.typeMismatch(type, .init(codingPath: codingPath, debugDescription: "")) + throw DecodingError.typeMismatch(type, .init(codingPath: decoder.codingPath, debugDescription: "")) } return val } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: Float.Type, forKey key: Self.Key) throws -> Float? { if let val = try decodeIfPresent(Double.self, forKey: key) { return Float(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: Int8.Type, forKey key: Self.Key) throws -> Int8? { if let val = try decodeIfPresent(Int.self, forKey: key) { return Int8(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: Int16.Type, forKey key: Self.Key) throws -> Int16? { if let val = try decodeIfPresent(Int.self, forKey: key) { return Int16(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: Int32.Type, forKey key: Self.Key) throws -> Int32? { if let val = try decodeIfPresent(Int.self, forKey: key) { return Int32(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: Int64.Type, forKey key: Self.Key) throws -> Int64? { throw DecodingError.typeMismatch( type, - .init(codingPath: [], debugDescription: "Int64 decode is not supported") + .init(codingPath: decoder.codingPath, debugDescription: "Int64 decode is not supported") ) } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: UInt.Type, forKey key: Self.Key) throws -> UInt? { if let val = try decodeIfPresent(Int.self, forKey: key) { return UInt(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: UInt8.Type, forKey key: Self.Key) throws -> UInt8? { if let val = try decodeIfPresent(Int.self, forKey: key) { return UInt8(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: UInt16.Type, forKey key: Self.Key) throws -> UInt16? { if let val = try decodeIfPresent(Int.self, forKey: key) { return UInt16(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: UInt32.Type, forKey key: Self.Key) throws -> UInt32? { if let val = try decodeIfPresent(Int.self, forKey: key) { return UInt32(val) } else { return nil } } - /// Decodes a value of the given type for the given key, if present. - /// - /// This method returns `nil` if the container does not have a value - /// associated with `key`, or if the value is null. The difference between - /// these states can be distinguished with a `contains(_:)` call. - /// - /// - parameter type: The type of value to decode. - /// - parameter key: The key that the decoded value is associated with. - /// - returns: A decoded value of the requested type, or `nil` if the - /// `Decoder` does not have an entry associated with the given key, or if - /// the value is a null value. - /// - throws: `DecodingError.typeMismatch` if the encountered encoded value - /// is not convertible to the requested type. func decodeIfPresent(_ type: UInt64.Type, forKey key: Self.Key) throws -> UInt64? { throw DecodingError.typeMismatch( type, - .init(codingPath: [], debugDescription: "UInt64 decode is not supported") + .init(codingPath: decoder.codingPath, debugDescription: "UInt64 decode is not supported") ) } @@ -464,14 +216,14 @@ internal struct _ETFKeyedDecodingContainer : KeyedDecodingContain /// - throws: `DecodingError.typeMismatch` if the encountered encoded value /// is not convertible to the requested type. func decodeIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> T? where T : Decodable { - if decoded[key.stringValue] == nil { return nil } - if try decodeNil(forKey: key) { return nil } - decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } + if decoded[key.stringValue] == nil { return nil } + if try decodeNil(forKey: key) { return nil } + guard let val = decoded[key.stringValue] as? T else { - throw DecodingError.typeMismatch(type, .init(codingPath: codingPath, debugDescription: "")) + throw DecodingError.typeMismatch(type, .init(codingPath: decoder.codingPath, debugDescription: "")) } return val } @@ -496,7 +248,16 @@ internal struct _ETFKeyedDecodingContainer : KeyedDecodingContain /// - throws: `DecodingError.typeMismatch` if the encountered stored value is /// not an unkeyed container. func nestedUnkeyedContainer(forKey key: Self.Key) throws -> UnkeyedDecodingContainer { - fatalError("TODO") + decoder.codingPath.append(key) + defer { decoder.codingPath.removeLast() } + + guard let arr = decoded[key.stringValue] as? [Any?] else { + throw DecodingError.typeMismatch( + [Any?].self, + .init(codingPath: decoder.codingPath, debugDescription: "") + ) + } + return _ETFUnkeyedDecodingContainer(with: arr, referencing: decoder) } /// Returns a `Decoder` instance for decoding `super` from the container diff --git a/Sources/ETFKit/Decoder/UnkeyedDecodingContainer.swift b/Sources/ETFKit/Decoder/UnkeyedDecodingContainer.swift new file mode 100644 index 0000000..fc8e96a --- /dev/null +++ b/Sources/ETFKit/Decoder/UnkeyedDecodingContainer.swift @@ -0,0 +1,277 @@ +// +// UnkeyedDecodingContainer.swift +// +// +// Created by Vincent Kwok on 10/6/22. +// + +import Foundation + +internal struct _ETFUnkeyedDecodingContainer : UnkeyedDecodingContainer { + internal let decoded: [Any?] + private let decoder: _ETFDecoder + + private(set) public var codingPath: [CodingKey] + + /// The number of elements contained within this container. + /// + /// If the number of elements is unknown, the value is `nil`. + var count: Int? { + decoded.count + } + + /// A Boolean value indicating whether there are no more elements left to be + /// decoded in the container. + var isAtEnd: Bool { + currentIndex == decoded.count - 1 + } + + /// The current decoding index of the container (i.e. the index of the next + /// element to be decoded.) Incremented after every successful decode call. + private(set) public var currentIndex = 0 + + init(with decoded: [Any?], referencing decoder: _ETFDecoder) { + self.decoder = decoder + self.decoded = decoded + codingPath = decoder.codingPath + } + + private func ensureNotAtEnd(_ type: T.Type) throws { + guard !isAtEnd else { + throw DecodingError.valueNotFound( + type, + .init(codingPath: decoder.codingPath, debugDescription: "No more values to decode") + ) + } + } + + /// Decodes a null value. + /// + /// If the value is not null, does not increment currentIndex. + /// + /// - returns: Whether the encountered value was null. + /// - throws: `DecodingError.valueNotFound` if there are no more values to + /// decode. + mutating func decodeNil() throws -> Bool { + try ensureNotAtEnd(Any?.self) + if decoded[currentIndex] == nil { + currentIndex += 1 + return true + } else { return false } + } + + mutating func decode(_ type: Float.Type) throws -> Float { + Float(try decode(Double.self)) + } + + mutating func decode(_ type: Int8.Type) throws -> Int8 { + Int8(try decode(Int.self)) + } + + mutating func decode(_ type: Int16.Type) throws -> Int16 { + Int16(try decode(Int.self)) + } + + mutating func decode(_ type: Int32.Type) throws -> Int32 { + Int32(try decode(Int.self)) + } + + mutating func decode(_ type: Int64.Type) throws -> Int64 { + decoder.codingPath.append(_ETFKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + throw DecodingError.typeMismatch( + type, + .init(codingPath: decoder.codingPath, debugDescription: "Int64 decode is not supported") + ) + } + + mutating func decode(_ type: UInt.Type) throws -> UInt { + UInt(try decode(Int.self)) + } + + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + UInt8(try decode(Int.self)) + } + + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + UInt16(try decode(Int.self)) + } + + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + UInt32(try decode(Int.self)) + } + + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + decoder.codingPath.append(_ETFKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + throw DecodingError.typeMismatch( + type, + .init(codingPath: decoder.codingPath, debugDescription: "UInt64 decode is not supported") + ) + } + + /// Decodes a value of the given type. + /// + /// - parameter type: The type of value to decode. + /// - returns: A value of the requested type, if present for the given key + /// and convertible to the requested type. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + /// - throws: `DecodingError.valueNotFound` if the encountered encoded value + /// is null, or of there are no more values to decode. + mutating func decode(_ type: T.Type) throws -> T where T : Decodable { + guard !(try decodeNil()) else { + throw DecodingError.valueNotFound( + type, + .init(codingPath: codingPath, debugDescription: "") + ) + } + + decoder.codingPath.append(_ETFKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + guard let val = decoded[currentIndex] as? T else { + throw DecodingError.typeMismatch( + type, + .init(codingPath: decoder.codingPath, debugDescription: "") + ) + } + return val + } + + /// Decodes a value of the given type, if present. + /// + /// This method returns `nil` if the container has no elements left to + /// decode, or if the value is null. The difference between these states can + /// be distinguished by checking `isAtEnd`. + /// + /// - parameter type: The type of value to decode. + /// - returns: A decoded value of the requested type, or `nil` if the value + /// is a null value, or if there are no more elements to decode. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + mutating func decodeIfPresent(_ type: Float.Type) throws -> Float? { + if let val = try decodeIfPresent(Double.self) { + return Float(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: Int8.Type) throws -> Int8? { + if let val = try decodeIfPresent(Int.self) { + return Int8(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: Int16.Type) throws -> Int16? { + if let val = try decodeIfPresent(Int.self) { + return Int16(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: Int32.Type) throws -> Int32? { + if let val = try decodeIfPresent(Int.self) { + return Int32(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: Int64.Type) throws -> Int64? { + decoder.codingPath.append(_ETFKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + throw DecodingError.typeMismatch( + type, + .init(codingPath: decoder.codingPath, debugDescription: "Int64 decode is not supported") + ) + } + + mutating func decodeIfPresent(_ type: UInt.Type) throws -> UInt? { + if let val = try decodeIfPresent(Int.self) { + return UInt(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: UInt8.Type) throws -> UInt8? { + if let val = try decodeIfPresent(Int.self) { + return UInt8(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: UInt16.Type) throws -> UInt16? { + if let val = try decodeIfPresent(Int.self) { + return UInt16(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: UInt32.Type) throws -> UInt32? { + if let val = try decodeIfPresent(Int.self) { + return UInt32(val) + } + return nil + } + + mutating func decodeIfPresent(_ type: UInt64.Type) throws -> UInt64? { + decoder.codingPath.append(_ETFKey(index: currentIndex)) + defer { decoder.codingPath.removeLast() } + + throw DecodingError.typeMismatch( + type, + .init(codingPath: decoder.codingPath, debugDescription: "UInt64 decode is not supported") + ) + } + + /// Decodes a value of the given type, if present. + /// + /// This method returns `nil` if the container has no elements left to + /// decode, or if the value is null. The difference between these states can + /// be distinguished by checking `isAtEnd`. + /// + /// - parameter type: The type of value to decode. + /// - returns: A decoded value of the requested type, or `nil` if the value + /// is a null value, or if there are no more elements to decode. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + mutating func decodeIfPresent(_ type: T.Type) throws -> T? where T : Decodable { + if currentIndex == decoded.count - 1 { return nil } + + if let val = decoded[currentIndex], let val = val as? T { return val } + return nil + } + + /// Decodes a nested container keyed by the given type. + /// + /// - parameter type: The key type to use for the container. + /// - returns: A keyed decoding container view into `self`. + /// - throws: `DecodingError.typeMismatch` if the encountered stored value is + /// not a keyed container. + mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + fatalError("TODO") + } + + /// Decodes an unkeyed nested container. + /// + /// - returns: An unkeyed decoding container view into `self`. + /// - throws: `DecodingError.typeMismatch` if the encountered stored value is + /// not an unkeyed container. + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + fatalError("TODO") + } + + /// Decodes a nested container and returns a `Decoder` instance for decoding + /// `super` from that container. + /// + /// - returns: A new `Decoder` to pass to `super.init(from:)`. + /// - throws: `DecodingError.valueNotFound` if the encountered encoded value + /// is null, or of there are no more values to decode. + mutating func superDecoder() throws -> Decoder { + fatalError("TODO") + } +} diff --git a/Sources/ETFKit/Encoder/PackingData+.swift b/Sources/ETFKit/Encoder/PackingData+.swift index b68bd84..7d4c6fe 100644 --- a/Sources/ETFKit/Encoder/PackingData+.swift +++ b/Sources/ETFKit/Encoder/PackingData+.swift @@ -72,10 +72,7 @@ internal extension Data { } else if let data = data as? [String : Any] { try appendDict(data) } else { - throw EncodingError.invalidValue( - data, - .init(codingPath: [], debugDescription: "Unsupported data type for packing") - ) + throw ETFKit.ETFEncodingError.UnencodableType("Unsupported data type for packing") } } } diff --git a/Sources/ETFKit/Structs/ETFKey.swift b/Sources/ETFKit/Structs/ETFKey.swift new file mode 100644 index 0000000..b88ff94 --- /dev/null +++ b/Sources/ETFKit/Structs/ETFKey.swift @@ -0,0 +1,35 @@ +// +// ETFKey.swift +// +// +// Created by Vincent Kwok on 10/6/22. +// + +import Foundation + +internal struct _ETFKey: CodingKey { + public var stringValue: String + public var intValue: Int? + + public init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + public init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + public init(stringValue: String, intValue: Int?) { + self.stringValue = stringValue + self.intValue = intValue + } + + internal init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } + + internal static let `super` = Self(stringValue: "super")! +} diff --git a/Tests/ETFKitTests/Decoder/CodableDecode.swift b/Tests/ETFKitTests/Decoder/CodableDecode.swift index 525b601..d4901eb 100644 --- a/Tests/ETFKitTests/Decoder/CodableDecode.swift +++ b/Tests/ETFKitTests/Decoder/CodableDecode.swift @@ -35,8 +35,12 @@ final class CodableDecodeTests: XCTestCase { let signed: Int64 let unsigned: UInt64 } + struct UnsupportedPrimitiveOptionals: Codable { + let signed: Int64? + let unsigned: UInt64? + } - func testDecodeStruct() throws { + func testDecodePrimitiveStruct() throws { XCTAssertEqual( try ETFDecoder().decode(Primitive.self, from: Data(base64Encoded: "g3QAAAAMbQAAAApzb21lU3RyaW5nbQAAAAtoZWxsbyB3b3JsZG0AAAAIZnVubnlJbnRi///8AG0AAAAGYUZsb2F0RkAJHrhR64UfbQAAAAZ0aGlzSXNzBWZhbHNlbQAAAAV3cm9uZ0Y/v+OuZjZDiG0AAAABYWL////+bQAAAAFiYgAAAgttAAAAAWNiAAAQAG0AAAABZGEDbQAAAAFlYWZtAAAAAWZiAABQAG0AAAABZ2IADIAA")!), Primitive( @@ -115,5 +119,22 @@ final class CodableDecodeTests: XCTestCase { try ETFDecoder().decode(UnsupportedPrimitive.self, from: Data(base64Encoded: "g3QAAAACbQAAAAZzaWduZWRGw3R4GuHGLrNtAAAACHVuc2lnbmVkRkI1PHHgIgAA")!), "Unsupported 64-bit types" ) + XCTAssertThrowsError( + try ETFDecoder().decode(UnsupportedPrimitiveOptionals.self, from: Data(base64Encoded: "g3QAAAAA")!), + "Unsupported 64-bit types (optionals)" + ) + } + + struct ComplexWithArray: Codable, Equatable { + let a: [String] + } + func testDecodeComplexStruct() throws { + XCTAssertEqual( + try ETFDecoder().decode(ComplexWithArray.self, from: Data(base64Encoded: "g3QAAAABbQAAAAFhbAAAAARtAAAABGxpc3RtAAAAAm9mbQAAAAE0bQAAAAdTdHJpbmdzag==")!), + ComplexWithArray( + a: ["list", "of", "4", "Strings"] + ), + "Struct with one [String] property" + ) } }