Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always encode empty elements as KeyedBox #101

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

struct KeyedBox {
typealias Key = String
typealias Attribute = SimpleBox
typealias Attribute = Box
typealias Element = Box

typealias Attributes = KeyedStorage<Key, Attribute>
Expand Down Expand Up @@ -40,7 +40,7 @@ extension KeyedBox {

extension KeyedBox: Box {
var isNull: Bool {
return false
return elements.isEmpty && attributes.isEmpty
}

func xmlString() -> String? {
Expand Down
32 changes: 0 additions & 32 deletions Sources/XMLCoder/Auxiliaries/Box/NullBox.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/XMLCoder/Auxiliaries/KeyedStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ extension KeyedStorage where Key == String, Value == Box {
} else if let value = element.value {
result.append(StringBox(value), at: element.key)
} else {
result.append(NullBox(), at: element.key)
result.append(element.transformToBoxTree(), at: element.key)
}

return result
Expand Down
4 changes: 2 additions & 2 deletions Sources/XMLCoder/Auxiliaries/Metatypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ extension Dictionary: AnySequence {}

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

extension Optional: AnyOptional {
init() {
public init() {
self = nil
}
}
2 changes: 1 addition & 1 deletion Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct XMLCoderElement: Equatable {

func transformToBoxTree() -> KeyedBox {
let attributes = KeyedStorage(self.attributes.map { key, value in
(key: key, value: StringBox(value) as SimpleBox)
(key: key, value: StringBox(value) as Box)
})
let storage = KeyedStorage<String, Box>()
var elements = self.elements.reduce(storage) { $0.merge(element: $1) }
Expand Down
2 changes: 0 additions & 2 deletions Sources/XMLCoder/Decoder/DecodingErrorExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ extension DecodingError {
/// - precondition: `value` is one of the types below.
static func _typeDescription(of box: Box) -> String {
switch box {
case is NullBox:
return "a null value"
case is BoolBox:
return "a boolean value"
case is DecimalBox:
Expand Down
20 changes: 6 additions & 14 deletions Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,6 @@ class XMLDecoderImplementation: Decoder {
let topContainer = try self.topContainer()

switch topContainer {
case _ where topContainer.isNull:
throw DecodingError.valueNotFound(
KeyedDecodingContainer<Key>.self,
DecodingError.Context(
codingPath: codingPath,
debugDescription:
"""
Cannot get keyed decoding container -- found null box instead.
"""
)
)
case let string as StringBox:
return KeyedDecodingContainer(XMLKeyedDecodingContainer<Key>(
referencing: self,
Expand Down Expand Up @@ -175,10 +164,13 @@ extension XMLDecoderImplementation {
case let keyedBox as SharedBox<KeyedBox>:
guard
let value = keyedBox.withShared({ $0.value as? B })
else { throw error }
else {
throw DecodingError.valueNotFound(valueType, DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected \(valueType) but found empty element instead."
))
}
return value
case is NullBox:
throw error
case let keyedBox as KeyedBox:
guard
let value = keyedBox.value as? B
Expand Down
2 changes: 1 addition & 1 deletion Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ extension XMLKeyedDecodingContainer {
keyedBox.attributes[key.stringValue]
}

let box: Box = elements.first ?? attributes.first ?? NullBox()
let box: Box = elements.first ?? attributes.first ?? KeyedBox()
return XMLDecoderImplementation(
referencing: box,
options: decoder.options,
Expand Down
4 changes: 2 additions & 2 deletions Sources/XMLCoder/Encoder/XMLEncoderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ class XMLEncoderImplementation: Encoder {

extension XMLEncoderImplementation {
/// Returns the given value boxed in a container appropriate for pushing onto the container stack.
func box() -> SimpleBox {
return NullBox()
func box() -> Box {
return KeyedBox()
}

func box(_ value: Bool) -> SimpleBox {
Expand Down
2 changes: 1 addition & 1 deletion Sources/XMLCoder/Encoder/XMLKeyedEncodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct XMLKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol {

public mutating func encodeNil(forKey key: Key) throws {
container.withShared {
$0.elements.append(NullBox(), at: _converted(key).stringValue)
$0.elements.append(KeyedBox(), at: _converted(key).stringValue)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/XMLCoderTests/Auxiliary/XMLElementTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class XMLElementTests: XCTestCase {
func testInitKeyed() {
let keyed = XMLCoderElement(key: "foo", box: KeyedBox(
elements: [] as [(String, Box)],
attributes: [("baz", NullBox()), ("blee", IntBox(42))] as [(String, SimpleBox)]
attributes: [("baz", KeyedBox()), ("blee", IntBox(42))] as [(String, Box)]
))

XCTAssertEqual(keyed.key, "foo")
Expand Down
57 changes: 57 additions & 0 deletions Tests/XMLCoderTests/BorderTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// BorderTest.swift
// XMLCoderTests
//
// Created by Max Desiatov on 12/05/2019.
//

import XCTest
@testable import XMLCoder

private let xml = """
<borders count="1">
<border/>
</borders>
""".data(using: .utf8)!

struct Borders: Codable, Equatable {
let items: [Border]
let count: Int

enum CodingKeys: String, CodingKey {
case items = "border"
case count
}
}

struct Border: Codable, Equatable, AnyOptional {
struct Value: Codable, Equatable {
let style: String?
}

let left: Value?
let right: Value?
let top: Value?
let bottom: Value?
let diagonal: Value?
let horizontal: Value?
let vertical: Value?

init() {
left = nil
right = nil
top = nil
bottom = nil
diagonal = nil
horizontal = nil
vertical = nil
}
}

final class BorderTest: XCTestCase {
func testSingleEmpty() throws {
let result = try XMLDecoder().decode(Borders.self, from: xml)
XCTAssertEqual(result.count, 1)
XCTAssertEqual(result.items[0], Border())
}
}
8 changes: 5 additions & 3 deletions Tests/XMLCoderTests/Box/KeyedBoxTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class KeyedBoxTests: XCTestCase {

func testIsNull() {
let box = Boxed()
XCTAssertEqual(box.isNull, false)
XCTAssertEqual(box.isNull, true)
}

func testUnbox() {
Expand Down Expand Up @@ -57,8 +57,10 @@ class KeyedBoxTests: XCTestCase {
elements: elements,
attributes: [("baz", StringBox("blee"))]
)
box.elements.append(NullBox(), at: "bar")
box.elements.append(KeyedBox(), at: "bar")
XCTAssertEqual(box.elements.count, 3)
XCTAssertEqual(box.elements["bar"].first as? NullBox, NullBox())
let barBox = box.elements["bar"].first as? KeyedBox
XCTAssertEqual(barBox?.elements.isEmpty, true)
XCTAssertEqual(barBox?.attributes.isEmpty, true)
}
}
31 changes: 0 additions & 31 deletions Tests/XMLCoderTests/Box/NullBoxTests.swift

This file was deleted.

8 changes: 2 additions & 6 deletions Tests/XMLCoderTests/Box/SharedBoxTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ class SharedBoxTests: XCTestCase {
}

func testXMLString() {
let nullBox = NullBox()
let sharedNullBox = SharedBox(nullBox)
XCTAssertEqual(sharedNullBox.xmlString(), nullBox.xmlString())

let boolBox = BoolBox(false)
let sharedBoolBox = SharedBox(boolBox)
XCTAssertEqual(sharedBoolBox.xmlString(), boolBox.xmlString())
Expand All @@ -47,14 +43,14 @@ class SharedBoxTests: XCTestCase {
XCTAssertEqual(sharedBoxAlias.withShared { $0.count }, 0)

sharedBox.withShared { unkeyedBox in
unkeyedBox.append(NullBox())
unkeyedBox.append(KeyedBox())
}

XCTAssertEqual(sharedBox.withShared { $0.count }, 1)
XCTAssertEqual(sharedBoxAlias.withShared { $0.count }, 1)

sharedBoxAlias.withShared { unkeyedBox in
unkeyedBox.append(NullBox())
unkeyedBox.append(KeyedBox())
}

XCTAssertEqual(sharedBox.withShared { $0.count }, 2)
Expand Down
12 changes: 8 additions & 4 deletions Tests/XMLCoderTests/Box/UnkeyedBoxTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,23 @@ class UnkeyedBoxTests: XCTestCase {

func testSubscript() {
var box = self.box
box[0] = NullBox()
box[0] = KeyedBox()
XCTAssertEqual(box.count, 2)
XCTAssertEqual(box[0] as? NullBox, NullBox())
let box0 = box[0] as? KeyedBox
XCTAssertEqual(box0?.elements.isEmpty, true)
XCTAssertEqual(box0?.attributes.isEmpty, true)
XCTAssertEqual(box[1] as? IntBox, IntBox(42))
}

func testInsertAt() {
var box = self.box
box.insert(NullBox(), at: 1)
box.insert(KeyedBox(), at: 1)
XCTAssertEqual(box.count, 3)

let box1 = box[1] as? KeyedBox
XCTAssertEqual(box[0] as? StringBox, StringBox("foo"))
XCTAssertEqual(box[1] as? NullBox, NullBox())
XCTAssertEqual(box1?.elements.isEmpty, true)
XCTAssertEqual(box1?.attributes.isEmpty, true)
XCTAssertEqual(box[2] as? IntBox, IntBox(42))
}
}
12 changes: 1 addition & 11 deletions Tests/XMLCoderTests/Minimal/BoxTreeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ class BoxTreeTests: XCTestCase {

let boxTree = root.transformToBoxTree()

guard let foo = boxTree.elements["foo"] as? UnkeyedBox else {
XCTAssert(
false,
"""
flattened.elements["foo"] is not an UnkeyedBox
"""
)
return
}

XCTAssertEqual(foo.count, 2)
XCTAssertEqual(boxTree.elements["foo"].count, 2)
}
}
33 changes: 33 additions & 0 deletions Tests/XMLCoderTests/Minimal/KeyedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ class KeyedTests: XCTestCase {
let value: [String: Int]
}

struct OptionalProperties: Codable, Equatable {
struct Item: Codable, Equatable {
let optionalInt: Int?
let optionalString: String?
}

let item: [Item]
}

struct ContainerCamelCase: Codable, Equatable {
let valUe: [String: Int]
let testAttribute: String
Expand All @@ -33,6 +42,30 @@ class KeyedTests: XCTestCase {
}
}

func testEmptyOptionalProperties() throws {
do {
let decoder = XMLDecoder()

let xmlString = "<container><item/><item/></container>"
let xmlData = xmlString.data(using: .utf8)!

let decoded = try decoder.decode(
OptionalProperties.self,
from: xmlData
)

XCTAssertEqual(decoded.item.count, 2)
XCTAssertNotNil(decoded.item.first)
XCTAssertNil(decoded.item.first?.optionalString)
XCTAssertNil(decoded.item.first?.optionalInt)
XCTAssertNotNil(decoded.item.last)
XCTAssertNil(decoded.item.last?.optionalString)
XCTAssertNil(decoded.item.last?.optionalInt)
} catch {
print(error)
}
}

func testEmpty() throws {
let decoder = XMLDecoder()

Expand Down