mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8545128cf |
@@ -0,0 +1,141 @@
|
|||||||
|
//
|
||||||
|
// TransformedAttribute.swift
|
||||||
|
//
|
||||||
|
// Created by Mathew Polzin on 9/30/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
public protocol AttrType {
|
||||||
|
associatedtype SerializedType
|
||||||
|
}
|
||||||
|
|
||||||
|
@propertyWrapper
|
||||||
|
public struct Attr<Serializer, Deserializer, Value>: AttrType where Serializer: Transformer, Deserializer: Transformer, Serializer.From == Value, Deserializer.To == Value {
|
||||||
|
|
||||||
|
public typealias SerializedType = Value
|
||||||
|
|
||||||
|
private var _wrappedValue: Value?
|
||||||
|
public var wrappedValue: Value {
|
||||||
|
guard let ret = _wrappedValue else {
|
||||||
|
fatalError("Accessed Transformed Value prior to initializing or decoding it.")
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(wrappedValue: Value) {
|
||||||
|
_wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(wrappedValue: Value, serializer: Serializer.Type, deserializer: Deserializer.Type) {
|
||||||
|
_wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(serializer: Serializer.Type, deserializer: Deserializer.Type) {
|
||||||
|
_wrappedValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attr where
|
||||||
|
Serializer == IdentityTransformer<Value>,
|
||||||
|
Deserializer == IdentityTransformer<Value> {
|
||||||
|
public init(wrappedValue: Value) {
|
||||||
|
_wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
_wrappedValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attr where
|
||||||
|
Deserializer == IdentityTransformer<Value>,
|
||||||
|
Serializer: Transformer, Serializer.From == Value {
|
||||||
|
|
||||||
|
public init(wrappedValue: Value, serializer: Serializer.Type) {
|
||||||
|
_wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(serializer: Serializer.Type) {
|
||||||
|
_wrappedValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attr where
|
||||||
|
Serializer == IdentityTransformer<Value>,
|
||||||
|
Deserializer: Transformer, Deserializer.To == Value {
|
||||||
|
|
||||||
|
public init(wrappedValue: Value, deserializer: Deserializer.Type) {
|
||||||
|
_wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(deserializer: Deserializer.Type) {
|
||||||
|
_wrappedValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attr: Encodable where
|
||||||
|
Serializer: Transformer, Serializer.To: Encodable {
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
try container.encode(Serializer.transform(wrappedValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attr: Decodable where
|
||||||
|
Deserializer: Transformer, Deserializer.From: Decodable {
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
let anyNil: Any? = nil
|
||||||
|
if container.decodeNil(),
|
||||||
|
let val = anyNil as? Value {
|
||||||
|
_wrappedValue = val
|
||||||
|
} else {
|
||||||
|
_wrappedValue = try Deserializer.transform(container.decode(Deserializer.From.self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol _Omittable {
|
||||||
|
static var nilValue: Self { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
@propertyWrapper
|
||||||
|
public struct Omittable<T>: AttrType, _Omittable {
|
||||||
|
|
||||||
|
public typealias SerializedType = T?
|
||||||
|
|
||||||
|
public let wrappedValue: T?
|
||||||
|
|
||||||
|
public init(wrappedValue: T?) {
|
||||||
|
self.wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var nilValue: Omittable<T> { return .init(wrappedValue: nil) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Omittable: Encodable where T: Encodable {
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
if Optional(wrappedValue) != nil {
|
||||||
|
try container.encode(wrappedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Omittable: Decodable where T: Decodable {
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
self = .init(wrappedValue: try container.decode(T.self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension KeyedDecodingContainer {
|
||||||
|
public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T where T : Decodable, T: _Omittable {
|
||||||
|
return try decodeIfPresent(T.self, forKey: key) ?? T.nilValue
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -29,6 +29,12 @@ public enum IdentityTransformer<T>: ReversibleTransformer {
|
|||||||
public static func reverse(_ value: T) throws -> T { return value }
|
public static func reverse(_ value: T) throws -> T { return value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Optional: Transformer where Wrapped: Transformer {
|
||||||
|
public static func transform(_ value: Wrapped.From?) throws -> Wrapped.To? {
|
||||||
|
return try value.map { try Wrapped.transform($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Validator
|
// MARK: - Validator
|
||||||
|
|
||||||
/// A Validator is a Transformer that throws an error if an invalid value
|
/// A Validator is a Transformer that throws an error if an invalid value
|
||||||
@@ -9,7 +9,74 @@ import XCTest
|
|||||||
import JSONAPI
|
import JSONAPI
|
||||||
import JSONAPITesting
|
import JSONAPITesting
|
||||||
|
|
||||||
|
struct T1: Encodable {
|
||||||
|
@Attr(serializer: IntToString.self)
|
||||||
|
var x: Int
|
||||||
|
|
||||||
|
init(x: Int) {
|
||||||
|
self._x = .init(wrappedValue: x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct T2: Decodable {
|
||||||
|
@Attr(deserializer: IntToString.self)
|
||||||
|
var y: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Tmp {
|
||||||
|
@Attr(serializer: IntToString.self)
|
||||||
|
var x: Int = 5
|
||||||
|
|
||||||
|
@Attr(deserializer: IntToString.self)
|
||||||
|
var y: String = "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct T3: Codable {
|
||||||
|
@Attr(serializer: StringToInt.self, deserializer: IntToString.self)
|
||||||
|
var y: String = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct T4: Encodable {
|
||||||
|
@Attr(serializer: StringToInt?.self)
|
||||||
|
var w: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct T5: Codable {
|
||||||
|
@Attr(serializer: StringToInt?.self, deserializer: IntToString?.self)
|
||||||
|
var x: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct T6: Decodable {
|
||||||
|
@Omittable
|
||||||
|
@Attr(deserializer: IntToString?.self)
|
||||||
|
var y: String?
|
||||||
|
}
|
||||||
|
|
||||||
class TransformerTests: XCTestCase {
|
class TransformerTests: XCTestCase {
|
||||||
|
func test_tmp() {
|
||||||
|
let t1 = T1(x: 2)
|
||||||
|
print(encodable: t1)
|
||||||
|
|
||||||
|
let t3 = T3(y: "3")
|
||||||
|
print(encodable: t3)
|
||||||
|
|
||||||
|
let t2 = decoded(type: T2.self, data: #"{"y":63}"#.data(using: .utf8)!)
|
||||||
|
print(t2.y)
|
||||||
|
|
||||||
|
let t4 = T4(w: .init(wrappedValue: nil))
|
||||||
|
let t4_2 = T4(w: .init(wrappedValue: "12"))
|
||||||
|
print(encodable: t4)
|
||||||
|
print(encodable: t4_2)
|
||||||
|
|
||||||
|
let t5 = decoded(type: T5.self, data: #"{"x":null}"#.data(using: .utf8)!)
|
||||||
|
let t5_2 = decoded(type: T5.self, data: #"{"x":10}"#.data(using: .utf8)!)
|
||||||
|
XCTAssertThrowsError(try JSONDecoder().decode(T5.self, from: #"{}"#.data(using: .utf8)!))
|
||||||
|
print(t5.x)
|
||||||
|
print(t5_2.x)
|
||||||
|
|
||||||
|
let t6 = decoded(type: T6.self, data: #"{}"#.data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
|
||||||
func testIdentityTransform() {
|
func testIdentityTransform() {
|
||||||
let inString = "hello world"
|
let inString = "hello world"
|
||||||
|
|
||||||
@@ -49,3 +116,23 @@ enum MoreThanFiveCharValidator: Validator {
|
|||||||
case fewerThanFiveChars
|
case fewerThanFiveChars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum StringToInt: Transformer {
|
||||||
|
public static func transform(_ value: String) throws -> Int {
|
||||||
|
let res = Int(value)
|
||||||
|
guard let ret = res else {
|
||||||
|
throw Error.nonIntegerString
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Error: Swift.Error {
|
||||||
|
case nonIntegerString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IntToString: Transformer {
|
||||||
|
public static func transform(_ value: Int) throws -> String {
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// Print the UTF8 encoded String value of data produced by encoding the given object.
|
||||||
func print<T: Encodable>(encodable: T) {
|
func print<T: Encodable>(encodable: T) {
|
||||||
print(String(data: try! JSONEncoder().encode(encodable), encoding: .utf8)!)
|
print(String(data: try! JSONEncoder().encode(encodable), encoding: .utf8)!)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user