Skip to content

Commit

Permalink
Made sure node encoding strategy is considered for values inside unke…
Browse files Browse the repository at this point in the history
…yed containers (#2)

This fixes a bug that would make `XMLEncoder` ignore the node-encoding strategy for values contained in unkeyed collections.

Given a set of types like this …

```swift
struct Foo: Codable {
  var bar = Bar()
  var unkeyedBars = [Bar()]
  var keyedBars = ["foo": Bar()]
}

struct Bar: Codable {
  let value = "baz"
  
  static func nodeEncoding(forKey codingKey: CodingKey) -> XMLEncoder.NodeEncoding {
    return .attribute
  }
}
```

… and an encoder configured like this …

```swift
let encoder = XMLEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.nodeEncodingStrategy = .custom { codableType, encoder in
  guard let barType = codableType as? Bar.Type else {
    return { _ in return .default }
  }
  return barType.nodeEncoding(forKey:)
}
```

… produces output that looks like this …

```xml
<foo>
    <bar value="baz" />
    <unkeyed_bars>
        <value>baz</value>
    </unkeyed_bars>
    <keyed_bars>
        <foo value="baz" />
    </keyed_bars>
</foo>
```

… while the correct output would look like this …

```xml
<foo>
    <unkeyed_bars value="baz" />
    <bar value="baz" />
    <keyed_bars>
        <foo value="baz" />
    </keyed_bars>
</foo>
```

* Make sure node encoding strategy is considered for values inside unkeyed containers
* Add unit tests for testing node-encoding strategy
  • Loading branch information
regexident authored and MaxDesiatov committed Nov 20, 2018
1 parent 811eced commit 18ce801
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 1 deletion.
10 changes: 9 additions & 1 deletion Sources/XMLCoder/Encoder/XMLEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,15 @@ fileprivate struct _XMLUnkeyedEncodingContainer : UnkeyedEncodingContainer {

public mutating func encode<T : Encodable>(_ value: T) throws {
self.encoder.codingPath.append(_XMLKey(index: self.count))
defer { self.encoder.codingPath.removeLast() }
let nodeEncodings = self.encoder.options.nodeEncodingStrategy.nodeEncodings(
forType: T.self,
with: self.encoder
)
self.encoder.nodeEncodings.append(nodeEncodings)
defer {
let _ = self.encoder.nodeEncodings.removeLast()
self.encoder.codingPath.removeLast()
}
self.container.add(try self.encoder.box(value))
}

Expand Down
192 changes: 192 additions & 0 deletions Tests/XMLCoderTests/NodeEncodingStrategyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import XCTest
@testable import XMLCoder

class NodeEncodingStrategyTests: XCTestCase {
fileprivate struct SingleContainer: Encodable {
let element: Element

enum CodingKeys: String, CodingKey {
case element = "element"
}
}

fileprivate struct KeyedContainer: Encodable {
let elements: [String: Element]

enum CodingKeys: String, CodingKey {
case elements = "element"
}
}

fileprivate struct UnkeyedContainer: Encodable {
let elements: [Element]

enum CodingKeys: String, CodingKey {
case elements = "element"
}
}

fileprivate struct Element: Encodable {
let key: String = "value"

enum CodingKeys: CodingKey {
case key
}

static func nodeEncoding(forKey codingKey: CodingKey) -> XMLEncoder.NodeEncoding {
return .attribute
}
}

func testSingleContainer() {
let encoder = XMLEncoder()
encoder.outputFormatting = .prettyPrinted

do {
let container = SingleContainer(element: Element())
let data = try encoder.encode(container, withRootKey: "container")
let xml = String(data: data, encoding: .utf8)!

let expected =
"""
<container>
<element>
<key>value</key>
</element>
</container>
"""
XCTAssertEqual(xml, expected)
} catch {
XCTAssert(false, "failed to decode the example: \(error)")
}

encoder.nodeEncodingStrategy = .custom { codableType, encoder in
guard let barType = codableType as? Element.Type else {
return { _ in return .default }
}
return barType.nodeEncoding(forKey:)
}

do {
let container = SingleContainer(element: Element())
let data = try encoder.encode(container, withRootKey: "container")
let xml = String(data: data, encoding: .utf8)!

let expected =
"""
<container>
<element key=\"value\" />
</container>
"""
XCTAssertEqual(xml, expected)
} catch {
XCTAssert(false, "failed to decode the example: \(error)")
}
}

func testKeyedContainer() {
let encoder = XMLEncoder()
encoder.outputFormatting = .prettyPrinted

do {
let container = KeyedContainer(elements: ["first": Element()])
let data = try encoder.encode(container, withRootKey: "container")
let xml = String(data: data, encoding: .utf8)!

let expected =
"""
<container>
<element>
<first>
<key>value</key>
</first>
</element>
</container>
"""
XCTAssertEqual(xml, expected)
} catch {
XCTAssert(false, "failed to decode the example: \(error)")
}

encoder.nodeEncodingStrategy = .custom { codableType, encoder in
guard let barType = codableType as? Element.Type else {
return { _ in return .default }
}
return barType.nodeEncoding(forKey:)
}

do {
let container = KeyedContainer(elements: ["first": Element()])
let data = try encoder.encode(container, withRootKey: "container")
let xml = String(data: data, encoding: .utf8)!

let expected =
"""
<container>
<element>
<first key=\"value\" />
</element>
</container>
"""
XCTAssertEqual(xml, expected)
} catch {
XCTAssert(false, "failed to decode the example: \(error)")
}
}

func testUnkeyedContainer() {
let encoder = XMLEncoder()
encoder.outputFormatting = .prettyPrinted

do {
let container = UnkeyedContainer(elements: [Element(), Element()])
let data = try encoder.encode(container, withRootKey: "container")
let xml = String(data: data, encoding: .utf8)!

let expected =
"""
<container>
<element>
<key>value</key>
</element>
<element>
<key>value</key>
</element>
</container>
"""
XCTAssertEqual(xml, expected)
} catch {
XCTAssert(false, "failed to decode the example: \(error)")
}

encoder.nodeEncodingStrategy = .custom { codableType, encoder in
guard let barType = codableType as? Element.Type else {
return { _ in return .default }
}
return barType.nodeEncoding(forKey:)
}

do {
let container = UnkeyedContainer(elements: [Element(), Element()])
let data = try encoder.encode(container, withRootKey: "container")
let xml = String(data: data, encoding: .utf8)!

let expected =
"""
<container>
<element key="value" />
<element key="value" />
</container>
"""
XCTAssertEqual(xml, expected)
} catch {
XCTAssert(false, "failed to decode the example: \(error)")
}
}

static var allTests = [
("testSingleContainer", testSingleContainer),
("testKeyedContainer", testKeyedContainer),
("testUnkeyedContainer", testUnkeyedContainer),
]
}
4 changes: 4 additions & 0 deletions XMLCoder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
BFE1C59121A4242300EA0458 /* NodeEncodingStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE1C58F21A4232100EA0458 /* NodeEncodingStrategyTests.swift */; };
OBJ_35 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
OBJ_41 /* XMLCoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* XMLCoderTests.swift */; };
OBJ_43 /* XMLCoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "XMLParsing::XMLParsing::Product" /* XMLCoder.framework */; };
Expand Down Expand Up @@ -56,6 +57,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
BFE1C58F21A4232100EA0458 /* NodeEncodingStrategyTests.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = NodeEncodingStrategyTests.swift; sourceTree = "<group>"; tabWidth = 4; };
D1BCEBCF21943CA6000B550F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D1BCEBD021943F09000B550F /* LinuxMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LinuxMain.swift; path = Tests/LinuxMain.swift; sourceTree = "<group>"; };
D1BCEBD12194416E000B550F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
Expand Down Expand Up @@ -124,6 +126,7 @@
isa = PBXGroup;
children = (
OBJ_25 /* XMLCoderTests.swift */,
BFE1C58F21A4232100EA0458 /* NodeEncodingStrategyTests.swift */,
);
name = XMLCoderTests;
path = Tests/XMLCoderTests;
Expand Down Expand Up @@ -290,6 +293,7 @@
buildActionMask = 0;
files = (
OBJ_41 /* XMLCoderTests.swift in Sources */,
BFE1C59121A4242300EA0458 /* NodeEncodingStrategyTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down

0 comments on commit 18ce801

Please sign in to comment.