mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbbacab105 | |||
| 4eb36d4594 | |||
| 43e02351de | |||
| 0b307bd3bc | |||
| 662a84ccf0 | |||
| 44f9bca7dc | |||
| 774b53b9b6 | |||
| 654d3bfd2b |
@@ -0,0 +1,124 @@
|
||||
//: [Previous](@previous)
|
||||
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
|
||||
/*******
|
||||
|
||||
Please enjoy these examples, but allow me the forced casting and the lack of error checking for the sake of brevity.
|
||||
|
||||
This playground focuses on receiving a resource, making some changes, and then creating a request body for a PATCH request.
|
||||
As with all examples in these playround pages, no actual networking code will be provided.
|
||||
|
||||
********/
|
||||
|
||||
// Mock up a server response
|
||||
let mockDogData = """
|
||||
{
|
||||
"data": {
|
||||
"id": "1234",
|
||||
"type": "dogs",
|
||||
"attributes": {
|
||||
"name": "Sparky"
|
||||
},
|
||||
"relationships": {
|
||||
"owner": {
|
||||
"data": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
//
|
||||
// MARK: - EXAMPLE 1 (Mutable Attributes)
|
||||
//
|
||||
|
||||
// pretend to have requested a Dog and received the mock data
|
||||
// now parse it.
|
||||
let parsedResponse = try! JSONDecoder().decode(MutableDogDocument.self, from: mockDogData)
|
||||
|
||||
// extract our Dog (skipping over any robustness to handle errors)
|
||||
var dog = parsedResponse.body.primaryResource!.value
|
||||
print("Received dog named: \(dog.name)")
|
||||
|
||||
// change the dog's name
|
||||
let changedDog = dog.tappingAttributes { $0.name = .init(value: "Julia") }
|
||||
|
||||
// create a document to be used as a request body for a PATCH request
|
||||
let patchRequest = MutableDogDocument(apiDescription: .none,
|
||||
body: .init(resourceObject: changedDog),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
|
||||
// encode and send off to server
|
||||
let encodedPatchRequest = try! JSONEncoder().encode(patchRequest)
|
||||
print("----")
|
||||
print(String(data: encodedPatchRequest, encoding:.utf8)!)
|
||||
|
||||
|
||||
//
|
||||
// MARK: - EXAMPLE 2 (Immutable Attributes)
|
||||
//
|
||||
print()
|
||||
print("####")
|
||||
print()
|
||||
|
||||
// pretend to have requested a Dog and received the mock data
|
||||
// now parse it.
|
||||
let parsedResponse2 = try! JSONDecoder().decode(SingleDogDocument.self, from: mockDogData)
|
||||
|
||||
// extract our Dog (skipping over any robustness to handle errors)
|
||||
var dog2 = parsedResponse2.body.primaryResource!.value
|
||||
print("Received dog named: \(dog2.name)")
|
||||
|
||||
// change the dog's name
|
||||
let changedDog2 = dog2.replacingAttributes { _ in
|
||||
return .init(name: .init(value: "Nigel"))
|
||||
}
|
||||
|
||||
// create a document to be used as a request body for a PATCH request
|
||||
let patchRequest2 = SingleDogDocument(apiDescription: .none,
|
||||
body: .init(resourceObject: changedDog2),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
|
||||
// encode and send off to server
|
||||
let encodedPatchRequest2 = try! JSONEncoder().encode(patchRequest2)
|
||||
print("----")
|
||||
print(String(data: encodedPatchRequest2, encoding:.utf8)!)
|
||||
|
||||
|
||||
//
|
||||
// MARK: - EXAMPLE 3 (Change relationship)
|
||||
//
|
||||
print()
|
||||
print("####")
|
||||
print()
|
||||
|
||||
// pretend to have requested a Dog and received the mock data
|
||||
// now parse it.
|
||||
let parsedResponse3 = try! JSONDecoder().decode(SingleDogDocument.self, from: mockDogData)
|
||||
|
||||
// extract our Dog (skipping over any robustness to handle errors)
|
||||
var dog3 = parsedResponse2.body.primaryResource!.value
|
||||
print("Received dog with owner: \(dog3 ~> \.owner)")
|
||||
|
||||
// give the dog an owner
|
||||
let changedDog3 = dog3.replacingRelationships { _ in
|
||||
return .init(owner: .init(id: Id(rawValue: "1")))
|
||||
}
|
||||
|
||||
// create a document to be used as a request body for a PATCH request
|
||||
let patchRequest3 = SingleDogDocument(apiDescription: .none,
|
||||
body: .init(resourceObject: changedDog3),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
|
||||
// encode and send off to server
|
||||
let encodedPatchRequest3 = try! JSONEncoder().encode(patchRequest3)
|
||||
print("----")
|
||||
print(String(data: encodedPatchRequest3, encoding:.utf8)!)
|
||||
@@ -119,6 +119,29 @@ public enum AlternativeDogDescription: ResourceObjectDescription {
|
||||
|
||||
public typealias AlternativeDog = ExampleEntity<AlternativeDogDescription>
|
||||
|
||||
public enum MutableDogDescription: ResourceObjectDescription {
|
||||
|
||||
public static var jsonType: String { return "dogs" }
|
||||
|
||||
public struct Attributes: JSONAPI.Attributes {
|
||||
public var name: Attribute<String>
|
||||
|
||||
public init(name: Attribute<String>) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
public struct Relationships: JSONAPI.Relationships {
|
||||
public var owner: ToOne<Person?>
|
||||
|
||||
public init(owner: ToOne<Person?>) {
|
||||
self.owner = owner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias MutableDog = ExampleEntity<MutableDogDescription>
|
||||
|
||||
public extension ResourceObject where Description == DogDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
|
||||
init(name: String, owner: Person?) throws {
|
||||
self = Dog(attributes: .init(name: .init(value: name)), relationships: DogDescription.Relationships(owner: .init(resourceObject: owner)), meta: .none, links: .none)
|
||||
@@ -141,4 +164,6 @@ public typealias House = ExampleEntity<HouseDescription>
|
||||
|
||||
public typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
|
||||
public typealias MutableDogDocument = JSONAPI.Document<SingleResourceBody<MutableDog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
|
||||
public typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<page name='Full Client & Server Example'/>
|
||||
<page name='Full Document Verbose Generation'/>
|
||||
<page name='Sparse Fieldsets Example'/>
|
||||
<page name='PATCHing'/>
|
||||
</pages>
|
||||
</playground>
|
||||
+2
-2
@@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
|
||||
#
|
||||
|
||||
spec.name = "MP-JSONAPI"
|
||||
spec.version = "2.2.0"
|
||||
spec.version = "2.4.0"
|
||||
spec.summary = "Swift Codable JSON API framework."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
@@ -136,6 +136,6 @@ See the JSON API Spec here: https://jsonapi.org/format/
|
||||
# spec.requires_arc = true
|
||||
|
||||
# spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
|
||||
spec.dependency "Poly", "~> 2.1"
|
||||
spec.dependency "Poly", "~> 2.2"
|
||||
|
||||
end
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
"repositoryURL": "https://github.com/mattpolzin/Poly.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "4a08517b24f8e9f6dd8c02ec7da316aac5c00e2e",
|
||||
"version": "2.1.0"
|
||||
"revision": "b24fd3b41bf3126d4c6dede3708135182172af60",
|
||||
"version": "2.2.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ let package = Package(
|
||||
targets: ["JSONAPITesting"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.1.0")),
|
||||
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.2.0")),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
||||
@@ -76,6 +76,9 @@ This library works well when used by both the server responsible for serializati
|
||||
- [Sparse Fieldsets](#sparse-fieldsets)
|
||||
- [Supporting Sparse Fieldset Encoding](#supporting-sparse-fieldset-encoding)
|
||||
- [Sparse Fieldset `typealias` comparisons](#sparse-fieldset-typealias-comparisons)
|
||||
- [Replacing and Tapping Attributes/Relationships](#replacing-and-tapping-attributesrelationships)
|
||||
- [Tapping](#tapping)
|
||||
- [Replacing](#replacing)
|
||||
- [Custom Attribute or Relationship Key Mapping](#custom-attribute-or-relationship-key-mapping)
|
||||
- [Custom Attribute Encode/Decode](#custom-attribute-encodedecode)
|
||||
- [Meta-Attributes](#meta-attributes)
|
||||
@@ -569,6 +572,35 @@ In order to support sparse fieldsets (which are encode-only), the following comp
|
||||
typealias SparseDocument<PrimaryResourceBody: JSONAPI.EncodableResourceBody, IncludeType: JSONAPI.Include> = JSONAPI.Document<PrimaryResourceBody, NoMetadata, NoLinks, IncludeType, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
```
|
||||
|
||||
### Replacing and Tapping Attributes/Relationships
|
||||
When you are working with an immutable Resource Object, it can be useful to replace its attributes or relationships. As a client, you might receive a resource from the server, update something, and then send the server a PATCH request.
|
||||
|
||||
`ResourceObject` is immutable, but you can create a new copy of a `ResourceObject` having updated attributes or relationships.
|
||||
|
||||
#### Tapping
|
||||
If your `Attributes` or `Relationships` struct is mutable (i.e. its properties are `var`s) then you may find `ResourceObject`'s `tappingAttributes()` and `tappingRelationships()` functions useful. For both, you pass a function that takes an `inout` copy of the respective object or value that you can mutate. The mutated value is then used to create a new `ResourceObject`.
|
||||
|
||||
For example, to take a hypothetical `Dog` resource object and change the name attribute:
|
||||
```swift
|
||||
let resourceObject = Dog(...)
|
||||
|
||||
let newResourceObject = resourceObject
|
||||
.tappingAttributes { $0.name = .init(value: "Charlie") }
|
||||
```
|
||||
|
||||
#### Replacing
|
||||
If your `Attributes` or `Relationships` struct is immutable (i.e. its properties are `let`s) then you may find `ResourceObject`'s `replacingAttributes()` and `replacingRelationships()` functions useful. For both, you pass a function that takes the current attributes or relationships and you return a new value. The new value is then used to create a new `ResourceObject`.
|
||||
|
||||
For example, to take a hypothetical `Dog` resource object and change the name attribute:
|
||||
```swift
|
||||
let resourceObject = Dog(...)
|
||||
|
||||
let newResourceObject = resourceObject
|
||||
.replacingAttributes { _ in
|
||||
return Dog.Attributes(name: .init(value: "Charlie"))
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Attribute or Relationship Key Mapping
|
||||
There is not anything special going on at the `JSONAPI.Attributes` and `JSONAPI.Relationships` levels, so you can easily provide custom key mappings by taking advantage of `Codable`'s `CodingKeys` pattern. Here are two models that will encode/decode equivalently but offer different naming in your codebase:
|
||||
```swift
|
||||
|
||||
@@ -169,3 +169,11 @@ extension Includes where I: _Poly10 {
|
||||
return values.compactMap { $0.j }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 11 includes
|
||||
public typealias Include11 = Poly11
|
||||
extension Includes where I: _Poly11 {
|
||||
public subscript(_ lookup: I.K.Type) -> [I.K] {
|
||||
return values.compactMap { $0.k }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// BasicError.swift
|
||||
// BasicJSONAPIError.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 9/29/19.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// GenericError.swift
|
||||
// GenericJSONAPIError.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 9/29/19.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Error.swift
|
||||
// JSONAPIError.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -79,3 +79,8 @@ extension Poly9: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped,
|
||||
extension Poly10: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped, I: EncodablePolyWrapped, J: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly10: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped, J: PolyWrapped {}
|
||||
|
||||
// MARK: - 11 types
|
||||
extension Poly11: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped, I: EncodablePolyWrapped, J: EncodablePolyWrapped, K: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly11: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped, J: PolyWrapped, K: PolyWrapped {}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// ResourceObject+Replacing.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 10/12/19.
|
||||
//
|
||||
|
||||
public extension JSONAPI.ResourceObject {
|
||||
/// Return a new `ResourceObject`, having replaced `self`'s
|
||||
/// `attributes` with the attributes returned by the given
|
||||
/// replacement function.
|
||||
///
|
||||
/// - important: `self` is not mutated. A copy of self is returned.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - replacement: A function that takes the existing `attributes` and returns the replacement.
|
||||
func replacingAttributes(_ replacement: (Description.Attributes) -> Description.Attributes) -> Self {
|
||||
return Self(id: id,
|
||||
attributes: replacement(attributes),
|
||||
relationships: relationships,
|
||||
meta: meta,
|
||||
links: links)
|
||||
}
|
||||
|
||||
/// Return a new `ResourceObject`, having updated `self`'s
|
||||
/// `attributes` with the tap function given.
|
||||
///
|
||||
/// - important: `self` is not mutated. A copy of self is returned.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - tap: A function that takes a copy of the existing `attributes` and mutates them.
|
||||
func tappingAttributes(_ tap: (inout Description.Attributes) -> Void) -> Self {
|
||||
var newAttributes = attributes
|
||||
tap(&newAttributes)
|
||||
return Self(id: id,
|
||||
attributes: newAttributes,
|
||||
relationships: relationships,
|
||||
meta: meta,
|
||||
links: links)
|
||||
}
|
||||
|
||||
/// Return a new `ResourceObject`, having replaced `self`'s
|
||||
/// `relationships` with the `relationships` returned by the given
|
||||
/// replacement function.
|
||||
///
|
||||
/// - important: `self` is not mutated. A copy of self is returned.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - replacement: A function that takes the existing relationships and returns the replacement.
|
||||
func replacingRelationships(_ replacement: (Description.Relationships) -> Description.Relationships) -> Self {
|
||||
return Self(id: id,
|
||||
attributes: attributes,
|
||||
relationships: replacement(relationships),
|
||||
meta: meta,
|
||||
links: links)
|
||||
}
|
||||
|
||||
/// Return a new `ResourceObject`, having updated `self`'s
|
||||
/// `relationships` with the tap function given.
|
||||
///
|
||||
/// - important: `self` is not mutated. A copy of self is returned.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - tap: A function that takes a copy of the existing `relationships` and mutates them.
|
||||
func tappingRelationships(_ tap: (inout Description.Relationships) -> Void) -> Self {
|
||||
var newRelationships = relationships
|
||||
tap(&newRelationships)
|
||||
return Self(id: id,
|
||||
attributes: attributes,
|
||||
relationships: newRelationships,
|
||||
meta: meta,
|
||||
links: links)
|
||||
}
|
||||
}
|
||||
-6
@@ -29,12 +29,6 @@ public enum IdentityTransformer<T>: ReversibleTransformer {
|
||||
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
|
||||
|
||||
/// A Validator is a Transformer that throws an error if an invalid value
|
||||
@@ -9,74 +9,7 @@ import XCTest
|
||||
import JSONAPI
|
||||
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 {
|
||||
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() {
|
||||
let inString = "hello world"
|
||||
|
||||
@@ -116,23 +49,3 @@ enum MoreThanFiveCharValidator: Validator {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,28 @@ class IncludedTests: XCTestCase {
|
||||
test_DecodeEncodeEquality(type: Includes<Include10<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9, TestEntity10>>.self,
|
||||
data: ten_different_type_includes)
|
||||
}
|
||||
|
||||
func test_ElevenDifferentIncludes() {
|
||||
let includes = decoded(type: Includes<Include11<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9, TestEntity10, TestEntity11>>.self,
|
||||
data: eleven_different_type_includes)
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity3.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity5.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity6.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity7.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity8.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity9.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity10.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity11.self].count, 1)
|
||||
}
|
||||
|
||||
func test_ElevenDifferentIncludes_encode() {
|
||||
test_DecodeEncodeEquality(type: Includes<Include11<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9, TestEntity10, TestEntity11>>.self,
|
||||
data: eleven_different_type_includes)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Appending
|
||||
@@ -503,4 +525,15 @@ extension IncludedTests {
|
||||
}
|
||||
|
||||
typealias TestEntity10 = BasicEntity<TestEntityType10>
|
||||
|
||||
enum TestEntityType11: ResourceObjectDescription {
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
public static var jsonType: String { return "test_entity11" }
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntity11 = BasicEntity<TestEntityType11>
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user