From 0532716158bdb8cfa46c6f488ae5fb6139588522 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sat, 11 Feb 2023 15:30:18 -0700 Subject: [PATCH] Cleanup Ref/EmbeddRef API --- Sources/PotentCodables/Refs.swift | 50 ++++++++++++++++++++-------- Tests/RefTests.swift | 55 ++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/Sources/PotentCodables/Refs.swift b/Sources/PotentCodables/Refs.swift index e5cdea418..11e7d9208 100644 --- a/Sources/PotentCodables/Refs.swift +++ b/Sources/PotentCodables/Refs.swift @@ -287,18 +287,30 @@ public struct CustomRef: Encodable { + public struct Value: Encodable { - public let value: EncodedValue + public let value: Any? - public init(_ value: EncodedValue) { + public init(_ value: T?) { + precondition(value == nil || value is Encodable) + self.value = value + } + + public init(_ value: Any) { + precondition(value is Encodable) self.value = value } public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: AnyCodingKey.self) - try container.encode(TI.typeId(of: EncodedValue.self), forKey: TKP.typeKey) - try container.encode(value, forKey: VKP.valueKey) + if let value = value { + var container = encoder.container(keyedBy: AnyCodingKey.self) + try container.encode(TI.typeId(of: type(of: value)), forKey: TKP.typeKey) + try container.encode((value as? Encodable).unsafelyUnwrapped, forKey: VKP.valueKey) + } + else { + var container = encoder.singleValueContainer() + try container.encodeNil() + } } } @@ -397,18 +409,30 @@ public struct CustomEmbeddedRef: Decodable /// {"@type" : "MyApp.VyValue", "name" : "Foo"} /// ``` /// - public struct Value: Encodable { + public struct Value: Encodable { - public let value: EncodedValue + public let value: Any? + + public init(_ value: T?) { + precondition(value == nil || value is Encodable) + self.value = value + } - public init(_ value: EncodedValue) { + public init(_ value: Any) { + precondition(value is Encodable) self.value = value } public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: AnyCodingKey.self) - try container.encode(TI.typeId(of: EncodedValue.self), forKey: TKP.typeKey) - try value.encode(to: encoder) + if let value = value { + var container = encoder.container(keyedBy: AnyCodingKey.self) + try container.encode(TI.typeId(of: type(of: value)), forKey: TKP.typeKey) + try (value as? Encodable).unsafelyUnwrapped.encode(to: encoder) + } + else { + var container = encoder.singleValueContainer() + try container.encodeNil() + } } } @@ -420,7 +444,7 @@ public struct CustomEmbeddedRef: Decodable /// public enum Refs { - enum Error: Swift.Error { + public enum Error: Swift.Error { case typeNotFound(String) case invalidValue(String) } diff --git a/Tests/RefTests.swift b/Tests/RefTests.swift index 9501a4eb0..37f15e972 100644 --- a/Tests/RefTests.swift +++ b/Tests/RefTests.swift @@ -48,6 +48,40 @@ class RefTests: XCTestCase { XCTAssertEqual(src.name, try ref.as(AValue.self).name) } + func testNestedRef() throws { + + struct TestValue: Codable { + var value: RefTestValue + + init(value: RefTestValue) { + self.value = value + } + + enum CodingKeys: CodingKey { + case value + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value = try container.decode(Ref.self, forKey: .value).as(RefTestValue.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(Ref.Value(value), forKey: .value) + } + } + + let src = TestValue(value: AValue(name: "test")) + + let json = try JSON.Encoder.default.encodeTree(src) + XCTAssertNotNil(json["value"]?["@type"], "AValue") + XCTAssertEqual(json.value?.value?.name, "test") + + let testValue = try JSONDecoder.default.decodeTree(TestValue.self, from: json) + XCTAssertEqual((testValue.value as? AValue)?.name, (src.value as? AValue)?.name) + } + func testCustomRef() throws { struct MyTypeKeys: TypeKeyProvider, ValueKeyProvider { @@ -279,13 +313,13 @@ class RefTests: XCTestCase { func testRefSingleNil() throws { - let json = try JSON.Encoder.default.encodeTree(Ref.Value(nil as Bool?)) - XCTAssertNotNil(json["@type"]) - XCTAssertEqual(json["value"], .null) + let nullValue: RefTestValue? = nil + let json = try JSON.Encoder.default.encodeTree(Ref.Value(nullValue)) + XCTAssertEqual(json, .null) - let ref = try JSONDecoder.default.decodeTree(Ref.self, from: json) + let ref = try JSONDecoder.default.decodeTreeIfPresent(Ref.self, from: json) - XCTAssertEqual(try ref.as(Bool?.self), nil) + XCTAssertNil(ref) } func testRefSingleBoolArray() throws { @@ -323,6 +357,17 @@ class RefTests: XCTestCase { XCTAssertThrowsError(try JSONDecoder.default.decodeTree(Ref.self, from: json)) } + func testEmbeddedRefSingleNil() throws { + + let nullValue: RefTestValue? = nil + let json = try JSON.Encoder.default.encodeTree(EmbeddedRef.Value(nullValue)) + XCTAssertEqual(json, .null) + + let ref = try JSONDecoder.default.decodeTreeIfPresent(EmbeddedRef.self, from: json) + + XCTAssertNil(ref) + } + func testEmbeddedRefDecodeFailsWhenTypeNotAuthorized() throws { struct TestValue: Codable {