Skip to content

Commit

Permalink
feat(HelperCoders): added conditional and property wrapper based he…
Browse files Browse the repository at this point in the history
…lpers
  • Loading branch information
soumyamahunt committed Nov 2, 2023
1 parent 6f8241a commit 4542ac2
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 0 deletions.
78 changes: 78 additions & 0 deletions Sources/HelperCoders/ConditionalCoder.swift
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)
}
}
8 changes: 8 additions & 0 deletions Sources/HelperCoders/HelperCoders.docc/HelperCoders.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Level up ``/MetaCodable``'s generated implementations with helpers assisting com
- Custom `Date` decoding/encoding approach, i.e. converting from UNIX timestamp, text formatted date etc.
- Custom `Data` decoding/encoding approach, i.e. converting from base64 text etc.
- Decoding/encoding non-confirming floats with text based infinity and not-a-number representations.
- Conditionally decode/encode with two helpers each handling one.
- Using existing property wrappers for custom decoding/encoding.

## Installation

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

- ``Base64Coder``

### Composition

- ``PropertyWrapperCoder``
- ``PropertyWrappable``
- ``ConditionalCoder``
56 changes: 56 additions & 0 deletions Sources/HelperCoders/PropertyWrapperCoder.swift
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 Tests/MetaCodableTests/HelperCoders/HelperCodersTests.swift
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)
}
}

0 comments on commit 4542ac2

Please sign in to comment.