Add ability to specify an Attribute needs to be transformed once it has been decoded.

This commit is contained in:
Mathew Polzin
2018-11-14 08:38:43 -08:00
parent 805ef4debe
commit fd82d5d7de
8 changed files with 215 additions and 25 deletions
+37 -11
View File
@@ -5,17 +5,28 @@
// Created by Mathew Polzin on 11/13/18.
//
public struct Attribute<Value: Codable>: Codable {
public let value: Value
public struct TransformAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: Codable where Transformer.From == RawValue {
private let rawValue: RawValue
public let value: Transformer.To
public init(rawValue: RawValue) throws {
self.rawValue = rawValue
value = try Transformer.transform(rawValue)
}
}
extension Attribute: Equatable where Value: Equatable {}
public typealias Attribute<T: Codable> = TransformAttribute<T, IdentityTransformer<T>>
extension TransformAttribute: Equatable where Transformer.From: Equatable, Transformer.To: Equatable {}
// MARK: - Codable
extension Attribute {
extension TransformAttribute {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawVal: RawValue
// A little trickery follows. If the value is nil, the
// container.decode(Value.self) will fail even if Value
// is Optional. However, we can check if decoding nil
@@ -23,12 +34,14 @@ extension Attribute {
// type at which point we can store nil in `value`.
let anyNil: Any? = nil
if container.decodeNil(),
let val = anyNil as? Value {
value = val
return
let val = anyNil as? Transformer.From {
rawVal = val
} else {
rawVal = try container.decode(Transformer.From.self)
}
value = try container.decode(Value.self)
rawValue = rawVal
value = try Transformer.transform(rawVal)
}
public func encode(to encoder: Encoder) throws {
@@ -37,11 +50,24 @@ extension Attribute {
// See note in decode above about the weirdness
// going on here.
let anyNil: Any? = nil
if let _ = anyNil as? Value,
(value as Any?) == nil {
if let _ = anyNil as? Transformer.From,
(rawValue as Any?) == nil {
try container.encodeNil()
}
try container.encode(value)
try container.encode(rawValue)
}
}
// MARK: - Transformers
public protocol Transformer {
associatedtype From
associatedtype To
static func transform(_ from: From) throws -> To
}
public enum IdentityTransformer<T>: Transformer {
public static func transform(_ from: T) throws -> T { return from }
}
+9 -2
View File
@@ -139,16 +139,23 @@ public extension Entity {
/// Access the attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
subscript<T>(_ path: KeyPath<EntityType.Attributes, Attribute<T>>) -> T {
subscript<T, TFRM: Transformer>(_ path: KeyPath<EntityType.Attributes, TransformAttribute<T, TFRM>>) -> TFRM.To {
return attributes[keyPath: path].value
}
/// Access the attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
subscript<T>(_ path: KeyPath<EntityType.Attributes, Attribute<T>?>) -> T? {
subscript<T, TFRM: Transformer>(_ path: KeyPath<EntityType.Attributes, TransformAttribute<T, TFRM>?>) -> TFRM.To? {
return attributes[keyPath: path]?.value
}
/// Access the attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
subscript<T, TFRM: Transformer, U>(_ path: KeyPath<EntityType.Attributes, TransformAttribute<T, TFRM>?>) -> U? where TFRM.To == U? {
return attributes[keyPath: path].flatMap { $0.value }
}
}
// MARK: Relationship Access