-
Notifications
You must be signed in to change notification settings - Fork 116
Encodable and Decodable support for choice elements #119
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
Merged
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
749e0fc
Add testing preliminaries for choice elements
bwetherfield ffbb395
Add ChoiceKey protocol conforming to CodingKey
jsbean 77f9699
Implement choice element encoding
bwetherfield c128f28
Implement choice element decoding
jsbean 312c4f5
Refactor clean up choice coding implementation
jsbean 91693e9
Rename XMLChoiceKey -> XMLChoiceCodingKey
jsbean b5684f3
Rename SingleElementBox to SingleKeyedBox
jsbean 5fd8d8f
Rename nestedSingleElementContainer -> nestedChoiceContainer
jsbean 5bda791
Cull redundancies
jsbean 045e07c
Add enum with associated value encoding tests
bwetherfield a7fb985
Fix usage to one key in the XMLChoiceDecodingContainer
jsbean b1b6c27
Factor out mapKeys to XMLDecoderImplementation.transformKeyedContainer
jsbean 048d0c3
Be more assertive in NestingTests (#44)
jsbean 5748eee
Merge branch 'master' into choice-implementation
jsbean c6ee065
Use KeyedBox like we used to (#46)
jsbean e6467d5
Rename scheme XMLCoder-Package -> XMLCoder
jsbean ce09102
Share scheme
jsbean f09c79d
Use Swift 4.2
jsbean 0414fd8
Use Swift 4.2 everywhere
jsbean e1f0c45
Bring back old performance testing baseline
jsbean 0b9c5cc
Whitespace
jsbean a8125e2
Bring back scheme management plist
jsbean bf52ca8
Bring back in empty AdditionalOptions
jsbean fd594fd
Whitespace
jsbean a930d00
Remove print statement
jsbean 5a7a64a
Merge early exits in ChoiceBox.init?(_: KeyedBox)
jsbean c000573
Tighten up SharedBox init callsite
jsbean d4bd9f4
Rename _converted -> converted
jsbean 4a99e95
Beef up XMLChoiceCodingKey doc comment
jsbean 7920b72
Rename local variable mySelf -> oldSelf
jsbean 683cb34
Wrangle long preconditionFailure messages
jsbean 7db9627
Reword Implement -> Implementing in doc comment
jsbean c213808
Throw errors instead of fatallyErroring
jsbean 32195c5
Add brief description to README
jsbean 8149ead
Keep README in tag-ological order
jsbean File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// ChoiceBox.swift | ||
// XMLCoder | ||
// | ||
// Created by James Bean on 7/18/19. | ||
// | ||
|
||
/// A `Box` which represents an element which is known to contain an XML choice element. | ||
struct ChoiceBox { | ||
var key: String = "" | ||
var element: Box = NullBox() | ||
} | ||
|
||
extension ChoiceBox: Box { | ||
var isNull: Bool { | ||
return false | ||
} | ||
|
||
func xmlString() -> String? { | ||
return nil | ||
} | ||
} | ||
|
||
extension ChoiceBox: SimpleBox {} | ||
|
||
extension ChoiceBox { | ||
init?(_ keyedBox: KeyedBox) { | ||
guard | ||
let firstKey = keyedBox.elements.keys.first, | ||
let firstElement = keyedBox.elements[firstKey].first | ||
else { | ||
return nil | ||
} | ||
self.init(key: firstKey, element: firstElement) | ||
} | ||
|
||
init(_ singleKeyedBox: SingleKeyedBox) { | ||
self.init(key: singleKeyedBox.key, element: singleKeyedBox.element) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// SingleKeyedBox.swift | ||
// XMLCoder | ||
// | ||
// Created by James Bean on 7/15/19. | ||
// | ||
|
||
/// A `Box` which contains a single `key` and `element` pair. This is useful for disambiguating elements which could either represent | ||
/// an element nested in a keyed or unkeyed container, or an choice between multiple known-typed values (implemented in Swift using | ||
/// enums with associated values). | ||
struct SingleKeyedBox: SimpleBox { | ||
var key: String | ||
var element: Box | ||
} | ||
|
||
extension SingleKeyedBox: Box { | ||
var isNull: Bool { | ||
return false | ||
} | ||
|
||
func xmlString() -> String? { | ||
return nil | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// | ||
// XMLChoiceCodingKey.swift | ||
// XMLCoder | ||
// | ||
// Created by Benjamin Wetherfield on 7/17/19. | ||
// | ||
|
||
/// An empty marker protocol that can be used in place of `CodingKey`. It must be used when | ||
/// attempting to encode and decode union-type–like enums with associated values to and from `XML` | ||
/// choice elements. | ||
/// | ||
/// - Important: In order for your `XML`-destined `Codable` type to be encoded and/or decoded | ||
/// properly, you must conform your custom `CodingKey` type additionally to `XMLChoiceCodingKey`. | ||
/// | ||
/// For example, say you have defined a type which can hold _either_ an `Int` _or_ a `String`: | ||
/// | ||
/// enum IntOrString { | ||
/// case int(Int) | ||
/// case string(String) | ||
/// } | ||
/// | ||
/// Implementing the requirements for the `Codable` protocol like this: | ||
/// | ||
/// extension IntOrString: Codable { | ||
/// enum CodingKeys: String, XMLChoiceCodingKey { | ||
/// case int | ||
/// case string | ||
/// } | ||
/// | ||
/// func encode(to encoder: Encoder) throws { | ||
/// var container = encoder.container(keyedBy: CodingKeys.self) | ||
/// switch self { | ||
/// case let .int(value): | ||
/// try container.encode(value, forKey: .int) | ||
/// case let .string(value): | ||
/// try container.encode(value, forKey: .string) | ||
/// } | ||
/// } | ||
/// | ||
/// 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)) | ||
/// } | ||
/// } | ||
/// } | ||
/// | ||
/// Retroactively conform the `CodingKeys` enum to `XMLChoiceCodingKey` when targeting `XML` as your | ||
/// encoded format. | ||
/// | ||
/// extension IntOrString.CodingKeys: XMLChoiceCodingKey {} | ||
/// | ||
/// - Note: The `XMLChoiceCodingKey` marker protocol allows the `XMLEncoder` / `XMLDecoder` to | ||
/// resolve ambiguities particular to the `XML` format between nested unkeyed container elements and | ||
/// choice elements. | ||
public protocol XMLChoiceCodingKey: CodingKey {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// | ||
// XMLChoiceDecodingContainer.swift | ||
// XMLCoder | ||
// | ||
// Created by James Bean on 7/18/19. | ||
// | ||
|
||
/// Container specialized for decoding XML choice elements. | ||
struct XMLChoiceDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol { | ||
typealias Key = K | ||
|
||
// MARK: Properties | ||
|
||
/// A reference to the decoder we're reading from. | ||
private let decoder: XMLDecoderImplementation | ||
|
||
/// A reference to the container we're reading from. | ||
private let container: SharedBox<ChoiceBox> | ||
|
||
/// The path of coding keys taken to get to this point in decoding. | ||
public private(set) var codingPath: [CodingKey] | ||
|
||
// MARK: - Initialization | ||
|
||
/// Initializes `self` by referencing the given decoder and container. | ||
init(referencing decoder: XMLDecoderImplementation, wrapping container: SharedBox<ChoiceBox>) { | ||
self.decoder = decoder | ||
container.withShared { $0.key = decoder.keyTransform($0.key) } | ||
self.container = container | ||
codingPath = decoder.codingPath | ||
} | ||
|
||
// MARK: - KeyedDecodingContainerProtocol Methods | ||
|
||
public var allKeys: [Key] { | ||
return container.withShared { [Key(stringValue: $0.key)!] } | ||
} | ||
|
||
public func contains(_ key: Key) -> Bool { | ||
return container.withShared { $0.key == key.stringValue } | ||
} | ||
|
||
public func decodeNil(forKey key: Key) throws -> Bool { | ||
return container.withShared { $0.element.isNull } | ||
} | ||
|
||
public func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T { | ||
guard container.withShared({ $0.key == key.stringValue }), key is XMLChoiceCodingKey else { | ||
throw DecodingError.typeMismatch( | ||
at: codingPath, | ||
expectation: type, | ||
reality: container | ||
) | ||
} | ||
return try decoder.unbox(container.withShared { $0.element }) | ||
} | ||
|
||
public func nestedContainer<NestedKey>( | ||
keyedBy _: NestedKey.Type, forKey key: Key | ||
) throws -> KeyedDecodingContainer<NestedKey> { | ||
throw DecodingError.typeMismatch( | ||
at: codingPath, | ||
expectation: NestedKey.self, | ||
reality: container | ||
) | ||
} | ||
|
||
public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { | ||
throw DecodingError.typeMismatch( | ||
at: codingPath, | ||
expectation: Key.self, | ||
reality: container | ||
) | ||
} | ||
|
||
public func superDecoder() throws -> Decoder { | ||
throw DecodingError.typeMismatch( | ||
at: codingPath, | ||
expectation: Key.self, | ||
reality: container | ||
) | ||
} | ||
|
||
public func superDecoder(forKey key: Key) throws -> Decoder { | ||
throw DecodingError.typeMismatch( | ||
at: codingPath, | ||
expectation: Key.self, | ||
reality: container | ||
) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.