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

Attributed Intrinsic (value coding key) #73

Merged
merged 28 commits into from Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0a8a07a
Bool: Support init from y,n,yes,no any case, false,true added upperca…
JoeMatt Jan 31, 2019
ae4d50a
KeyedEncoding: Add new string formatters, capitalized, lowercased, up…
JoeMatt Jan 31, 2019
e65d30b
SharedBoxProtocol: Generalize for any Box inheritance
JoeMatt Jan 31, 2019
99fde81
Remove junk string in BreakfastTest xml
JoeMatt Jan 31, 2019
38b79a2
Element coding, remove empty brackets if element string value is empt…
JoeMatt Jan 31, 2019
0300dd1
Add DynamicNodeEncoding protocol
JoeMatt Jan 31, 2019
14549a6
XMLEncoder: Add both option to value encoding, refactor encoder
JoeMatt Jan 31, 2019
f8c594a
XMLDecoder.XMLDecodingStorage refactor initial value to inline var
JoeMatt Jan 31, 2019
2ee4b03
Clear up most swiftlint warnings
JoeMatt Jan 31, 2019
f5f6f8d
Rename left over values from different branch
JoeMatt Jan 31, 2019
e86be14
test: Add coding / decoding tests to DynamicNodeEncoding
JoeMatt Jan 31, 2019
5e94557
Convrted BooksTest to DynamicNodeEncoding, tests string equality
JoeMatt Jan 31, 2019
39b5999
Swiftfomat corrections
JoeMatt Jan 31, 2019
f5a8578
Add test coverage for String+Extensions
JoeMatt Jan 31, 2019
a110b83
Fix lowercasingFirstLetter was capitalized
JoeMatt Feb 6, 2019
559f35a
closes #12, support attributed intrinsic values
JoeMatt Jan 31, 2019
04290c6
Fix formatting
MaxDesiatov Feb 9, 2019
faa290a
Merge branch 'master' into feature/12-attributedIntrinsic
MaxDesiatov Feb 9, 2019
429913e
Merge `master`
MaxDesiatov Feb 9, 2019
b1b096c
Fix compilation error
MaxDesiatov Feb 9, 2019
c5af41b
#12 - Fix broken unit tests
JoeMatt Feb 21, 2019
539f042
Merge branch 'master' into feature/12-attributedIntrinsic
MaxDesiatov Feb 21, 2019
e89383e
#12 - Add enum associated value intrinsic encoding
JoeMatt Feb 22, 2019
9fc089c
Merge branch 'master' into feature/12-attributedIntrinsic
MaxDesiatov Feb 22, 2019
aaa9e1a
Fix comment typos
MaxDesiatov Feb 22, 2019
a0de61b
Fix line length coding style
MaxDesiatov Feb 22, 2019
ff3f19e
Fix comment typo, rely on more type inference
MaxDesiatov Feb 22, 2019
7a4f94f
Make tests throwing, remove print usage
MaxDesiatov Feb 22, 2019
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
11 changes: 11 additions & 0 deletions Sources/XMLCoder/Auxiliaries/Box/KeyedBox.swift
Expand Up @@ -106,6 +106,17 @@ extension KeyedBox: Box {
}
}

extension KeyedBox {
var value: SimpleBox? {
guard
elements.count == 1,
let value = elements["value"] as? SimpleBox
?? elements[""] as? SimpleBox,
!value.isNull else { return nil }
return value
}
}

extension KeyedBox: CustomStringConvertible {
var description: String {
return "{attributes: \(attributes), elements: \(elements)}"
Expand Down
7 changes: 6 additions & 1 deletion Sources/XMLCoder/Auxiliaries/XMLCoderElement.swift
Expand Up @@ -47,7 +47,7 @@ struct XMLCoderElement: Equatable {
func flatten() -> KeyedBox {
let attributes = self.attributes.mapValues { StringBox($0) }

let keyedElements: [String: Box] = elements.reduce([String: Box]()) { (result, element) -> [String: Box] in
var keyedElements = elements.reduce([String: Box]()) { (result, element) -> [String: Box] in
var result = result
let key = element.key

Expand Down Expand Up @@ -93,6 +93,11 @@ struct XMLCoderElement: Equatable {
return result
}

// Handle attributed unkeyed value <foo attr="bar">zap</foo>
// Value should be zap. Detect only when no other elements exist
if keyedElements.isEmpty, let value = value {
keyedElements["value"] = StringBox(value)
}
let keyedBox = KeyedBox(elements: keyedElements, attributes: attributes)

return keyedBox
Expand Down
23 changes: 15 additions & 8 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Expand Up @@ -126,15 +126,18 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
public func decode<T: Decodable>(
_ type: T.Type, forKey key: Key
) throws -> T {
let attributeNotFound = container.withShared { keyedBox in
keyedBox.attributes[key.stringValue] == nil
let attributeFound = container.withShared { keyedBox in
keyedBox.attributes[key.stringValue] != nil
}
let elementNotFound = container.withShared { keyedBox in
keyedBox.elements[key.stringValue] == nil

let elementFound = container.withShared { keyedBox in
keyedBox.elements[key.stringValue] != nil || keyedBox.value != nil
}

if let type = type as? AnyEmptySequence.Type, attributeNotFound,
elementNotFound, let result = type.init() as? T {
if let type = type as? AnyEmptySequence.Type,
!attributeFound,
!elementFound,
let result = type.init() as? T {
return result
}

Expand Down Expand Up @@ -163,8 +166,12 @@ struct XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
_ type: T.Type,
forKey key: Key
) throws -> T {
let elementOrNil = container.withShared { keyedBox in
keyedBox.elements[key.stringValue]
let elementOrNil = container.withShared { keyedBox -> KeyedBox.Element? in
if ["value", ""].contains(key.stringValue) {
return keyedBox.elements[key.stringValue] ?? keyedBox.value
} else {
return keyedBox.elements[key.stringValue]
}
}

let attributeOrNil = container.withShared { keyedBox in
Expand Down
206 changes: 206 additions & 0 deletions Tests/XMLCoderTests/AttributedIntrinsicTest.swift
@@ -0,0 +1,206 @@
//
// AttributedIntrinsicTest.swift
// XMLCoderTests
//
// Created by Joseph Mattiello on 1/23/19.
//

import Foundation
import XCTest
@testable import XMLCoder

let fooXML = """
<?xml version="1.0" encoding="UTF-8"?>
<foo id="123">456</foo>
""".data(using: .utf8)!

private struct Foo: Codable, DynamicNodeEncoding {
let id: String
MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
let value: String

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

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

private struct FooEmptyKeyed: Codable, DynamicNodeEncoding {
let id: String
MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
let unkeyedValue: Int

enum CodingKeys: String, CodingKey {
case id
MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
case unkeyedValue = ""
}

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

final class AttributedIntrinsicTest: XCTestCase {
func testEncode() throws {
let encoder = XMLEncoder()
encoder.outputFormatting = []

let foo1 = FooEmptyKeyed(id: "123", unkeyedValue: 456)

let header = XMLHeader(version: 1.0, encoding: "UTF-8")
let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header)
let xmlString = String(data: encoded, encoding: .utf8)
XCTAssertNotNil(xmlString)

// Test string equivalency
let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines)
let originalXML = String(data: fooXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
XCTAssertEqual(encodedXML, originalXML)
}

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

let foo1 = try decoder.decode(Foo.self, from: fooXML)
XCTAssertEqual(foo1.id, "123")
XCTAssertEqual(foo1.value, "456")

let foo2 = try decoder.decode(FooEmptyKeyed.self, from: fooXML)
XCTAssertEqual(foo2.id, "123")
XCTAssertEqual(foo2.unkeyedValue, 456)
}

static var allTests = [
("testEncode", testEncode),
("testDecode", testDecode),
]
}

// MARK: - Enums

let attributedEnumXML = """
<?xml version="1.0" encoding="UTF-8"?>
<foo><number type="string">ABC</number><number type="int">123</number></foo>
""".data(using: .utf8)!

private struct Foo2: Codable {
let number: [FooNumber]
}

private struct FooNumber: Codable, DynamicNodeEncoding {
public let type: FooEnum

public init(type: FooEnum) {
self.type = type
}

enum CodingKeys: String, CodingKey {
case type
case typeValue = ""
}

public static func nodeEncoding(forKey key: CodingKey) -> XMLEncoder.NodeEncoding {
switch key {
case FooNumber.CodingKeys.type: return .attribute
default: return .element
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

type = try container.decode(FooEnum.self, forKey: .type)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch type {
case let .string(value):
try container.encode("string", forKey: .type)
try container.encode(value, forKey: .typeValue)
case let .int(value):
try container.encode("int", forKey: .type)
try container.encode(value, forKey: .typeValue)
}
}
}

private enum FooEnum: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case string
case int
}

public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try values.decodeIfPresent(String.self, forKey: .string) {
self = .string(value)
return
} else if let value = try values.decodeIfPresent(Int.self, forKey: .int) {
self = .int(value)
return
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "No coded value for string or int"))
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .string(value):
try container.encode(value, forKey: .string)
case let .int(value):
try container.encode(value, forKey: .int)
}
}

case string(String)
case int(Int)
}

final class AttributedEnumIntrinsicTest: XCTestCase {
func testEncode() throws {
let encoder = XMLEncoder()
encoder.outputFormatting = []

let foo1 = Foo2(number: [FooNumber(type: FooEnum.string("ABC")), FooNumber(type: FooEnum.int(123))])

let header = XMLHeader(version: 1.0, encoding: "UTF-8")
let encoded = try encoder.encode(foo1, withRootKey: "foo", header: header)
let xmlString = String(data: encoded, encoding: .utf8)
XCTAssertNotNil(xmlString)
// Test string equivalency
let encodedXML = xmlString!.trimmingCharacters(in: .whitespacesAndNewlines)
let originalXML = String(data: attributedEnumXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
XCTAssertEqual(encodedXML, originalXML)
}

// TODO: Fix decoding

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo Violation: TODOs should be resolved (Fix decoding). (todo)

// func testDecode() throws {
// let decoder = XMLDecoder()
// decoder.errorContextLength = 10
//
// let foo = try decoder.decode(Foo2.self, from: attributedEnumXML)
// XCTAssertEqual(foo.number[0].type, FooEnum.string("ABC"))
// XCTAssertEqual(foo.number[1].type, FooEnum.int(123))
// }

static var allTests = [
("testEncode", testEncode),
// ("testDecode", testDecode),
]
}
2 changes: 1 addition & 1 deletion Tests/XMLCoderTests/BooksTest.swift
Expand Up @@ -218,7 +218,7 @@ final class BooksTest: XCTestCase {

XCTAssertEqual(book1, book2)

// Test string equivlancy
// Test string equivalency
let encodedXML = String(data: data, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
let originalXML = String(data: bookXML, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines)
XCTAssertEqual(encodedXML, originalXML)
Expand Down
4 changes: 4 additions & 0 deletions XMLCoder.xcodeproj/project.pbxproj
Expand Up @@ -26,6 +26,7 @@
A61FE03C21E4EAB10015D993 /* KeyedIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */; };
B34B3C08220381AC00BCBA30 /* String+ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */; };
B35157CE21F986DD009CA0CC /* DynamicNodeEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */; };
B3B6902E220A71DF0084D407 /* AttributedIntrinsicTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */; };
B3BE1D612202C1F600259831 /* DynamicNodeEncodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */; };
B3BE1D632202CB1400259831 /* XMLEncoderImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */; };
B3BE1D652202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */; };
Expand Down Expand Up @@ -135,6 +136,7 @@
A61FE03A21E4EA8B0015D993 /* KeyedIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedIntTests.swift; sourceTree = "<group>"; };
B34B3C07220381AB00BCBA30 /* String+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ExtensionsTests.swift"; sourceTree = "<group>"; };
B35157CD21F986DD009CA0CC /* DynamicNodeEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncoding.swift; sourceTree = "<group>"; };
B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedIntrinsicTest.swift; sourceTree = "<group>"; };
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicNodeEncodingTest.swift; sourceTree = "<group>"; };
B3BE1D622202CB1400259831 /* XMLEncoderImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLEncoderImplementation.swift; sourceTree = "<group>"; };
B3BE1D642202CB7200259831 /* XMLEncoderImplementation+SingleValueEncodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XMLEncoderImplementation+SingleValueEncodingContainer.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -378,6 +380,7 @@
OBJ_38 /* RelationshipsTest.swift */,
BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */,
D1FC040421C7EF8200065B43 /* RJISample.swift */,
B3B6902D220A71DF0084D407 /* AttributedIntrinsicTest.swift */,
B3BE1D602202C1F600259831 /* DynamicNodeEncodingTest.swift */,
A61DCCD621DF8DB300C0A19D /* ClassTests.swift */,
D14D8A8521F1D6B300B0D31A /* SingleChildTests.swift */,
Expand Down Expand Up @@ -618,6 +621,7 @@
A61FE03921E4D60B0015D993 /* UnkeyedIntTests.swift in Sources */,
BF63EF6B21D10284001D38C5 /* XMLElementTests.swift in Sources */,
BF9457ED21CBB6BC005ACFDE /* BoolTests.swift in Sources */,
B3B6902E220A71DF0084D407 /* AttributedIntrinsicTest.swift in Sources */,
D1FC040521C7EF8200065B43 /* RJISample.swift in Sources */,
BF63EF0A21CD7C1A001D38C5 /* URLTests.swift in Sources */,
BF9457CE21CBB516005ACFDE /* StringBoxTests.swift in Sources */,
Expand Down