-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(
HelperCoders
): added conditional and property wrapper based he…
…lpers
- Loading branch information
1 parent
6f8241a
commit 4542ac2
Showing
4 changed files
with
220 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,78 @@ | ||
import MetaCodable | ||
|
||
/// An ``/MetaCodable/HelperCoder`` that helps decoding/encoding | ||
/// with two separate ``/MetaCodable/HelperCoder``s. | ||
/// | ||
/// This type can be used to use separate ``/MetaCodable/HelperCoder``s | ||
/// for decoding and encoding. | ||
public struct ConditionalCoder<D: HelperCoder, E: HelperCoder>: HelperCoder | ||
where D.Coded == E.Coded { | ||
/// The ``/MetaCodable/HelperCoder`` used for decoding. | ||
@usableFromInline | ||
internal let decoder: D | ||
/// The ``/MetaCodable/HelperCoder`` used for encoding. | ||
@usableFromInline | ||
internal let encoder: E | ||
|
||
/// Creates a new instance of ``/MetaCodable/HelperCoder`` that decodes/encodes | ||
/// conditionally with provided decoder/encoder respectively. | ||
/// | ||
/// The provided decoder is used only for decoding | ||
/// and encoder only for encoding. | ||
/// | ||
/// - Parameters: | ||
/// - decoder: The ``/MetaCodable/HelperCoder`` used for decoding. | ||
/// - encoder: The ``/MetaCodable/HelperCoder`` used for encoding. | ||
public init(decoder: D, encoder: E) { | ||
self.decoder = decoder | ||
self.encoder = encoder | ||
} | ||
|
||
/// Decodes using the decode specific ``/MetaCodable/HelperCoder`` | ||
/// from the given `decoder`. | ||
/// | ||
/// - Parameter decoder: The decoder to read data from. | ||
/// - Returns: The decoded value. | ||
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error. | ||
@inlinable | ||
public func decode(from decoder: Decoder) throws -> D.Coded { | ||
return try self.decoder.decode(from: decoder) | ||
} | ||
|
||
/// Decodes optional value using the decode specific | ||
/// ``/MetaCodable/HelperCoder`` from the given `decoder`. | ||
/// | ||
/// - Parameter decoder: The decoder to read data from. | ||
/// - Returns: The decoded optional value. | ||
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error. | ||
@inlinable | ||
public func decodeIfPresent(from decoder: Decoder) throws -> D.Coded? { | ||
return try self.decoder.decodeIfPresent(from: decoder) | ||
} | ||
|
||
/// Encodes using the encode specific ``/MetaCodable/HelperCoder`` | ||
/// from the given `encoder`. | ||
/// | ||
/// - Parameters: | ||
/// - value: The value to encode. | ||
/// - encoder: The encoder to write data to. | ||
/// | ||
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error. | ||
@inlinable | ||
public func encode(_ value: E.Coded, to encoder: Encoder) throws { | ||
try self.encoder.encode(value, to: encoder) | ||
} | ||
|
||
/// Encodes optional value using the encode specific | ||
/// ``/MetaCodable/HelperCoder`` from the given `encoder`. | ||
/// | ||
/// - Parameters: | ||
/// - value: The value to encode. | ||
/// - encoder: The encoder to write data to. | ||
/// | ||
/// - Throws: If the underlying ``/MetaCodable/HelperCoder`` throws error. | ||
@inlinable | ||
public func encodeIfPresent(_ value: E.Coded?, to encoder: Encoder) throws { | ||
try self.encoder.encodeIfPresent(value, to: encoder) | ||
} | ||
} |
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
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,56 @@ | ||
import MetaCodable | ||
|
||
/// An ``/MetaCodable/HelperCoder`` that helps decoding/encoding | ||
/// using existing property wrappers. | ||
/// | ||
/// This type can be used to reuse existing property | ||
/// wrappers with custom decoding/encoding with | ||
/// ``MetaCodable`` generated implementations. | ||
public struct PropertyWrapperCoder<Wrapper: PropertyWrappable>: HelperCoder { | ||
/// Creates a new instance of ``/MetaCodable/HelperCoder`` that decodes/encodes | ||
/// using existing property wrappers. | ||
/// | ||
/// - Returns: A new property wrapper based decoder/encoder. | ||
public init() {} | ||
|
||
/// Decodes using `Wrapper` type from the given `decoder`. | ||
/// | ||
/// - Parameter decoder: The decoder to read data from. | ||
/// - Returns: The wrapped value decoded. | ||
/// - Throws: If the property wrapper throws error. | ||
@inlinable | ||
public func decode(from decoder: Decoder) throws -> Wrapper.Wrapped { | ||
return try Wrapper(from: decoder).wrappedValue | ||
} | ||
|
||
/// Encodes given value using `Wrapper` type to the provided `encoder`. | ||
/// | ||
/// - Parameters: | ||
/// - value: The wrapped value to encode. | ||
/// - encoder: The encoder to write data to. | ||
/// | ||
/// - Throws: If the property wrapper throws error. | ||
@inlinable | ||
public func encode(_ value: Wrapper.Wrapped, to encoder: Encoder) throws { | ||
try Wrapper(wrappedValue: value).encode(to: encoder) | ||
} | ||
} | ||
|
||
/// A type representing a [property wrapper]. | ||
/// | ||
/// A property wrapper adds a layer of separation | ||
/// between code that manages how a property is stored | ||
/// and the code that defines a property. | ||
/// | ||
/// [property wrapper]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers | ||
public protocol PropertyWrappable: Codable { | ||
/// The type of the value wrapped. | ||
associatedtype Wrapped | ||
/// The value wrapped. | ||
var wrappedValue: Wrapped { get } | ||
/// Creates new instance wrapping provided value. | ||
/// | ||
/// - Parameters: | ||
/// - wrappedValue: The value to be wrapped. | ||
init(wrappedValue: Wrapped) | ||
} |
78 changes: 78 additions & 0 deletions
78
Tests/MetaCodableTests/HelperCoders/HelperCodersTests.swift
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,78 @@ | ||
import HelperCoders | ||
import MetaCodable | ||
import XCTest | ||
|
||
final class HelperCodersTests: XCTestCase { | ||
func testConditionalCoding() throws { | ||
let jsonStr = """ | ||
{ | ||
"date": "1997-11-04T10:38:21Z", | ||
"optionalDate": "1997-11-04T10:38:21Z" | ||
} | ||
""" | ||
let json = try XCTUnwrap(jsonStr.data(using: .utf8)) | ||
let model = try JSONDecoder().decode(Model.self, from: json) | ||
let epoch: Double = 878639901 | ||
XCTAssertEqual(model.date.timeIntervalSince1970, epoch) | ||
let encoded = try JSONEncoder().encode(model) | ||
let customDecoder = JSONDecoder() | ||
customDecoder.dateDecodingStrategy = .secondsSince1970 | ||
let newModel = try customDecoder.decode(MirrorModel.self, from: encoded) | ||
XCTAssertEqual(newModel.date.timeIntervalSince1970, epoch) | ||
} | ||
|
||
func testPropertyWrapperCoding() throws { | ||
let jsonStr = """ | ||
{ | ||
"int": 100 | ||
} | ||
""" | ||
let json = try XCTUnwrap(jsonStr.data(using: .utf8)) | ||
let model = try JSONDecoder().decode(PropModel.self, from: json) | ||
XCTAssertEqual(model.int, 5) | ||
let encoded = try JSONEncoder().encode(model) | ||
let obj = try JSONSerialization.jsonObject(with: encoded) | ||
let dict = try XCTUnwrap(obj as? [String: Any]) | ||
XCTAssertEqual(dict["int"] as? Int, 5) | ||
} | ||
} | ||
|
||
@Codable | ||
fileprivate struct Model { | ||
@CodedBy( | ||
ConditionalCoder( | ||
decoder: ISO8601DateCoder(), | ||
encoder: Since1970DateCoder() | ||
) | ||
) | ||
let date: Date | ||
@CodedBy( | ||
ConditionalCoder( | ||
decoder: ISO8601DateCoder(), | ||
encoder: Since1970DateCoder() | ||
) | ||
) | ||
let optionalDate: Date? | ||
} | ||
|
||
fileprivate struct MirrorModel: Codable { | ||
let date: Date | ||
let optionalDate: Date? | ||
} | ||
|
||
@Codable | ||
fileprivate struct PropModel { | ||
@CodedBy(PropertyWrapperCoder<ConstIntCoder>()) | ||
let int: Int | ||
} | ||
|
||
@propertyWrapper | ||
struct ConstIntCoder: PropertyWrappable { | ||
var wrappedValue: Int { 5 } | ||
|
||
init(wrappedValue: Int) {} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
try wrappedValue.encode(to: encoder) | ||
} | ||
} |