Skip to content

Commit

Permalink
Added unit tests for array and dictionary properties (#7)
Browse files Browse the repository at this point in the history
This implements and fixes following tests for `Decodable` containers:

### `Array`:

- empty array
- single element array
- multi element array

### `Dictionary`:

- empty dictionary
- single element dictionary
- multi element dictionary

* Added unit tests for array and dictionary properties
* Fix container tests, add AnyEmptySequence, cleanup
  • Loading branch information
regexident authored and MaxDesiatov committed Dec 16, 2018
1 parent 7e13f4b commit 20163e6
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 56 deletions.
30 changes: 14 additions & 16 deletions Sources/XMLCoder/Decoder/XMLDecoder.swift
Expand Up @@ -286,7 +286,7 @@ internal class _XMLDecoder: Decoder {
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}

guard let topContainer = self.storage.topContainer as? [String: Any] else {
guard let topContainer = storage.topContainer as? [String: Any] else {
throw DecodingError._typeMismatch(at: codingPath, expectation: [String: Any].self, reality: storage.topContainer)
}

Expand All @@ -303,12 +303,10 @@ internal class _XMLDecoder: Decoder {

let topContainer: [Any]

if let container = self.storage.topContainer as? [Any] {
if let container = storage.topContainer as? [Any] {
topContainer = container
} else if let container = self.storage.topContainer as? [AnyHashable: Any] {
topContainer = [container]
} else {
throw DecodingError._typeMismatch(at: codingPath, expectation: [Any].self, reality: storage.topContainer)
topContainer = [storage.topContainer]
}

return _XMLUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
Expand Down Expand Up @@ -727,7 +725,7 @@ extension _XMLDecoder {
switch options.dateDecodingStrategy {
case .deferredToDate:
storage.push(container: value)
defer { self.storage.popContainer() }
defer { storage.popContainer() }
return try Date(from: self)

case .secondsSince1970:
Expand All @@ -740,9 +738,9 @@ extension _XMLDecoder {

case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
let string = try self.unbox(value, as: String.self)!
let string = try unbox(value, as: String.self)!
guard let date = _iso8601Formatter.date(from: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected date string to be ISO8601-formatted."))
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Expected date string to be ISO8601-formatted."))
}

return date
Expand All @@ -760,7 +758,7 @@ extension _XMLDecoder {

case let .custom(closure):
storage.push(container: value)
defer { self.storage.popContainer() }
defer { storage.popContainer() }
return try closure(self)
}
}
Expand All @@ -771,7 +769,7 @@ extension _XMLDecoder {
switch options.dataDecodingStrategy {
case .deferredToData:
storage.push(container: value)
defer { self.storage.popContainer() }
defer { storage.popContainer() }
return try Data(from: self)

case .base64:
Expand All @@ -787,7 +785,7 @@ extension _XMLDecoder {

case let .custom(closure):
storage.push(container: value)
defer { self.storage.popContainer() }
defer { storage.popContainer() }
return try closure(self)
}
}
Expand All @@ -803,13 +801,13 @@ extension _XMLDecoder {
internal func unbox<T: Decodable>(_ value: Any, as type: T.Type) throws -> T? {
let decoded: T
if type == Date.self || type == NSDate.self {
guard let date = try self.unbox(value, as: Date.self) else { return nil }
guard let date = try unbox(value, as: Date.self) else { return nil }
decoded = date as! T
} else if type == Data.self || type == NSData.self {
guard let data = try self.unbox(value, as: Data.self) else { return nil }
guard let data = try unbox(value, as: Data.self) else { return nil }
decoded = data as! T
} else if type == URL.self || type == NSURL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
guard let urlString = try unbox(value, as: String.self) else {
return nil
}

Expand All @@ -820,11 +818,11 @@ extension _XMLDecoder {

decoded = (url as! T)
} else if type == Decimal.self || type == NSDecimalNumber.self {
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
guard let decimal = try unbox(value, as: Decimal.self) else { return nil }
decoded = decimal as! T
} else {
storage.push(container: value)
defer { self.storage.popContainer() }
defer { storage.popContainer() }
return try type.init(from: self)
}

Expand Down
12 changes: 6 additions & 6 deletions Sources/XMLCoder/Decoder/XMLDecodingStorage.swift
Expand Up @@ -15,29 +15,29 @@ internal struct _XMLDecodingStorage {

/// The container stack.
/// Elements may be any one of the XML types (String, [String : Any]).
internal private(set) var containers: [Any] = []
private var containers: [Any] = []

// MARK: - Initialization

/// Initializes `self` with no containers.
internal init() {}
init() {}

// MARK: - Modifying the Stack

internal var count: Int {
var count: Int {
return containers.count
}

internal var topContainer: Any {
var topContainer: Any {
precondition(!containers.isEmpty, "Empty container stack.")
return containers.last!
}

internal mutating func push(container: Any) {
mutating func push(container: Any) {
containers.append(container)
}

internal mutating func popContainer() {
mutating func popContainer() {
precondition(!containers.isEmpty, "Empty container stack.")
containers.removeLast()
}
Expand Down
61 changes: 37 additions & 24 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Expand Up @@ -8,6 +8,15 @@

import Foundation

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
private protocol AnyEmptySequence {
init()
}

extension Array: AnyEmptySequence {}
extension Dictionary: AnyEmptySequence {}

// MARK: Decoding Containers

internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
Expand Down Expand Up @@ -78,15 +87,15 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decodeNil(forKey key: Key) throws -> Bool {
if let entry = self.container[key.stringValue] {
if let entry = container[key.stringValue] {
return entry is NSNull
} else {
return true
}
}

public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -101,7 +110,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -116,7 +125,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -131,7 +140,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -146,7 +155,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -161,7 +170,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -176,7 +185,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -191,7 +200,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -206,7 +215,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -221,22 +230,22 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }

guard let value = try self.decoder.unbox(entry, as: UInt32.self) else {
guard let value = try decoder.unbox(entry, as: UInt32.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}

return value
}

public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -251,22 +260,22 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }

guard let value = try self.decoder.unbox(entry, as: Float.self) else {
guard let value = try decoder.unbox(entry, as: Float.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}

return value
}

public func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

Expand All @@ -281,29 +290,33 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer
}

public func decode(_ type: String.Type, forKey key: Key) throws -> String {
guard let entry = self.container[key.stringValue] else {
guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }

guard let value = try self.decoder.unbox(entry, as: String.self) else {
guard let value = try decoder.unbox(entry, as: String.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}

return value
}

public func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
guard let entry = self.container[key.stringValue] else {
if type is AnyEmptySequence.Type && container[key.stringValue] == nil {
return (type as! AnyEmptySequence.Type).init() as! T
}

guard let entry = container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}

decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
defer { decoder.codingPath.removeLast() }

guard let value = try self.decoder.unbox(entry, as: type) else {
guard let value = try decoder.unbox(entry, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}

Expand All @@ -312,7 +325,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer

public func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
defer { decoder.codingPath.removeLast() }

guard let value = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key,
Expand All @@ -330,7 +343,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer

public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
defer { decoder.codingPath.removeLast() }

guard let value = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key,
Expand All @@ -347,7 +360,7 @@ internal struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainer

private func _superDecoder(forKey key: CodingKey) throws -> Decoder {
decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
defer { decoder.codingPath.removeLast() }

let value: Any = container[key.stringValue] ?? NSNull()
return _XMLDecoder(referencing: value, at: decoder.codingPath, options: decoder.options)
Expand Down
4 changes: 2 additions & 2 deletions Sources/XMLCoder/Encoder/XMLEncoder.swift
Expand Up @@ -341,7 +341,7 @@ internal class _XMLEncoder: Encoder {
// We haven't yet pushed a container at this level; do so here.
topContainer = storage.pushKeyedContainer()
} else {
guard let container = self.storage.containers.last as? NSMutableDictionary else {
guard let container = storage.lastContainer as? NSMutableDictionary else {
preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.")
}

Expand All @@ -359,7 +359,7 @@ internal class _XMLEncoder: Encoder {
// We haven't yet pushed a container at this level; do so here.
topContainer = storage.pushUnkeyedContainer()
} else {
guard let container = self.storage.containers.last as? NSMutableArray else {
guard let container = storage.lastContainer as? NSMutableArray else {
preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.")
}

Expand Down

0 comments on commit 20163e6

Please sign in to comment.