Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,17 +287,17 @@ extension XMLKeyedDecodingContainer {

switch strategy(key) {
case .attribute?:
box = try getAttributeBox(attributes, key)
box = try getAttributeBox(for: type, attributes, key)
case .element?:
box = elements
box = try getElementBox(for: type, elements, key)
case .elementOrAttribute?:
box = try getAttributeOrElementBox(attributes, elements, key)
default:
switch type {
case is XMLAttributeProtocol.Type:
box = try getAttributeBox(attributes, key)
box = try getAttributeBox(for: type, attributes, key)
case is XMLElementProtocol.Type:
box = elements
box = try getElementBox(for: type, elements, key)
default:
box = try getAttributeOrElementBox(attributes, elements, key)
}
Expand Down Expand Up @@ -339,9 +339,24 @@ extension XMLKeyedDecodingContainer {
return unwrapped
}

private func getAttributeBox(_ attributes: [KeyedBox.Attribute], _ key: Key) throws -> Box {
let attributeBox = attributes.first ?? NullBox()
return attributeBox
private func getAttributeBox<T: Decodable>(for type: T.Type, _ attributes: [KeyedBox.Attribute], _ key: Key) throws -> Box {
if let box = attributes.first { return box }
if type is AnyOptional.Type || type is XMLOptionalAttributeProtocol.Type { return NullBox() }

throw DecodingError.keyNotFound(key, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "No attribute found for key \(_errorDescription(of: key))."
))
}

private func getElementBox<T: Decodable>(for type: T.Type, _ elements: [KeyedBox.Element], _ key: Key) throws -> Box {
guard elements.isEmpty else { return elements }
if type is AnyOptional.Type || type is XMLDecodableSequence.Type { return elements }

throw DecodingError.keyNotFound(key, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "No element found for key \(_errorDescription(of: key))."
))
}

private func getAttributeOrElementBox(_ attributes: [KeyedBox.Attribute], _ elements: [KeyedBox.Element], _ key: Key) throws -> Box {
Expand Down
163 changes: 163 additions & 0 deletions Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,167 @@ final class DynamicNodeDecodingTest: XCTestCase {
let test = try decoder.decode(TestStruct.self, from: overlappingKeys)
XCTAssertEqual(test, TestStruct(attribute: 123, element: "StringValue"))
}

struct Foo<T: Decodable>: Decodable {
var nested: T
}

struct ArrayFoo<T: Decodable>: Decodable {
var nested: [T]
}

struct NestedElement: Decodable, DynamicNodeDecoding {
var field1: String
var field2: String

static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .element }
}

struct NestedAttribute: Decodable, DynamicNodeDecoding {
var field1: String
var field2: String

static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .attribute }
}

struct NestedElementOrAttribute: Decodable, DynamicNodeDecoding {
var field1: String
var field2: String

static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .elementOrAttribute }
}

func testGenericKeyedFailsOnMissingValues() {
let failureElementXML =
"""
<Root>
<nested><field1>value_1</field1></nested>
</Root>
"""

let failureAttributeXML =
"""
<Root>
<nested field1="value_1" />
</Root>
"""

XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedElement>.self, from: Data(failureElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedAttribute>.self, from: Data(failureElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedElementOrAttribute>.self, from: Data(failureElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}

XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedElement>.self, from: Data(failureAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedAttribute>.self, from: Data(failureAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedElementOrAttribute>.self, from: Data(failureAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
}

func testGenericUnkeyedFailsOnMissingValues() {
let failureAttributeXML =
"""
<Root>
<nested field1="value_1" />
<nested field1="value_1" />
<nested field1="value_1" />
</Root>
"""

let failureElementXML =
"""
<Root>
<nested><field1>value_1</field1></nested>
<nested><field1>value_1</field1></nested>
<nested><field1>value_1</field1></nested>
</Root>
"""

XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedElement>.self, from: Data(failureAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedAttribute>.self, from: Data(failureAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedElementOrAttribute>.self, from: Data(failureAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}

XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedElement>.self, from: Data(failureElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedAttribute>.self, from: Data(failureElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedElementOrAttribute>.self, from: Data(failureElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
}

func testGenericKeyedSucceedsWithoutMissingValues() {
let successElementXML =
"""
<Root>
<nested><field1>value_1</field1><field2>value_2</field2></nested>
</Root>
"""

let successAttributeXML =
"""
<Root>
<nested field1="value_1" field2="value_2" />
</Root>
"""

XCTAssertNoThrow(try XMLDecoder().decode(Foo<NestedElement>.self, from: Data(successElementXML.utf8)))
XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedAttribute>.self, from: Data(successElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertNoThrow(try XMLDecoder().decode(Foo<NestedElementOrAttribute>.self, from: Data(successElementXML.utf8)))

XCTAssertThrowsError(try XMLDecoder().decode(Foo<NestedElement>.self, from: Data(successAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertNoThrow(try XMLDecoder().decode(Foo<NestedAttribute>.self, from: Data(successAttributeXML.utf8)))
XCTAssertNoThrow(try XMLDecoder().decode(Foo<NestedElementOrAttribute>.self, from: Data(successAttributeXML.utf8)))
}

func testGenericUnkeyedSucceedsWithoutMissingValues() {
let successElementXML =
"""
<Root>
<nested><field1>value_1</field1><field2>value_2</field2></nested>
<nested><field1>value_1</field1><field2>value_2</field2></nested>
</Root>
"""

let successAttributeXML =
"""
<Root>
<nested field1="value_1" field2="value_2" />
<nested field1="value_1" field2="value_2" />
</Root>
"""

XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo<NestedElement>.self, from: Data(successElementXML.utf8)))
XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedAttribute>.self, from: Data(successElementXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo<NestedElementOrAttribute>.self, from: Data(successElementXML.utf8)))

XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo<NestedElement>.self, from: Data(successAttributeXML.utf8))) {
guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return }
}
XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo<NestedAttribute>.self, from: Data(successAttributeXML.utf8)))
XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo<NestedElementOrAttribute>.self, from: Data(successAttributeXML.utf8)))
}
}