Skip to content
Prev Previous commit
Next Next commit
Implement DynamicNodeDecoding with tests
  • Loading branch information
MaxDesiatov committed Mar 24, 2019
commit de36e3ad84d4d2af0f10a206830f4f3eebfd278e
5 changes: 4 additions & 1 deletion Sources/XMLCoder/Decoder/XMLDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,10 @@ open class XMLDecoder {
) -> ((CodingKey) -> NodeDecoding) {
switch self {
case .deferredToDecoder:
return { _ in .elementOrAttribute }
guard let dynamicType = codableType as? DynamicNodeDecoding.Type else {
return { _ in .elementOrAttribute }
}
return dynamicType.nodeDecoding(for:)
case let .custom(closure):
return closure(codableType, decoder)
}
Expand Down
214 changes: 214 additions & 0 deletions Tests/XMLCoderTests/DynamicNodeDecodingTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
//
// DynamicNodeEncodingTest.swift
// XMLCoderTests
//
// Created by Joseph Mattiello on 1/23/19.
//

import Foundation
import XCTest
@testable import XMLCoder

private let overlappingKeys = """
<?xml version="1.0" encoding="UTF-8"?>
<test key="123">
<key>
StringValue
</key>
</test>
""".data(using: .utf8)!

private let libraryXMLYN = """
<?xml version="1.0" encoding="UTF-8"?>
<library count="2">
<book id="123">
<id>123</id>
<title>Cat in the Hat</title>
<category main="Y"><value>Kids</value></category>
<category main="N"><value>Wildlife</value></category>
</book>
<book id="456">
<id>456</id>
<title>1984</title>
<category main="Y"><value>Classics</value></category>
<category main="N"><value>News</value></category>
</book>
</library>
""".data(using: .utf8)!

private let libraryXMLYNStrategy = """
<?xml version="1.0" encoding="UTF-8"?>
<library>
<count>2</count>
<book title="Cat in the Hat">
<id>123</id>
<category>
<main>true</main>
<value>Kids</value>
</category>
<category>
<main>false</main>
<value>Wildlife</value>
</category>
</book>
<book title="1984">
<id>456</id>
<category>
<main>true</main>
<value>Classics</value>
</category>
<category>
<main>false</main>
<value>News</value>
</category>
</book>
</library>
""".data(using: .utf8)!

private struct TestStruct: Codable, Equatable, DynamicNodeDecoding {
let attribute: Int
let element: String

private enum CodingKeys: CodingKey {
case attribute
case element

public var stringValue: String {
return "key"
}
}

static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
switch key {
case CodingKeys.attribute:
return .attribute
default:
return .element
}
}
}

private struct Library: Codable, Equatable, DynamicNodeDecoding {
let count: Int
let books: [Book]

enum CodingKeys: String, CodingKey {
case count
case books = "book"
}

static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding {
switch key {
case CodingKeys.count:
return .attribute
default:
return .element
}
}
}

private struct Book: Codable, Equatable, DynamicNodeEncoding {
let id: UInt
let title: String
let categories: [Category]

enum CodingKeys: String, CodingKey {
case id
case title
case categories = "category"
}

static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case Book.CodingKeys.id: return .both
default: return .element
}
}
}

private struct Category: Codable, Equatable, DynamicNodeEncoding {
let main: Bool
let value: String

private enum CodingKeys: String, CodingKey {
case main
case value
}

static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case Category.CodingKeys.main:
return .attribute
default:
return .element
}
}
}

final class DynamicNodeDecodingTest: XCTestCase {
func testDecode() throws {
let decoder = XMLDecoder()
decoder.errorContextLength = 10

let library = try decoder.decode(Library.self, from: libraryXMLYN)
XCTAssertEqual(library.books.count, 2)
XCTAssertEqual(library.count, 2)

let book1 = library.books[0]
XCTAssertEqual(book1.id, 123)
XCTAssertEqual(book1.title, "Cat in the Hat")

let book1Categories = book1.categories
XCTAssertEqual(book1Categories.count, 2)
XCTAssertEqual(book1Categories[0].value, "Kids")
XCTAssertTrue(book1Categories[0].main)
XCTAssertEqual(book1Categories[1].value, "Wildlife")
XCTAssertFalse(book1Categories[1].main)

let book2 = library.books[1]
XCTAssertEqual(book2.id, 456)
XCTAssertEqual(book2.title, "1984")

let book2Categories = book2.categories
XCTAssertEqual(book2Categories.count, 2)
XCTAssertEqual(book2Categories[0].value, "Classics")
XCTAssertTrue(book2Categories[0].main)
XCTAssertEqual(book2Categories[1].value, "News")
XCTAssertFalse(book2Categories[1].main)
}

func testStrategyPriority() throws {
let decoder = XMLDecoder()
decoder.errorContextLength = 10

decoder.nodeDecodingStrategy = .custom { type, _ in
{ key in
guard
type == Book.self &&
key.stringValue == Book.CodingKeys.title.stringValue
else {
return .element
}

return .attribute
}
}

let library = try decoder.decode(Library.self, from: libraryXMLYNStrategy)
XCTAssertEqual(library.count, 2)
}

func testOverlappingKeys() throws {
let decoder = XMLDecoder()
decoder.errorContextLength = 10

let test = try decoder.decode(TestStruct.self, from: overlappingKeys)
XCTAssertEqual(test, TestStruct(attribute: 123, element: "StringValue"))
}

static var allTests = [
("testDecode", testDecode),
("testStrategyPriority", testStrategyPriority),
("testOverlappingKeys", testOverlappingKeys),
]
}
2 changes: 1 addition & 1 deletion Tests/XMLCoderTests/SingleChildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct ProudParent: Codable, Equatable {
var myChildAge: [Int]
}

final class Test: XCTestCase {
final class SingleChildTest: XCTestCase {
func testEncoder() throws {
let encoder = XMLEncoder()

Expand Down
4 changes: 4 additions & 0 deletions XMLCoder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
D14D8A8621F1D6B300B0D31A /* SingleChildTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */; };
D158F12F2229892C0032B449 /* DynamicNodeDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D158F12E2229892C0032B449 /* DynamicNodeDecoding.swift */; };
D162674321F9B2AF0056D1D8 /* OptionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D162674121F9B2850056D1D8 /* OptionalTests.swift */; };
D1761D1F2247F04500F53CEF /* DynamicNodeDecodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1761D1E2247F04500F53CEF /* DynamicNodeDecodingTest.swift */; };
D1CB1EF521EA9599009CAF02 /* RJITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* RJITest.swift */; };
D1CFC8242226B13F00B03222 /* NamespaceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CFC8222226AFB400B03222 /* NamespaceTest.swift */; };
D1E0C85321D8E65E0042A261 /* ErrorContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E0C85121D8E6540042A261 /* ErrorContextTest.swift */; };
Expand Down Expand Up @@ -203,6 +204,7 @@
D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleChildTests.swift; sourceTree = "<group>"; };
D158F12E2229892C0032B449 /* DynamicNodeDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeDecoding.swift; sourceTree = "<group>"; };
D162674121F9B2850056D1D8 /* OptionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalTests.swift; sourceTree = "<group>"; };
D1761D1E2247F04500F53CEF /* DynamicNodeDecodingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeDecodingTest.swift; sourceTree = "<group>"; };
D1CFC8222226AFB400B03222 /* NamespaceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamespaceTest.swift; sourceTree = "<group>"; };
D1E0C85121D8E6540042A261 /* ErrorContextTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorContextTest.swift; sourceTree = "<group>"; };
D1E0C85421D91EBF0042A261 /* Metatypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metatypes.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -389,6 +391,7 @@
D1FC040421C7EF8200065B43 /* RJISample.swift */,
B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */,
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */,
D1761D1E2247F04500F53CEF /* DynamicNodeDecodingTest.swift */,
A61DCCD621DF8DB300C0A19D /* ClassTests.swift */,
D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */,
D1CFC8222226AFB400B03222 /* NamespaceTest.swift */,
Expand Down Expand Up @@ -638,6 +641,7 @@
D1CFC8242226B13F00B03222 /* NamespaceTest.swift in Sources */,
BF9457D021CBB516005ACFDE /* UIntBoxTests.swift in Sources */,
OBJ_80 /* BooksTest.swift in Sources */,
D1761D1F2247F04500F53CEF /* DynamicNodeDecodingTest.swift in Sources */,
OBJ_81 /* BreakfastTest.swift in Sources */,
OBJ_82 /* CDCatalog.swift in Sources */,
BF63EF0021CCDED2001D38C5 /* XMLStackParserTests.swift in Sources */,
Expand Down