From 58d5f1ea7c0271a420f2b363577f3538a9115494 Mon Sep 17 00:00:00 2001 From: "Alkenso (Vladimir Vashurkin)" Date: Tue, 9 May 2023 10:07:39 +0300 Subject: [PATCH 1/2] Fix case when XMLNodeDecoding wants to decode elementOrAttribute, but value is not specified. Fix error reporting when key not found for element or attribute. --- .../Decoder/XMLKeyedDecodingContainer.swift | 29 +++++++++--- .../DynamicNodeDecodingTest.swift | 44 +++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index a9710354..8e50fb91 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -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) } @@ -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(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(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 { diff --git a/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift b/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift index afd82157..480e8819 100644 --- a/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift +++ b/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift @@ -184,4 +184,48 @@ final class DynamicNodeDecodingTest: XCTestCase { let test = try decoder.decode(TestStruct.self, from: overlappingKeys) XCTAssertEqual(test, TestStruct(attribute: 123, element: "StringValue")) } + + func testMissingValues() { + let xml = + """ + + + + """ + + struct Foo: 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 } + } + + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(xml.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(xml.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(xml.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + } } From a7c16be41518f322b9a8671f4c8ad478a3daa4d7 Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Tue, 9 May 2023 21:51:11 +0200 Subject: [PATCH 2/2] Add extra tests for unkeyed containers --- .../DynamicNodeDecodingTest.swift | 171 +++++++++++++++--- 1 file changed, 145 insertions(+), 26 deletions(-) diff --git a/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift b/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift index 480e8819..0eea0c1f 100644 --- a/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift +++ b/Tests/XMLCoderTests/AdvancedFeatures/DynamicNodeDecodingTest.swift @@ -184,48 +184,167 @@ final class DynamicNodeDecodingTest: XCTestCase { let test = try decoder.decode(TestStruct.self, from: overlappingKeys) XCTAssertEqual(test, TestStruct(attribute: 123, element: "StringValue")) } + + struct Foo: Decodable { + var nested: T + } + + struct ArrayFoo: 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 testMissingValues() { - let xml = + func testGenericKeyedFailsOnMissingValues() { + let failureElementXML = + """ + + value_1 + + """ + + let failureAttributeXML = """ """ - - struct Foo: Decodable { - var nested: T + + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(failureElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } - - struct NestedElement: Decodable, DynamicNodeDecoding { - var field1: String - var field2: String - - static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .element } + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(failureElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } - - struct NestedAttribute: Decodable, DynamicNodeDecoding { - var field1: String - var field2: String - - static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .attribute } + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(failureElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } - struct NestedElementOrAttribute: Decodable, DynamicNodeDecoding { - var field1: String - var field2: String - - static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .elementOrAttribute } + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(failureAttributeXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } - - XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(xml.utf8))) { + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(failureAttributeXML.utf8))) { guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } - XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(xml.utf8))) { + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(failureAttributeXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + } + + func testGenericUnkeyedFailsOnMissingValues() { + let failureAttributeXML = + """ + + + + + + """ + + let failureElementXML = + """ + + value_1 + value_1 + value_1 + + """ + + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(failureAttributeXML.utf8))) { guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } - XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(xml.utf8))) { + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(failureAttributeXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(failureAttributeXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(failureElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(failureElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(failureElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + } + + func testGenericKeyedSucceedsWithoutMissingValues() { + let successElementXML = + """ + + value_1value_2 + + """ + + let successAttributeXML = + """ + + + + """ + + XCTAssertNoThrow(try XMLDecoder().decode(Foo.self, from: Data(successElementXML.utf8))) + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(successElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertNoThrow(try XMLDecoder().decode(Foo.self, from: Data(successElementXML.utf8))) + + XCTAssertThrowsError(try XMLDecoder().decode(Foo.self, from: Data(successAttributeXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertNoThrow(try XMLDecoder().decode(Foo.self, from: Data(successAttributeXML.utf8))) + XCTAssertNoThrow(try XMLDecoder().decode(Foo.self, from: Data(successAttributeXML.utf8))) + } + + func testGenericUnkeyedSucceedsWithoutMissingValues() { + let successElementXML = + """ + + value_1value_2 + value_1value_2 + + """ + + let successAttributeXML = + """ + + + + + """ + + XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo.self, from: Data(successElementXML.utf8))) + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(successElementXML.utf8))) { + guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } + } + XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo.self, from: Data(successElementXML.utf8))) + + XCTAssertThrowsError(try XMLDecoder().decode(ArrayFoo.self, from: Data(successAttributeXML.utf8))) { guard case DecodingError.keyNotFound = $0 else { XCTFail("Invalid error thrown: \($0)"); return } } + XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo.self, from: Data(successAttributeXML.utf8))) + XCTAssertNoThrow(try XMLDecoder().decode(ArrayFoo.self, from: Data(successAttributeXML.utf8))) } }