Skip to content

Commit 4542ac2

Browse files
committed
feat(HelperCoders): added conditional and property wrapper based helpers
1 parent 6f8241a commit 4542ac2

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import MetaCodable
2+
3+
/// An ``/MetaCodable/HelperCoder`` that helps decoding/encoding
4+
/// with two separate ``/MetaCodable/HelperCoder``s.
5+
///
6+
/// This type can be used to use separate ``/MetaCodable/HelperCoder``s
7+
/// for decoding and encoding.
8+
public struct ConditionalCoder<D: HelperCoder, E: HelperCoder>: HelperCoder
9+
where D.Coded == E.Coded {
10+
/// The ``/MetaCodable/HelperCoder`` used for decoding.
11+
@usableFromInline
12+
internal let decoder: D
13+
/// The ``/MetaCodable/HelperCoder`` used for encoding.
14+
@usableFromInline
15+
internal let encoder: E
16+
17+
/// Creates a new instance of ``/MetaCodable/HelperCoder`` that decodes/encodes
18+
/// conditionally with provided decoder/encoder respectively.
19+
///
20+
/// The provided decoder is used only for decoding
21+
/// and encoder only for encoding.
22+
///
23+
/// - Parameters:
24+
/// - decoder: The ``/MetaCodable/HelperCoder`` used for decoding.
25+
/// - encoder: The ``/MetaCodable/HelperCoder`` used for encoding.
26+
public init(decoder: D, encoder: E) {
27+
self.decoder = decoder
28+
self.encoder = encoder
29+
}
30+
31+
/// Decodes using the decode specific ``/MetaCodable/HelperCoder``
32+
/// from the given `decoder`.
33+
///
34+
/// - Parameter decoder: The decoder to read data from.
35+
/// - Returns: The decoded value.
36+
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error.
37+
@inlinable
38+
public func decode(from decoder: Decoder) throws -> D.Coded {
39+
return try self.decoder.decode(from: decoder)
40+
}
41+
42+
/// Decodes optional value using the decode specific
43+
/// ``/MetaCodable/HelperCoder`` from the given `decoder`.
44+
///
45+
/// - Parameter decoder: The decoder to read data from.
46+
/// - Returns: The decoded optional value.
47+
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error.
48+
@inlinable
49+
public func decodeIfPresent(from decoder: Decoder) throws -> D.Coded? {
50+
return try self.decoder.decodeIfPresent(from: decoder)
51+
}
52+
53+
/// Encodes using the encode specific ``/MetaCodable/HelperCoder``
54+
/// from the given `encoder`.
55+
///
56+
/// - Parameters:
57+
/// - value: The value to encode.
58+
/// - encoder: The encoder to write data to.
59+
///
60+
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error.
61+
@inlinable
62+
public func encode(_ value: E.Coded, to encoder: Encoder) throws {
63+
try self.encoder.encode(value, to: encoder)
64+
}
65+
66+
/// Encodes optional value using the encode specific
67+
/// ``/MetaCodable/HelperCoder`` from the given `encoder`.
68+
///
69+
/// - Parameters:
70+
/// - value: The value to encode.
71+
/// - encoder: The encoder to write data to.
72+
///
73+
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error.
74+
@inlinable
75+
public func encodeIfPresent(_ value: E.Coded?, to encoder: Encoder) throws {
76+
try self.encoder.encodeIfPresent(value, to: encoder)
77+
}
78+
}

Sources/HelperCoders/HelperCoders.docc/HelperCoders.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Level up ``/MetaCodable``'s generated implementations with helpers assisting com
1414
- Custom `Date` decoding/encoding approach, i.e. converting from UNIX timestamp, text formatted date etc.
1515
- Custom `Data` decoding/encoding approach, i.e. converting from base64 text etc.
1616
- Decoding/encoding non-confirming floats with text based infinity and not-a-number representations.
17+
- Conditionally decode/encode with two helpers each handling one.
18+
- Using existing property wrappers for custom decoding/encoding.
1719

1820
## Installation
1921

@@ -54,3 +56,9 @@ Level up ``/MetaCodable``'s generated implementations with helpers assisting com
5456
### Data
5557

5658
- ``Base64Coder``
59+
60+
### Composition
61+
62+
- ``PropertyWrapperCoder``
63+
- ``PropertyWrappable``
64+
- ``ConditionalCoder``
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import MetaCodable
2+
3+
/// An ``/MetaCodable/HelperCoder`` that helps decoding/encoding
4+
/// using existing property wrappers.
5+
///
6+
/// This type can be used to reuse existing property
7+
/// wrappers with custom decoding/encoding with
8+
/// ``MetaCodable`` generated implementations.
9+
public struct PropertyWrapperCoder<Wrapper: PropertyWrappable>: HelperCoder {
10+
/// Creates a new instance of ``/MetaCodable/HelperCoder`` that decodes/encodes
11+
/// using existing property wrappers.
12+
///
13+
/// - Returns: A new property wrapper based decoder/encoder.
14+
public init() {}
15+
16+
/// Decodes using `Wrapper` type from the given `decoder`.
17+
///
18+
/// - Parameter decoder: The decoder to read data from.
19+
/// - Returns: The wrapped value decoded.
20+
/// - Throws: If the property wrapper throws error.
21+
@inlinable
22+
public func decode(from decoder: Decoder) throws -> Wrapper.Wrapped {
23+
return try Wrapper(from: decoder).wrappedValue
24+
}
25+
26+
/// Encodes given value using `Wrapper` type to the provided `encoder`.
27+
///
28+
/// - Parameters:
29+
/// - value: The wrapped value to encode.
30+
/// - encoder: The encoder to write data to.
31+
///
32+
/// - Throws: If the property wrapper throws error.
33+
@inlinable
34+
public func encode(_ value: Wrapper.Wrapped, to encoder: Encoder) throws {
35+
try Wrapper(wrappedValue: value).encode(to: encoder)
36+
}
37+
}
38+
39+
/// A type representing a [property wrapper].
40+
///
41+
/// A property wrapper adds a layer of separation
42+
/// between code that manages how a property is stored
43+
/// and the code that defines a property.
44+
///
45+
/// [property wrapper]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers
46+
public protocol PropertyWrappable: Codable {
47+
/// The type of the value wrapped.
48+
associatedtype Wrapped
49+
/// The value wrapped.
50+
var wrappedValue: Wrapped { get }
51+
/// Creates new instance wrapping provided value.
52+
///
53+
/// - Parameters:
54+
/// - wrappedValue: The value to be wrapped.
55+
init(wrappedValue: Wrapped)
56+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import HelperCoders
2+
import MetaCodable
3+
import XCTest
4+
5+
final class HelperCodersTests: XCTestCase {
6+
func testConditionalCoding() throws {
7+
let jsonStr = """
8+
{
9+
"date": "1997-11-04T10:38:21Z",
10+
"optionalDate": "1997-11-04T10:38:21Z"
11+
}
12+
"""
13+
let json = try XCTUnwrap(jsonStr.data(using: .utf8))
14+
let model = try JSONDecoder().decode(Model.self, from: json)
15+
let epoch: Double = 878639901
16+
XCTAssertEqual(model.date.timeIntervalSince1970, epoch)
17+
let encoded = try JSONEncoder().encode(model)
18+
let customDecoder = JSONDecoder()
19+
customDecoder.dateDecodingStrategy = .secondsSince1970
20+
let newModel = try customDecoder.decode(MirrorModel.self, from: encoded)
21+
XCTAssertEqual(newModel.date.timeIntervalSince1970, epoch)
22+
}
23+
24+
func testPropertyWrapperCoding() throws {
25+
let jsonStr = """
26+
{
27+
"int": 100
28+
}
29+
"""
30+
let json = try XCTUnwrap(jsonStr.data(using: .utf8))
31+
let model = try JSONDecoder().decode(PropModel.self, from: json)
32+
XCTAssertEqual(model.int, 5)
33+
let encoded = try JSONEncoder().encode(model)
34+
let obj = try JSONSerialization.jsonObject(with: encoded)
35+
let dict = try XCTUnwrap(obj as? [String: Any])
36+
XCTAssertEqual(dict["int"] as? Int, 5)
37+
}
38+
}
39+
40+
@Codable
41+
fileprivate struct Model {
42+
@CodedBy(
43+
ConditionalCoder(
44+
decoder: ISO8601DateCoder(),
45+
encoder: Since1970DateCoder()
46+
)
47+
)
48+
let date: Date
49+
@CodedBy(
50+
ConditionalCoder(
51+
decoder: ISO8601DateCoder(),
52+
encoder: Since1970DateCoder()
53+
)
54+
)
55+
let optionalDate: Date?
56+
}
57+
58+
fileprivate struct MirrorModel: Codable {
59+
let date: Date
60+
let optionalDate: Date?
61+
}
62+
63+
@Codable
64+
fileprivate struct PropModel {
65+
@CodedBy(PropertyWrapperCoder<ConstIntCoder>())
66+
let int: Int
67+
}
68+
69+
@propertyWrapper
70+
struct ConstIntCoder: PropertyWrappable {
71+
var wrappedValue: Int { 5 }
72+
73+
init(wrappedValue: Int) {}
74+
75+
func encode(to encoder: Encoder) throws {
76+
try wrappedValue.encode(to: encoder)
77+
}
78+
}

0 commit comments

Comments
 (0)