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

[WIP / Proof of concept] Add Decoding support for enums with associated values #113

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5c7d809
Add Decoding support for enums with associated values
jsbean Jul 12, 2019
a9e9796
There should be no space before and one after any comma
jsbean Jul 12, 2019
870f9e8
Lines should not have trailing whitespace
jsbean Jul 12, 2019
36f0507
Lines should not have trailing whitespace
jsbean Jul 12, 2019
280de28
Limit vertical whitespace to a single empty line
jsbean Jul 12, 2019
9ee2b90
Lines should not have trailing whitespace
jsbean Jul 12, 2019
6d6353d
Relax elements.count constraint, fix test (kind of)
jsbean Jul 13, 2019
09120a3
Destructure closure to help compiler
jsbean Jul 13, 2019
ba4c18e
Initialize KeyedStorage explicitly with empty attributes
jsbean Jul 13, 2019
b70ef0a
Run swiftformat
bwetherfield Jul 13, 2019
d19fd94
Remove prints
bwetherfield Jul 13, 2019
cc4b7cc
Remove xcscheme
bwetherfield Jul 13, 2019
999397c
Run swiftformat
bwetherfield Jul 13, 2019
fe7893c
Merge remote-tracking branch 'jsbean/bean/decoding-enums' into wether…
bwetherfield Jul 13, 2019
717f56c
Add concrete decoding tests
bwetherfield Jul 13, 2019
315583b
Fix formatting
bwetherfield Jul 13, 2019
0e459b1
Remove type(of:) definitions
bwetherfield Jul 13, 2019
f243699
Add concrete decoding nested keyed/unkeyed tests (1 of 4 failing) (#6)
bwetherfield Jul 13, 2019
b3648dc
Build out pathways to sniff out enum amongst nested arrays
jsbean Jul 13, 2019
a6b67b5
Fix nested arrays test
jsbean Jul 13, 2019
ce1fd9a
Whitespace
jsbean Jul 13, 2019
8f7f330
Remove comment about failure
jsbean Jul 13, 2019
f1a432a
Add perf baselines
jsbean Jul 13, 2019
03439ff
Add basic enum round trip test
jsbean Jul 13, 2019
53b2256
Add failing Array of enums round trip test
jsbean Jul 13, 2019
d26a698
Merge remote-tracking branch 'jsbean/bean/decoding-enums' into wether…
bwetherfield Jul 13, 2019
f0e1d04
Fix roundtrip test
bwetherfield Jul 14, 2019
70d82fe
Fix testIntOrStringArrayRoundTrip()
bwetherfield Jul 14, 2019
e57b271
Add pattern matching to encoding step
bwetherfield Jul 14, 2019
85dcf97
Improve patternmatching syntax
bwetherfield Jul 14, 2019
a9e4a27
Fix formatting
bwetherfield Jul 14, 2019
d81bce7
Merge branch 'bean/decoding-enums' into fix-just-encoding
bwetherfield Jul 14, 2019
a1f3af0
Merge pull request #9 from bwetherfield/fix-just-encoding
jsbean Jul 14, 2019
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
5 changes: 5 additions & 0 deletions Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ struct XMLCoderElement: Equatable {
let storage = KeyedStorage<String, Box>()
var elements = self.elements.reduce(storage) { $0.merge(element: $1) }

// Handle enum with associated value case, in which there are no attributes _or_ elements.
if let value = value, elements.isEmpty, attributes.isEmpty {
elements.append(StringBox(value), at: key)
}

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
// Handle attributed unkeyed value <foo attr="bar">zap</foo>
// Value should be zap. Detect only when no other elements exist
if elements.isEmpty, let value = value {
Expand Down
14 changes: 11 additions & 3 deletions Sources/XMLCoder/Decoder/XMLDecoderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,17 @@ class XMLDecoderImplementation: Decoder {
case let unkeyed as SharedBox<UnkeyedBox>:
return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed)
case let keyed as SharedBox<KeyedBox>:
guard let firstKey = keyed.withShared({ $0.elements.keys.first }) else { fallthrough }

return XMLUnkeyedDecodingContainer(referencing: self, wrapping: SharedBox(keyed.withShared { $0.elements[firstKey] }))
// In order to support decoding enums with associated values, transform the `keyed` box
// into an unkeyed box composed of a single key-valued `KeyedBox` element for each
// key-value pair found in the original.
//
// NB: This currently breaks `testDecodeUnkeyedWithinUnkeyed()`.
return XMLUnkeyedDecodingContainer(
referencing: self,
wrapping: keyed.withShared {
SharedBox($0.elements.map { KeyedBox(elements: KeyedStorage([$0])) })
}
)
default:
throw DecodingError.typeMismatch(
at: codingPath,
Expand Down
15 changes: 12 additions & 3 deletions Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,18 @@ struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer {
let box = container.withShared { unkeyedBox in
unkeyedBox[self.currentIndex]
}

let value = try decode(decoder, box)

var value = try decode(decoder,box)
MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved

// In order to support decoding enums with associated values, check to see if we have
// performed an injection of single key-valued `KeyedBox` elements in
// XMLDecoderImplementation.unkeyedContainer(), and attempt to decode the single element
// contained therein.
if value == nil {
if let keyed = box as? KeyedBox, keyed.elements.count == 1 {
value = try decode(decoder, keyed.elements[keyed.elements.keys[0]])
}
}

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
defer { currentIndex += 1 }

if value == nil, let type = type as? AnyOptional.Type,
Expand Down
79 changes: 79 additions & 0 deletions Tests/XMLCoderTests/EnumAssociatedValueTestComposite.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// EnumAssociatedValueTestComposite.swift
// XMLCoderTests
//
// Created by James Bean on 7/12/19.
//

import XCTest
import XMLCoder

private struct IntWrapper: Decodable, Equatable {
let wrapped: Int
}

private struct StringWrapper: Decodable, Equatable {
let wrapped: String
}

private enum IntOrStringWrapper: Equatable {
case int(IntWrapper)
case string(StringWrapper)
}

extension IntOrStringWrapper: Decodable {

enum CodingKeys: String, CodingKey {
case int
case string
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self = .int(try container.decode(IntWrapper.self, forKey: .int))
} catch {
self = .string(try container.decode(StringWrapper.self, forKey: .string))
}
}
}


MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
class EnumAssociatedValueTestComposite: XCTestCase {

func testIntOrStringWrapper() throws {
let xml = """
<container>
<string>
<wrapped>A Word About Woke Times</wrapped>
</string>
</container>
"""
let result = try XMLDecoder().decode(IntOrStringWrapper.self, from: xml.data(using: .utf8)!)
let expected = IntOrStringWrapper.string(StringWrapper(wrapped: "A Word About Woke Times"))
XCTAssertEqual(result, expected)
}

func testArrayOfIntOrStringWrappers() throws {
let xml = """
<container>
<string>
<wrapped>A Word About Woke Times</wrapped>
</string>
<int>
<wrapped>9000</wrapped>
</int>
<string>
<wrapped>A Word About Woke Tomes</wrapped>
</string>
</container>
"""
let result = try XMLDecoder().decode([IntOrStringWrapper].self, from: xml.data(using: .utf8)!)
let expected: [IntOrStringWrapper] = [
.string(StringWrapper(wrapped: "A Word About Woke Times")),
.int(IntWrapper(wrapped: 9000)),
.string(StringWrapper(wrapped: "A Word About Woke Tomes")),
]
XCTAssertEqual(result, expected)
}
}
71 changes: 71 additions & 0 deletions Tests/XMLCoderTests/EnumAssociatedValueTestSimple.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// EnumAssociatedValueTestSimple.swift
// XMLCoderTests
//
// Created by James Bean on 7/9/19.
//

import XCTest
import XMLCoder

private enum IntOrString {
case int(Int)
case string(String)
}

extension IntOrString: Decodable {

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
enum CodingKeys: String, CodingKey {
case int
case string
}

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self = .int(try container.decode(Int.self, forKey: .int))
} catch {
self = .string(try container.decode(String.self, forKey: .string))
}
}
}

extension IntOrString: Equatable { }

class EnumAssociatedValuesTest: XCTestCase {

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
func testIntOrStringIntDecoding() throws {
let xml = "<int>42</int>"
let result = try XMLDecoder().decode(IntOrString.self, from: xml.data(using: .utf8)!)
let expected = IntOrString.int(42)
XCTAssertEqual(result, expected)
}

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
func testIntOrStringStringDecoding() throws {
let xml = "<string>forty-two</string>"
let result = try XMLDecoder().decode(IntOrString.self, from: xml.data(using: .utf8)!)
let expected = IntOrString.string("forty-two")
XCTAssertEqual(result, expected)
}

MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
func testIntOrStringArrayDecoding() throws {
let xml = """
<container>
<int>1</int>
<string>two</string>
<string>three</string>
<int>4</int>
<int>5</int>
</container>
"""
let result = try XMLDecoder().decode([IntOrString].self, from: xml.data(using: .utf8)!)
let expected: [IntOrString] = [
.int(1),
.string("two"),
.string("three"),
.int(4),
.int(5),
]
XCTAssertEqual(result, expected)
}
}
Loading