-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #60 from outfoxx/feature/transformable
Add `ValueCodingTransformerProviding` that allows encoding/decoding without implmenting `Codable`
- Loading branch information
Showing
2 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,116 @@ | ||
// | ||
// ValueTransformerProviding.swift | ||
// PotentCodables | ||
// | ||
// Copyright © 2021 Outfox, inc. | ||
// | ||
// | ||
// Distributed under the MIT License, See LICENSE for details. | ||
// | ||
|
||
import Foundation | ||
|
||
|
||
public protocol InitializableValueEncodingTransformer: ValueEncodingTransformer { | ||
init() throws | ||
} | ||
|
||
public protocol ValueEncodingTransformerProviding { | ||
|
||
associatedtype EncodingTransformer: InitializableValueEncodingTransformer where EncodingTransformer.Target == Self | ||
|
||
} | ||
|
||
|
||
public protocol InitializableValueDecodingTransformer: ValueDecodingTransformer { | ||
init() throws | ||
} | ||
|
||
public protocol ValueDecodingTransformerProviding { | ||
|
||
associatedtype DecodingTransformer: InitializableValueDecodingTransformer where DecodingTransformer.Target == Self | ||
|
||
} | ||
|
||
|
||
public typealias InitializableValueCodingTransformer = InitializableValueEncodingTransformer & InitializableValueDecodingTransformer | ||
|
||
public protocol ValueCodingTransformerProviding: ValueEncodingTransformerProviding, ValueDecodingTransformerProviding | ||
where EncodingTransformer == CodingTransformer, DecodingTransformer == CodingTransformer { | ||
|
||
associatedtype CodingTransformer: InitializableValueCodingTransformer where CodingTransformer.Target == Self | ||
|
||
} | ||
|
||
|
||
public extension KeyedDecodingContainer { | ||
|
||
func decode<Value: ValueDecodingTransformerProviding>(_ type: Value.Type, forKey key: Key) throws -> Value { | ||
return try decode(forKey: key, using: Value.DecodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension UnkeyedDecodingContainer { | ||
|
||
mutating func decode<Value: ValueDecodingTransformerProviding>(_ type: Value.Type) throws -> Value { | ||
return try decode(using: Value.DecodingTransformer()) | ||
} | ||
|
||
mutating func decodeContents<Value: ValueDecodingTransformerProviding>(_ type: Value.Type) throws -> [Value] { | ||
return try decodeContents(using: Value.DecodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension SingleValueDecodingContainer { | ||
|
||
mutating func decode<Value: ValueDecodingTransformerProviding>(_ type: Value.Type) throws -> Value { | ||
return try decode(using: Value.DecodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension KeyedEncodingContainer { | ||
|
||
mutating func encode<Value: ValueEncodingTransformerProviding>(_ value: Value, forKey key: Key) throws { | ||
return try encode(value, forKey: key, using: Value.EncodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension UnkeyedEncodingContainer { | ||
|
||
mutating func encode<Value: ValueEncodingTransformerProviding>(_ value: Value) throws { | ||
return try encode(value, using: Value.EncodingTransformer()) | ||
} | ||
|
||
mutating func encode<S: Sequence>(contentsOf values: S) throws where S.Element: ValueEncodingTransformerProviding { | ||
return try encode(contentsOf: values, using: S.Element.EncodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension SingleValueEncodingContainer { | ||
|
||
mutating func encode<Value: ValueEncodingTransformerProviding>(_ value: Value) throws { | ||
return try encode(value, using: Value.EncodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension TopLevelDecoder { | ||
|
||
func decode<Value: ValueDecodingTransformerProviding>(_ type: Value.Type, from input: Input) throws -> Value { | ||
return try decode(from: input, using: Value.DecodingTransformer()) | ||
} | ||
|
||
} | ||
|
||
public extension TopLevelEncoder { | ||
|
||
func encode<Value: ValueEncodingTransformerProviding>(_ value: Value) throws -> Output { | ||
return try encode(value, using: Value.EncodingTransformer()) | ||
} | ||
|
||
} |
This file contains 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,156 @@ | ||
// | ||
// ValueTransformerProvidingTests.swift | ||
// PotentCodables | ||
// | ||
// Copyright © 2021 Outfox, inc. | ||
// | ||
// | ||
// Distributed under the MIT License, See LICENSE for details. | ||
// | ||
|
||
@testable import PotentCodables | ||
@testable import PotentJSON | ||
import XCTest | ||
|
||
|
||
class ValueTransformableTests: XCTestCase { | ||
|
||
func testTopLevel() throws { | ||
let encoded = Uncodable(data: Data([1, 2, 3, 4, 5])) | ||
let data = try JSONEncoder.default.encode(encoded) | ||
let decoded = try JSONDecoder.default.decode(Uncodable.self, from: data) | ||
XCTAssertEqual(encoded, decoded) | ||
} | ||
|
||
func testKeyContainer() throws { | ||
|
||
struct Test: Codable, Equatable { | ||
var value: Uncodable | ||
|
||
init(value: Uncodable) { | ||
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(Uncodable.self, forKey: .value) | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(value, forKey: .value) | ||
} | ||
} | ||
|
||
let encoded = Test(value: Uncodable(data: Data([1, 2, 3, 4, 5]))) | ||
let data = try JSONEncoder.default.encode(encoded) | ||
let decoded = try JSONDecoder.default.decode(Test.self, from: data) | ||
XCTAssertEqual(encoded, decoded) | ||
} | ||
|
||
func testUnkeyedContainerContents() throws { | ||
|
||
struct Test: Codable, Equatable { | ||
var value: [Uncodable] | ||
|
||
init(value: [Uncodable]) { | ||
self.value = value | ||
} | ||
|
||
init(from decoder: Decoder) throws { | ||
var container = try decoder.unkeyedContainer() | ||
self.value = try container.decodeContents(Uncodable.self) | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.unkeyedContainer() | ||
try container.encode(contentsOf: value) | ||
} | ||
} | ||
|
||
let encoded = Test(value: [Uncodable(data: Data([1, 2, 3, 4, 5])), Uncodable(data: Data([6, 7, 8, 9, 0]))]) | ||
let data = try JSONEncoder.default.encode(encoded) | ||
let decoded = try JSONDecoder.default.decode(Test.self, from: data) | ||
XCTAssertEqual(encoded, decoded) | ||
} | ||
|
||
func testUnkeyedContainer() throws { | ||
|
||
struct Test: Codable, Equatable { | ||
var value: [Uncodable] | ||
|
||
init(value: [Uncodable]) { | ||
self.value = value | ||
} | ||
|
||
init(from decoder: Decoder) throws { | ||
var container = try decoder.unkeyedContainer() | ||
var value: [Uncodable] = [] | ||
while !container.isAtEnd { | ||
value.append(try container.decode(Uncodable.self)) | ||
} | ||
self.value = value | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.unkeyedContainer() | ||
for element in value { | ||
try container.encode(element) | ||
} | ||
} | ||
} | ||
|
||
let encoded = Test(value: [Uncodable(data: Data([1, 2, 3, 4, 5])), Uncodable(data: Data([6, 7, 8, 9, 0]))]) | ||
let data = try JSONEncoder.default.encode(encoded) | ||
let decoded = try JSONDecoder.default.decode(Test.self, from: data) | ||
XCTAssertEqual(encoded, decoded) | ||
} | ||
|
||
func testSingleValueContainer() throws { | ||
|
||
struct Test: Codable, Equatable { | ||
var value: Uncodable | ||
|
||
init(value: Uncodable) { | ||
self.value = value | ||
} | ||
|
||
init(from decoder: Decoder) throws { | ||
var container = try decoder.singleValueContainer() | ||
self.value = try container.decode(Uncodable.self) | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.singleValueContainer() | ||
try container.encode(value) | ||
} | ||
} | ||
|
||
let encoded = Test(value: Uncodable(data: Data([1, 2, 3, 4, 5]))) | ||
let data = try JSONEncoder.default.encode(encoded) | ||
let decoded = try JSONDecoder.default.decode(Test.self, from: data) | ||
XCTAssertEqual(encoded, decoded) | ||
} | ||
|
||
} | ||
|
||
|
||
struct Uncodable: Equatable { | ||
var data: Data | ||
} | ||
|
||
extension Uncodable: ValueCodingTransformerProviding { | ||
|
||
struct CodingTransformer: InitializableValueCodingTransformer { | ||
func decode(_ value: Data) throws -> Uncodable { | ||
return Uncodable(data: try JSONDecoder.default.decode(Data.self, from: value)) | ||
} | ||
func encode(_ value: Uncodable) throws -> Data { | ||
return try JSONEncoder.default.encode(value.data) | ||
} | ||
} | ||
} |