mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58a7c82436 | |||
| dc30cb3b9e | |||
| 7045373708 | |||
| 952fe8ba7e | |||
| 951c04ad44 | |||
| 57df6b147e | |||
| a88844fe5e | |||
| 850a713dcb | |||
| e3c637a41e | |||
| 9cbc626410 | |||
| 6dd14daf53 | |||
| 744e08acf4 | |||
| 5da4a963d4 | |||
| 5433dddc81 | |||
| 95f9d8084d | |||
| d6911f170c | |||
| 9972d13a4e | |||
| 4dc63167d6 | |||
| 845d085455 | |||
| 45eb7d06bd | |||
| 6ce962cec9 | |||
| 477dae0a73 | |||
| 4afe4cfb31 | |||
| 65144d27ae | |||
| 1ec4b21c34 | |||
| 15e13e2cc7 | |||
| 1d8ceef317 | |||
| e546614c15 | |||
| 8939d637d4 | |||
| e3b005b89a | |||
| 2de6580519 | |||
| 875d938b95 | |||
| 1ed34b00dc | |||
| 531a7f35bd | |||
| dc42ec27fc | |||
| cf746e182f | |||
| 52d2e9819d | |||
| 81b78cd749 | |||
| 3a0ede2372 | |||
| cb04a01e71 | |||
| 3c29244540 | |||
| a9e1e49a79 | |||
| 9e6e713ad2 | |||
| 308f168a8c | |||
| 8726bcc9fa | |||
| 25bda795a7 | |||
| 14a0256885 | |||
| b2041e9bc8 | |||
| fd9a626551 | |||
| 69bb40aa00 | |||
| 060e0e3898 | |||
| b374367234 | |||
| b363db30cd | |||
| 6904416623 | |||
| c04d3301b6 | |||
| 5a64bebc99 | |||
| 790b0fbf52 | |||
| 539ecc451a | |||
| 7eb1b05eae | |||
| 3408263c2a |
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import Poly
|
||||
|
||||
// MARK: - Preamble (setup)
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//: [Previous](@previous)
|
||||
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import JSONAPIOpenAPI
|
||||
|
||||
// print Entity Schema
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
|
||||
let personSchemaData = try? encoder.encode(Person.openAPINode())
|
||||
|
||||
print("Person Schema")
|
||||
print("====")
|
||||
print(personSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
|
||||
print("====")
|
||||
|
||||
let dogDocumentSchemaData = try? encoder.encode(SingleDogDocument.openAPINodeWithExample(using: encoder))
|
||||
|
||||
print("Dog Document Schema")
|
||||
print("====")
|
||||
print(dogDocumentSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
|
||||
print("====")
|
||||
|
||||
let batchPersonSchemaData = try? encoder.encode(BatchPeopleDocument.openAPINodeWithExample(using: encoder))
|
||||
|
||||
print("Batch Person Document Schema")
|
||||
print("====")
|
||||
print(batchPersonSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
|
||||
print("====")
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import JSONAPITestLib
|
||||
import JSONAPITesting
|
||||
|
||||
/*******
|
||||
|
||||
@@ -11,12 +11,12 @@ Please enjoy these examples, but allow me the forced casting and the lack of err
|
||||
********/
|
||||
|
||||
// MARK: - Literal Expressibility
|
||||
// The JSONAPITestLib provides literal expressibility for key types to
|
||||
// The JSONAPITesting framework provides literal expressibility for key types to
|
||||
// make creating tests easier
|
||||
let dog = Dog(id: "1234", attributes: Dog.Attributes(name: "Buddy"), relationships: Dog.Relationships(owner: nil), meta: .none, links: .none)
|
||||
|
||||
// MARK: - JSON API structure checking
|
||||
// The JSONAPITestLib provides a `check` function for each Entity type
|
||||
// The JSONAPITesting framework provides a `check` function for each Entity type
|
||||
// that uses reflection to catch mistakes that are not forbidden by
|
||||
// Swift's type system but will result in unexpected results when
|
||||
// encoding/decoding. It is a good idea to add a `check` to each of
|
||||
|
||||
@@ -11,8 +11,6 @@ Please enjoy these examples, but allow me the forced casting and the lack of err
|
||||
// MARK: - Create a request or response body with one Dog in it
|
||||
let dogFromCode = try! Dog(name: "Buddy", owner: nil)
|
||||
|
||||
typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
let singleDogDocument = SingleDogDocument(apiDescription: .none, body: .init(entity: dogFromCode), includes: .none, meta: .none, links: .none)
|
||||
|
||||
let singleDogData = try! JSONEncoder().encode(singleDogDocument)
|
||||
@@ -34,8 +32,6 @@ let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner
|
||||
let houses = [House(attributes: .none, relationships: .none, meta: .none, links: .none), House(attributes: .none, relationships: .none, meta: .none, links: .none)]
|
||||
let people = try! [Person(id: personIds[0], name: ["Gary", "Doe"], favoriteColor: "Orange-Red", friends: [], dogs: [dogs[0], dogs[1]], home: houses[0]), Person(id: personIds[1], name: ["Elise", "Joy"], favoriteColor: "Red", friends: [], dogs: [dogs[2]], home: houses[1])]
|
||||
|
||||
typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
let includes = dogs.map { BatchPeopleDocument.Include($0) } + houses.map { BatchPeopleDocument.Include($0) }
|
||||
let batchPeopleDocument = BatchPeopleDocument(apiDescription: .none, body: .init(entities: people), includes: .init(values: includes), meta: .none, links: .none)
|
||||
let batchPeopleData = try! JSONEncoder().encode(batchPeopleDocument)
|
||||
|
||||
@@ -139,4 +139,6 @@ public enum HouseDescription: EntityDescription {
|
||||
|
||||
public typealias House = ExampleEntity<HouseDescription>
|
||||
|
||||
public typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
public typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import JSONAPITesting // for the convenience of literal initialization
|
||||
import JSONAPIOpenAPI
|
||||
import SwiftCheck
|
||||
import JSONAPIArbitrary
|
||||
|
||||
extension PersonDescription.Attributes: Arbitrary, Sampleable {
|
||||
public static var arbitrary: Gen<PersonDescription.Attributes> {
|
||||
return Gen.compose { c in
|
||||
return PersonDescription.Attributes(name: c.generate(),
|
||||
favoriteColor: c.generate())
|
||||
}
|
||||
}
|
||||
|
||||
public static var sample: PersonDescription.Attributes {
|
||||
return .init(name: ["Abbie", "Eibba"], favoriteColor: "Blue")
|
||||
}
|
||||
}
|
||||
|
||||
extension PersonDescription.Relationships: Arbitrary, Sampleable {
|
||||
public static var arbitrary: Gen<PersonDescription.Relationships> {
|
||||
return Gen.compose { c in
|
||||
return PersonDescription.Relationships(friends: c.generate(),
|
||||
dogs: c.generate(),
|
||||
home: c.generate())
|
||||
}
|
||||
}
|
||||
|
||||
public static var sample: PersonDescription.Relationships {
|
||||
return .init(friends: ["1", "2"], dogs: ["2"], home: "1")
|
||||
}
|
||||
}
|
||||
|
||||
extension DogDescription.Attributes: Arbitrary, Sampleable {
|
||||
public static var arbitrary: Gen<DogDescription.Attributes> {
|
||||
return Gen.compose { c in
|
||||
return DogDescription.Attributes(name: c.generate())
|
||||
}
|
||||
}
|
||||
|
||||
public static var sample: DogDescription.Attributes {
|
||||
return DogDescription.Attributes.arbitrary.generate
|
||||
}
|
||||
}
|
||||
|
||||
extension DogDescription.Relationships: Arbitrary, Sampleable {
|
||||
public static var arbitrary: Gen<DogDescription.Relationships> {
|
||||
return Gen.compose { c in
|
||||
return DogDescription.Relationships(owner: c.generate())
|
||||
}
|
||||
}
|
||||
|
||||
public static var sample: DogDescription.Relationships {
|
||||
return DogDescription.Relationships.arbitrary.generate
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Sampleable where PrimaryResourceBody: Arbitrary, IncludeType: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary, Error: Arbitrary, APIDescription: Arbitrary {
|
||||
public static var sample: Document {
|
||||
return Document.arbitrary.generate
|
||||
}
|
||||
|
||||
public static var successSample: Document? {
|
||||
return Document.arbitraryData.generate
|
||||
}
|
||||
|
||||
public static var failureSample: Document? {
|
||||
return Document.arbitraryErrors.generate
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,7 @@
|
||||
<pages>
|
||||
<page name='Test Library'/>
|
||||
<page name='Usage'/>
|
||||
<page name='Full Client & Server Example'/>
|
||||
<page name='Full Document Verbose Generation'/>
|
||||
</pages>
|
||||
</playground>
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright 2018 Mathew Polzin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+36
-1
@@ -1,7 +1,42 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
|
||||
{
|
||||
"package": "AnyCodable",
|
||||
"repositoryURL": "https://github.com/Flight-School/AnyCodable.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "396ccc3dba5bdee04c1e742e7fab40582861401e",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "FileCheck",
|
||||
"repositoryURL": "https://github.com/llvm-swift/FileCheck.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "89b8480055f9adf8ce2f9ad5e2fac7ac1076242e",
|
||||
"version": "0.0.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Poly",
|
||||
"repositoryURL": "https://github.com/mattpolzin/Poly.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "77f45b8963a51c02d71fc4075eba5cff47ff0d07",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftCheck",
|
||||
"repositoryURL": "https://github.com/typelift/SwiftCheck.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "cf9958085b2ee1643e541e407c3233d1b76c18ff",
|
||||
"version": "0.11.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
|
||||
+29
-5
@@ -10,21 +10,45 @@ let package = Package(
|
||||
name: "JSONAPI",
|
||||
targets: ["JSONAPI"]),
|
||||
.library(
|
||||
name: "JSONAPITestLib",
|
||||
targets: ["JSONAPITestLib"])
|
||||
name: "JSONAPITesting",
|
||||
targets: ["JSONAPITesting"]),
|
||||
.library(
|
||||
name: "JSONAPIArbitrary",
|
||||
targets: ["JSONAPIArbitrary"]),
|
||||
.library(
|
||||
name: "JSONAPIOpenAPI",
|
||||
targets: ["JSONAPIOpenAPI"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/mattpolzin/Poly.git", from: "1.0.0"),
|
||||
.package(url: "https://github.com/Flight-School/AnyCodable.git", from: "0.1.0"),
|
||||
.package(url: "https://github.com/typelift/SwiftCheck.git", from: "0.11.0")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "JSONAPI",
|
||||
dependencies: []),
|
||||
dependencies: ["Poly"]),
|
||||
.target(
|
||||
name: "JSONAPITestLib",
|
||||
name: "JSONAPITesting",
|
||||
dependencies: ["JSONAPI"]),
|
||||
.target(
|
||||
name: "JSONAPIArbitrary",
|
||||
dependencies: ["JSONAPI", "SwiftCheck"]),
|
||||
.target(
|
||||
name: "JSONAPIOpenAPI",
|
||||
dependencies: ["JSONAPI", "AnyCodable", "JSONAPIArbitrary"]),
|
||||
.testTarget(
|
||||
name: "JSONAPITests",
|
||||
dependencies: ["JSONAPITestLib"])
|
||||
dependencies: ["JSONAPI", "JSONAPITesting"]),
|
||||
.testTarget(
|
||||
name: "JSONAPITestingTests",
|
||||
dependencies: ["JSONAPI", "JSONAPITesting"]),
|
||||
.testTarget(
|
||||
name: "JSONAPIArbitraryTests",
|
||||
dependencies: ["JSONAPI", "SwiftCheck", "JSONAPIArbitrary"]),
|
||||
.testTarget(
|
||||
name: "JSONAPIOpenAPITests",
|
||||
dependencies: ["JSONAPI", "JSONAPIOpenAPI"])
|
||||
],
|
||||
swiftLanguageVersions: [.v4_2]
|
||||
)
|
||||
|
||||
@@ -5,59 +5,65 @@ A Swift package for encoding to- and decoding from **JSON API** compliant reques
|
||||
|
||||
See the JSON API Spec here: https://jsonapi.org/format/
|
||||
|
||||
## Table of Contents
|
||||
<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
|
||||
:warning: Although I find the type-safety of this framework appealing, the Swift compiler currently has enough trouble with it that it can become difficult to reason about errors produced by small typos. Similarly, auto-complete fails to provide reasonable suggestions much of the time. If you get the code right, everything compiles, otherwise it can suck to figure out what is wrong. This is mostly a concern when creating entities in-code (servers and test suites must do this). Writing a client that uses this framework to ingest JSON API Compliant API responses is much less painful. :warning:
|
||||
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Primary Goals](#primary-goals)
|
||||
- [Caveat](#caveat)
|
||||
- [Dev Environment](#dev-environment)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Xcode project](#xcode-project)
|
||||
- [Running the Playground](#running-the-playground)
|
||||
- [Project Status](#project-status)
|
||||
- [Encoding/Decoding](#encodingdecoding)
|
||||
- [Document](#document)
|
||||
- [Resource Object](#resource-object)
|
||||
- [Relationship Object](#relationship-object)
|
||||
- [Links Object](#links-object)
|
||||
- [Misc](#misc)
|
||||
- [JSONAPITestLib](#jsonapitestlib)
|
||||
- [Entity Validator](#entity-validator)
|
||||
- [Potential Improvements](#potential-improvements)
|
||||
- [Usage](#usage)
|
||||
- [`JSONAPI.EntityDescription`](#jsonapientitydescription)
|
||||
- [`JSONAPI.Entity`](#jsonapientity)
|
||||
- [`Meta`](#meta)
|
||||
- [`Links`](#links)
|
||||
- [`IdType`](#idtype)
|
||||
- [`MaybeRawId`](#mayberawid)
|
||||
- [Convenient `typealiases`](#convenient-typealiases)
|
||||
- [`JSONAPI.Relationships`](#jsonapirelationships)
|
||||
- [`JSONAPI.Attributes`](#jsonapiattributes)
|
||||
- [`Transformer`](#transformer)
|
||||
- [`Validator`](#validator)
|
||||
- [Computed `Attribute`](#computed-attribute)
|
||||
- [Copying `Entities`](#copying-entities)
|
||||
- [`JSONAPI.Document`](#jsonapidocument)
|
||||
- [`ResourceBody`](#resourcebody)
|
||||
- [nullable `PrimaryResource`](#nullable-primaryresource)
|
||||
- [`MetaType`](#metatype)
|
||||
- [`LinksType`](#linkstype)
|
||||
- [`IncludeType`](#includetype)
|
||||
- [`APIDescriptionType`](#apidescriptiontype)
|
||||
- [`Error`](#error)
|
||||
- [`JSONAPI.Meta`](#jsonapimeta)
|
||||
- [`JSONAPI.Links`](#jsonapilinks)
|
||||
- [`JSONAPI.RawIdType`](#jsonapirawidtype)
|
||||
- [Custom Attribute or Relationship Key Mapping](#custom-attribute-or-relationship-key-mapping)
|
||||
- [Custom Attribute Encode/Decode](#custom-attribute-encodedecode)
|
||||
- [Meta-attributes](#meta-attributes)
|
||||
- [Example](#example)
|
||||
- [Preamble (Setup shared by server and client)](#preamble-setup-shared-by-server-and-client)
|
||||
- [Server Pseudo-example](#server-pseudo-example)
|
||||
- [Client Pseudo-example](#client-pseudo-example)
|
||||
- [JSONAPITestLib](#jsonapitestlib)
|
||||
## Table of Contents
|
||||
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
|
||||
|
||||
- [JSONAPI](#jsonapi)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Primary Goals](#primary-goals)
|
||||
- [Caveat](#caveat)
|
||||
- [Dev Environment](#dev-environment)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Xcode project](#xcode-project)
|
||||
- [Running the Playground](#running-the-playground)
|
||||
- [Project Status](#project-status)
|
||||
- [JSON:API](#jsonapi)
|
||||
- [Document](#document)
|
||||
- [Resource Object](#resource-object)
|
||||
- [Relationship Object](#relationship-object)
|
||||
- [Links Object](#links-object)
|
||||
- [Misc](#misc)
|
||||
- [Testing](#testing)
|
||||
- [Entity Validator](#entity-validator)
|
||||
- [Potential Improvements](#potential-improvements)
|
||||
- [Usage](#usage)
|
||||
- [`JSONAPI.EntityDescription`](#jsonapientitydescription)
|
||||
- [`JSONAPI.Entity`](#jsonapientity)
|
||||
- [`Meta`](#meta)
|
||||
- [`Links`](#links)
|
||||
- [`IdType`](#idtype)
|
||||
- [`MaybeRawId`](#mayberawid)
|
||||
- [Convenient `typealiases`](#convenient-typealiases)
|
||||
- [`JSONAPI.Relationships`](#jsonapirelationships)
|
||||
- [`JSONAPI.Attributes`](#jsonapiattributes)
|
||||
- [`Transformer`](#transformer)
|
||||
- [`Validator`](#validator)
|
||||
- [Computed `Attribute`](#computed-attribute)
|
||||
- [Copying `Entities`](#copying-entities)
|
||||
- [`JSONAPI.Document`](#jsonapidocument)
|
||||
- [`ResourceBody`](#resourcebody)
|
||||
- [nullable `PrimaryResource`](#nullable-primaryresource)
|
||||
- [`MetaType`](#metatype)
|
||||
- [`LinksType`](#linkstype)
|
||||
- [`IncludeType`](#includetype)
|
||||
- [`APIDescriptionType`](#apidescriptiontype)
|
||||
- [`Error`](#error)
|
||||
- [`JSONAPI.Meta`](#jsonapimeta)
|
||||
- [`JSONAPI.Links`](#jsonapilinks)
|
||||
- [`JSONAPI.RawIdType`](#jsonapirawidtype)
|
||||
- [Custom Attribute or Relationship Key Mapping](#custom-attribute-or-relationship-key-mapping)
|
||||
- [Custom Attribute Encode/Decode](#custom-attribute-encodedecode)
|
||||
- [Meta-Attributes](#meta-attributes)
|
||||
- [Meta-Relationships](#meta-relationships)
|
||||
- [Example](#example)
|
||||
- [Preamble (Setup shared by server and client)](#preamble-setup-shared-by-server-and-client)
|
||||
- [Server Pseudo-example](#server-pseudo-example)
|
||||
- [Client Pseudo-example](#client-pseudo-example)
|
||||
- [JSONAPI+Testing](#jsonapitesting)
|
||||
- [JSONAPI+Arbitrary](#jsonapiarbitrary)
|
||||
- [JSONAPI+OpenAPI](#jsonapiopenapi)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
@@ -89,31 +95,79 @@ Note that Playground support for importing non-system Frameworks is still a bit
|
||||
|
||||
## Project Status
|
||||
|
||||
### Encoding/Decoding
|
||||
### JSON:API
|
||||
#### Document
|
||||
- [x] `data`
|
||||
- [x] `included`
|
||||
- [x] `errors`
|
||||
- [x] `meta`
|
||||
- [x] `jsonapi`
|
||||
- [x] `links`
|
||||
- `data`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [x] OpenAPI
|
||||
- `included`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [x] OpenAPI
|
||||
- `errors`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
- `meta`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
- `jsonapi` (i.e. API Information)
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
- `links`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
|
||||
#### Resource Object
|
||||
- [x] `id`
|
||||
- [x] `type`
|
||||
- [x] `attributes`
|
||||
- [x] `relationships`
|
||||
- [x] `links`
|
||||
- [x] `meta`
|
||||
- `id`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [x] OpenAPI
|
||||
- `type`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] OpenAPI
|
||||
- `attributes`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] OpenAPI
|
||||
- `relationships`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] OpenAPI
|
||||
- `links`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
- `meta`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
|
||||
#### Relationship Object
|
||||
- [x] `data`
|
||||
- [x] `links`
|
||||
- [x] `meta`
|
||||
- `data`
|
||||
- [x] Encoding/Decoding
|
||||
- [x] Arbitrary
|
||||
- [x] OpenAPI
|
||||
- `links`
|
||||
- [x] Encoding/Decoding
|
||||
- [ ] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
- `meta`
|
||||
- [x] Encoding/Decoding
|
||||
- [ ] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
|
||||
#### Links Object
|
||||
- [x] `href`
|
||||
- [x] `meta`
|
||||
- `href`
|
||||
- [x] Encoding/Decoding
|
||||
- [ ] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
- `meta`
|
||||
- [x] Encoding/Decoding
|
||||
- [ ] Arbitrary
|
||||
- [ ] OpenAPI
|
||||
|
||||
### Misc
|
||||
- [x] Support transforms on `Attributes` values (e.g. to support different representations of `Date`)
|
||||
@@ -121,7 +175,7 @@ Note that Playground support for importing non-system Frameworks is still a bit
|
||||
- [ ] Support sparse fieldsets. At the moment, not sure what this support will look like. A client can likely just define a new model to represent a sparse population of another model in a very specific use case. On the server side, it becomes much more appealing to be able to support arbitrary combinations of omitted fields.
|
||||
- [ ] Create more descriptive errors that are easier to use for troubleshooting.
|
||||
|
||||
### JSONAPITestLib
|
||||
### Testing
|
||||
#### Entity Validator
|
||||
- [x] Disallow optional array in `Attribute` (should be empty array, not `null`).
|
||||
- [x] Only allow `TransformedAttribute` and its derivatives as stored properties within `Attributes` struct. Computed properties can still be any type because they do not get encoded or decoded.
|
||||
@@ -142,7 +196,7 @@ In this documentation, in order to draw attention to the difference between the
|
||||
|
||||
An `EntityDescription` is the `JSONAPI` framework's representation of what the **SPEC** calls a *Resource Object*. You might create the following `EntityDescription` to represent a person in a network of friends:
|
||||
|
||||
```
|
||||
```swift
|
||||
enum PersonDescription: IdentifiedEntityDescription {
|
||||
static var jsonType: String { return "people" }
|
||||
|
||||
@@ -225,14 +279,14 @@ A `RawIdType` is the underlying type that uniquely identifies an `Entity`. This
|
||||
#### Convenient `typealiases`
|
||||
|
||||
Often you can use one `RawIdType` for many if not all of your `Entities`. That means you can save yourself some boilerplate by using `typealias`es like the following:
|
||||
```
|
||||
```swift
|
||||
public typealias Entity<Description: JSONAPI.EntityDescription, Meta: JSONAPI.Meta, Links: JSONAPI.Links> = JSONAPI.Entity<Description, Meta, Links, String>
|
||||
|
||||
public typealias NewEntity<Description: JSONAPI.EntityDescription, Meta: JSONAPI.Meta, Links: JSONAPI.Links> = JSONAPI.Entity<Description, Meta, Links, Unidentified>
|
||||
```
|
||||
|
||||
It can also be nice to create a `typealias` for each type of entity you want to work with:
|
||||
```
|
||||
```swift
|
||||
typealias Person = Entity<PersonDescription, NoMetadata, NoLinks>
|
||||
|
||||
typealias NewPerson = NewEntity<PersonDescription, NoMetadata, NoLinks>
|
||||
@@ -247,17 +301,17 @@ There are two types of `Relationships`: `ToOneRelationship` and `ToManyRelations
|
||||
In addition to identifying entities by Id and type, `Relationships` can contain `Meta` or `Links` that follow the same rules as [`Meta`](#jsonapimeta) and [`Links`](#jsonapilinks) elsewhere in the JSON API Document.
|
||||
|
||||
To describe a relationship that may be omitted (i.e. the key is not even present in the JSON object), you make the entire `ToOneRelationship` or `ToManyRelationship` optional. However, this is not recommended because you can also represent optional relationships as nullable which means the key is always present. A `ToManyRelationship` can naturally represent the absence of related values with an empty array, so `ToManyRelationship` does not support nullability at all. A `ToOneRelationship` can be marked as nullable (i.e. the value could be either `null` or a resource identifier) like this:
|
||||
```
|
||||
```swift
|
||||
let nullableRelative: ToOneRelationship<Person?, NoMetadata, NoLinks>
|
||||
```
|
||||
|
||||
An entity that does not have relationships can be described by adding the following to an `EntityDescription`:
|
||||
```
|
||||
```swift
|
||||
typealias Relationships = NoRelationships
|
||||
```
|
||||
|
||||
`Relationship` values boil down to `Ids` of other entities. To access the `Id` of a related `Entity`, you can use the custom `~>` operator with the `KeyPath` of the `Relationship` from which you want the `Id`. The friends of the above `Person` `Entity` can be accessed as follows (type annotations for clarity):
|
||||
```
|
||||
```swift
|
||||
let friendIds: [Person.Identifier] = person ~> \.friends
|
||||
```
|
||||
|
||||
@@ -266,27 +320,27 @@ let friendIds: [Person.Identifier] = person ~> \.friends
|
||||
The `Attributes` of an `EntityDescription` can contain any JSON encodable/decodable types as long as they are wrapped in an `Attribute`, `ValidatedAttribute`, or `TransformedAttribute` `struct`.
|
||||
|
||||
To describe an attribute that may be omitted (i.e. the key might not even be in the JSON object), you make the entire `Attribute` optional:
|
||||
```
|
||||
```swift
|
||||
let optionalAttribute: Attribute<String>?
|
||||
```
|
||||
|
||||
To describe an attribute that is expected to exist but might have a `null` value, you make the value within the `Attribute` optional:
|
||||
```
|
||||
```swift
|
||||
let nullableAttribute: Attribute<String?>
|
||||
```
|
||||
|
||||
An entity that does not have attributes can be described by adding the following to an `EntityDescription`:
|
||||
```
|
||||
```swift
|
||||
typealias Attributes = NoAttributes
|
||||
```
|
||||
|
||||
`Attributes` can be accessed via the `subscript` operator of the `Entity` type as follows:
|
||||
```
|
||||
```swift
|
||||
let favoriteColor: String = person[\.favoriteColor]
|
||||
```
|
||||
|
||||
NOTE: Because of support for computed properties that are not wrapped in `Attribute`, `TransformedAttribute`, or `ValidatedAttribute`, the compiler cannot always infer the type of thing you want back when using subscript attribute access. The following code is ambiguous about whether it should return a `String` or an `Attribute<String>`:
|
||||
```
|
||||
```swift
|
||||
let favoriteColor = person[\.favoriteColor]
|
||||
```
|
||||
|
||||
@@ -295,7 +349,7 @@ let favoriteColor = person[\.favoriteColor]
|
||||
Sometimes you need to use a type that does not encode or decode itself in the way you need to represent it as a serialized JSON object. For example, the Swift `Foundation` type `Date` can encode/decode itself to `Double` out of the box, but you might want to represent dates as ISO 8601 compliant `String`s instead. The Foundation library `JSONDecoder` has a setting to make this adjustment, but for the sake of an example, you could create a `Transformer`.
|
||||
|
||||
A `Transformer` just provides one static function that transforms one type to another. You might define one for an ISO 8601 compliant `Date` like this:
|
||||
```
|
||||
```swift
|
||||
enum ISODateTransformer: Transformer {
|
||||
public static func transform(_ value: String) throws -> Date {
|
||||
// parse Date out of input and return
|
||||
@@ -304,14 +358,14 @@ enum ISODateTransformer: Transformer {
|
||||
```
|
||||
|
||||
Then you define the attribute as a `TransformedAttribute` instead of an `Attribute`:
|
||||
```
|
||||
```swift
|
||||
let date: TransformedAttribute<String, ISODateTransformer>
|
||||
```
|
||||
|
||||
Note that the first generic parameter of `TransformAttribute` is the type you expect to decode from JSON, not the type you want to end up with after transformation.
|
||||
|
||||
If you make your `Transformer` a `ReversibleTransformer` then your life will be a bit easier when you construct `TransformedAttributes` because you have access to initializers for both the pre- and post-transformed value types. Continuing with the above example of a `ISODateTransformer`:
|
||||
```
|
||||
```swift
|
||||
extension ISODateTransformer: ReversibleTransformer {
|
||||
public static func reverse(_ value: Date) throws -> String {
|
||||
// serialize Date to a String
|
||||
@@ -330,7 +384,7 @@ You can also creator `Validators` and `ValidatedAttribute`s. A `Validator` is ju
|
||||
|
||||
You can add computed properties to your `EntityDescription.Attributes` struct if you would like to expose attributes that are not explicitly represented by the JSON. These computed properties do not have to be wrapped in `Attribute`, `ValidatedAttribute`, or `TransformedAttribute`. This allows computed attributes to be of types that are not `Codable`. Here's an example of how you might take the `Person[\.name]` attribute from the example above and create a `fullName` computed property.
|
||||
|
||||
```
|
||||
```swift
|
||||
public var fullName: Attribute<String> {
|
||||
return name.map { $0.joined(separator: " ") }
|
||||
}
|
||||
@@ -343,7 +397,7 @@ public var fullName: Attribute<String> {
|
||||
|
||||
The above can be accomplished with code like the following:
|
||||
|
||||
```
|
||||
```swift
|
||||
// use case 1
|
||||
let person1 = person.withNewIdentifier()
|
||||
|
||||
@@ -356,7 +410,7 @@ let newlyIdentifiedPerson2 = unidentifiedPerson.identified(by: "2232")
|
||||
### `JSONAPI.Document`
|
||||
|
||||
The entirety of a JSON API request or response is encoded or decoded from- or to a `Document`. As an example, a JSON API response containing one `Person` and no included entities could be decoded as follows:
|
||||
```
|
||||
```swift
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let responseStructure = JSONAPI.Document<SingleResourceBody<Person>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self
|
||||
@@ -392,7 +446,7 @@ The second generic type of a `JSONAPIDocument` is a `Meta`. This `Meta` follows
|
||||
```
|
||||
|
||||
You would then create the following `Meta` type:
|
||||
```
|
||||
```swift
|
||||
struct PageMetadata: JSONAPI.Meta {
|
||||
let total: Int
|
||||
let limit: Int
|
||||
@@ -443,7 +497,7 @@ You can specify `NoLinks` if the part of the document being described should not
|
||||
### `JSONAPI.RawIdType`
|
||||
|
||||
If you want to create new `JSONAPI.Entity` values and assign them Ids then you will need to conform at least one type to `CreatableRawIdType`. Doing so is easy; here are two example conformances for `UUID` and `String` (via `UUID`):
|
||||
```
|
||||
```swift
|
||||
extension UUID: CreatableRawIdType {
|
||||
public static func unique() -> UUID {
|
||||
return UUID()
|
||||
@@ -459,7 +513,7 @@ extension String: CreatableRawIdType {
|
||||
|
||||
### 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
|
||||
public enum EntityDescription1: JSONAPI.EntityDescription {
|
||||
public static var jsonType: String { return "entity" }
|
||||
|
||||
@@ -485,7 +539,7 @@ public enum EntityDescription2: JSONAPI.EntityDescription {
|
||||
|
||||
### Custom Attribute Encode/Decode
|
||||
You can safely provide your own encoding or decoding functions for your Attributes struct if you need to as long as you are careful that your encode operation correctly reverses your decode operation. Although this is generally not necessary, `AttributeType` provides a convenience method to make your decoding a bit less boilerplate ridden. This is what it looks like:
|
||||
```
|
||||
```swift
|
||||
public enum EntityDescription1: JSONAPI.EntityDescription {
|
||||
public static var jsonType: String { return "entity" }
|
||||
|
||||
@@ -527,12 +581,12 @@ extension EntityDescription1.Attributes {
|
||||
}
|
||||
```
|
||||
|
||||
### Meta-attributes
|
||||
### Meta-Attributes
|
||||
This advanced feature may not ever be useful, but if you find yourself in the situation of dealing with an API that does not 100% follow the **SPEC** then you might find meta-attributes are just the thing to make your entities more natural to work with.
|
||||
|
||||
Suppose, for example, you are presented with the unfortunate situation where a piece of information you need is only available as part of the `Id` of an entity. Perhaps a user's `Id` is formatted "{integer}-{createdAt}" where "createdAt" is the unix timestamp when the user account was created. The following `UserDescription` will expose what you need as an attribute. Realistically, this code is still terrible for its error handling. Using a `Result` type and/or invariants would clean things up substantially.
|
||||
|
||||
```
|
||||
```swift
|
||||
enum UserDescription: EntityDescription {
|
||||
public static var jsonType: String { return "users" }
|
||||
|
||||
@@ -566,17 +620,50 @@ typealias User = JSONAPI.Entity<UserDescription, NoMetadata, NoLinks, String>
|
||||
|
||||
Given a value `user` of the above entity type, you can access the `createdAt` attribute just like you would any other:
|
||||
|
||||
```
|
||||
```swift
|
||||
let createdAt = user[\.createdAt]
|
||||
```
|
||||
|
||||
This works because `createdAt` is defined in the form: `var {name}: ({Entity}) -> {Value}` where `{Entity}` is the `JSONAPI.Entity` described by the `EntityDescription` containing the meta-attribute.
|
||||
|
||||
### Meta-Relationships
|
||||
This advanced feature may not ever be useful, but if you find yourself in the situation of dealing with an API that does not 100% follow the **SPEC** then you might find meta-relationships are just the thing to make your entities more natural to work with.
|
||||
|
||||
Similarly to Meta-Attributes, Meta-Relationships allow you to represent non-compliant relationships as computed relationship properties. In the following example, a relationship is created from some attributes on the JSON model.
|
||||
|
||||
```swift
|
||||
enum UserDescription: EntityDescription {
|
||||
public static var jsonType: String { return "users" }
|
||||
|
||||
struct Attributes: JSONAPI.Attributes {
|
||||
let friend_id: Attribute<String>
|
||||
}
|
||||
|
||||
struct Relationships: JSONAPI.Relationships {
|
||||
public var friend: (User) -> User.Identifier {
|
||||
return { user in
|
||||
return User.Identifier(rawValue: user[\.friend_id])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias User = JSONAPI.Entity<UserDescription, NoMetadata, NoLinks, String>
|
||||
```
|
||||
|
||||
Given a value `user` of the above entity type, you can access the `friend` relationship just like you would any other:
|
||||
|
||||
```swift
|
||||
let friendId = user ~> \.friend
|
||||
```
|
||||
|
||||
This works because `friend` is defined in the form: `var {name}: ({Entity}) -> {Identifier}` where `{Entity}` is the `JSONAPI.Entity` described by the `EntityDescription` containing the meta-relationship.
|
||||
|
||||
## Example
|
||||
The following serves as a sort of pseudo-example. It skips server/client implementation details not related to JSON:API but still gives a more complete picture of what an implementation using this framework might look like. You can play with this example code in the Playground provided with this repo.
|
||||
|
||||
### Preamble (Setup shared by server and client)
|
||||
```
|
||||
```swift
|
||||
// We make String a CreatableRawIdType.
|
||||
var GlobalStringId: Int = 0
|
||||
extension String: CreatableRawIdType {
|
||||
@@ -648,7 +735,7 @@ typealias SingleArticleDocumentWithIncludes = Document<SingleResourceBody<Articl
|
||||
typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoIncludes>
|
||||
```
|
||||
### Server Pseudo-example
|
||||
```
|
||||
```swift
|
||||
// Skipping over all the API and database stuff, here's a chunk of code
|
||||
// that creates a document. Note that this document is the entirety
|
||||
// of a JSON:API response body.
|
||||
@@ -708,7 +795,7 @@ print(String(data: otherResponseData, encoding: .utf8)!)
|
||||
```
|
||||
|
||||
### Client Pseudo-example
|
||||
```
|
||||
```swift
|
||||
enum NetworkError: Swift.Error {
|
||||
case serverError
|
||||
case quantityMismatch
|
||||
@@ -745,5 +832,15 @@ print(response.article)
|
||||
print(response.author)
|
||||
```
|
||||
|
||||
## JSONAPITestLib
|
||||
The `JSONAPI` framework is packaged with a test library to help you test your `JSONAPI` integration. The test library is called `JSONAPITestLib`. It provides literal expressibility for `Attribute`, `ToOneRelationship`, and `Id` in many situations so that you can easily write test `Entity` values into your unit tests. It also provides a `check()` function for each `Entity` type that can be used to catch problems with your `JSONAPI` structures that are not caught by Swift's type system. You can see the `JSONAPITestLib` in action in the Playground included with the `JSONAPI` repository.
|
||||
# JSONAPI+Testing
|
||||
The `JSONAPI` framework is packaged with a test library to help you test your `JSONAPI` integration. The test library is called `JSONAPITesting`. It provides literal expressibility for `Attribute`, `ToOneRelationship`, and `Id` in many situations so that you can easily write test `Entity` values into your unit tests. It also provides a `check()` function for each `Entity` type that can be used to catch problems with your `JSONAPI` structures that are not caught by Swift's type system. You can see the `JSONAPITesting` in action in the Playground included with the `JSONAPI` repository.
|
||||
|
||||
# JSONAPI+Arbitrary
|
||||
The `JSONAPIArbitrary` framework adds `Arbitrary` support via `SwiftCheck`. With a little extra work on your part, this framework will allow you to create "arbitrary" (i.e. randomly generated) instances of your JSONAPI entities, includes, documents, etc.
|
||||
|
||||
This Framework is currently undocumented, but familiarity with `SwiftCheck` will likely afford the user enough information to use this library to aid in the generation of arbitrary JSONAPI Documents for testing purposes.
|
||||
|
||||
# JSONAPI+OpenAPI
|
||||
The `JSONAPIOpenAPI` framework adds the ability to generate OpenAPI compliant JSON documentation of a JSONAPI Document.
|
||||
|
||||
This library is in its infancy. The documentation will grow as the framework becomes more complete.
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by Mathew Polzin on 11/5/18.
|
||||
//
|
||||
|
||||
import Poly
|
||||
|
||||
public protocol JSONAPIDocument: Codable, Equatable {
|
||||
associatedtype PrimaryResourceBody: JSONAPI.ResourceBody
|
||||
associatedtype MetaType: JSONAPI.Meta
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
//
|
||||
|
||||
public typealias Include = Poly
|
||||
import Poly
|
||||
|
||||
public typealias Include = JSONPoly
|
||||
|
||||
public struct Includes<I: Include>: Codable, Equatable {
|
||||
public static var none: Includes { return .init(values: []) }
|
||||
|
||||
@@ -16,7 +16,7 @@ public protocol AttributeType: Codable {
|
||||
|
||||
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
|
||||
public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: AttributeType where Transformer.From == RawValue {
|
||||
let rawValue: RawValue
|
||||
public let rawValue: RawValue
|
||||
|
||||
public let value: Transformer.To
|
||||
|
||||
|
||||
@@ -522,6 +522,23 @@ public extension EntityProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Meta-Relationship Access
|
||||
public extension EntityProxy {
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
/// This allows you to write `entity ~> \.other` instead
|
||||
/// of `entity.relationships.other.id`.
|
||||
public static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> Identifier>) -> Identifier {
|
||||
return entity.relationships[keyPath: path](entity)
|
||||
}
|
||||
|
||||
/// Access to all Ids of a `ToManyRelationship`.
|
||||
/// This allows you to write `entity ~> \.others` instead
|
||||
/// of `entity.relationships.others.ids`.
|
||||
public static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> [Identifier]>) -> [Identifier] {
|
||||
return entity.relationships[keyPath: path](entity)
|
||||
}
|
||||
}
|
||||
|
||||
infix operator ~>
|
||||
|
||||
// MARK: - Codable
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Poly+PrimaryResource.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 11/22/18.
|
||||
//
|
||||
|
||||
import Poly
|
||||
|
||||
/// Poly is a protocol to which types that
|
||||
/// are polymorphic belong to. Specifically,
|
||||
/// Poly1, Poly2, Poly3, etc. types conform
|
||||
/// to the Poly protocol. These types allow
|
||||
/// typesafe grouping of a number of
|
||||
/// disparate types under one roof for
|
||||
/// the purposes of JSON API compliant
|
||||
/// encoding or decoding.
|
||||
public typealias JSONPoly = Poly & PrimaryResource
|
||||
|
||||
public typealias PolyWrapped = Codable & Equatable
|
||||
|
||||
extension Poly0: PrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
throw JSONAPIEncodingError.illegalDecoding("Attempted to decode Poly0, which should represent a thing that is not expected to be found in a document.")
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
throw JSONAPIEncodingError.illegalEncoding("Attempted to encode Poly0, which should represent a thing that is not expected to be found in a document.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 1 type
|
||||
extension Poly1: PrimaryResource, MaybePrimaryResource where A: PolyWrapped {}
|
||||
|
||||
// MARK: - 2 types
|
||||
extension Poly2: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped {}
|
||||
|
||||
// MARK: - 3 types
|
||||
extension Poly3: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
||||
|
||||
// MARK: - 4 types
|
||||
extension Poly4: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
||||
|
||||
// MARK: - 5 types
|
||||
extension Poly5: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
||||
|
||||
// MARK: - 6 types
|
||||
extension Poly6: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
||||
|
||||
// MARK: - 7 types
|
||||
extension Poly7: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
||||
|
||||
// MARK: - 8 types
|
||||
extension Poly8: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
||||
|
||||
// MARK: - 9 types
|
||||
extension Poly9: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// APIDescription+Arbitrary.swift
|
||||
// JSONAPIArbitrary
|
||||
//
|
||||
// Created by Mathew Polzin on 1/21/19.
|
||||
//
|
||||
|
||||
import SwiftCheck
|
||||
import JSONAPI
|
||||
|
||||
extension APIDescription: Arbitrary where Meta: Arbitrary {
|
||||
public static var arbitrary: Gen<APIDescription<Meta>> {
|
||||
return Gen.compose { c in
|
||||
APIDescription(version: c.generate(),
|
||||
meta: c.generate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NoAPIDescription: Arbitrary {
|
||||
public static var arbitrary: Gen<NoAPIDescription> {
|
||||
return Gen.pure(.none)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Attribute+Arbitrary.swift
|
||||
// JSONAPIArbitrary
|
||||
//
|
||||
// Created by Mathew Polzin on 1/15/19.
|
||||
//
|
||||
|
||||
import SwiftCheck
|
||||
import JSONAPI
|
||||
|
||||
extension Attribute: Arbitrary where RawValue: Arbitrary {
|
||||
public static var arbitrary: Gen<Attribute<RawValue>> {
|
||||
return RawValue.arbitrary.map { .init(value: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot conform TransformedAttribute to Arbitrary here
|
||||
// because there is no way to guarantee that an arbitrary
|
||||
// RawValue will successfully transform or that an
|
||||
// arbitrary Value will successfully reverse-transform.
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// Document+Arbitrary.swift
|
||||
// JSONAPIArbitrary
|
||||
//
|
||||
// Created by Mathew Polzin on 1/21/19.
|
||||
//
|
||||
|
||||
import SwiftCheck
|
||||
import JSONAPI
|
||||
|
||||
extension Document.Body.Data: Arbitrary where PrimaryResourceBody: Arbitrary, IncludeType: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary {
|
||||
public static var arbitrary: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body.Data> {
|
||||
return Gen.compose { c in
|
||||
Document.Body.Data(primary: c.generate(),
|
||||
includes: c.generate(),
|
||||
meta: c.generate(),
|
||||
links: c.generate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body: Arbitrary where PrimaryResourceBody: Arbitrary, IncludeType: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary, Error: Arbitrary {
|
||||
public static var arbitrary: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body> {
|
||||
return Gen.one(of: [
|
||||
arbitraryData,
|
||||
arbitraryErrors
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body where PrimaryResourceBody: Arbitrary, IncludeType: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary {
|
||||
/// Arbitrary Document.Body with data (guaranteed to not
|
||||
/// be an error body).
|
||||
public static var arbitraryData: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body> {
|
||||
return Document.Body.Data.arbitrary.map(Document.Body.data)
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body where MetaType: Arbitrary, LinksType: Arbitrary, Error: Arbitrary {
|
||||
/// Arbitrary Document.Body with errors (guaranteed to not
|
||||
/// be a data body).
|
||||
public static var arbitraryErrors: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body> {
|
||||
return Gen.compose { c in
|
||||
Document.Body.errors(c.generate(),
|
||||
meta: c.generate(),
|
||||
links: c.generate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body where Error: Arbitrary {
|
||||
/// Arbitrary Document.Body with errors but no
|
||||
/// metadata or links (also guaranteed to not
|
||||
/// be a data body).
|
||||
public static var arbitraryErrorsWithoutMetaOrLinks: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body> {
|
||||
return Gen.compose { c in
|
||||
Document.Body.errors(c.generate(),
|
||||
meta: nil,
|
||||
links: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Arbitrary where PrimaryResourceBody: Arbitrary, IncludeType: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary, Error: Arbitrary, APIDescription: Arbitrary {
|
||||
public static var arbitrary: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>> {
|
||||
return Gen.one(of: [
|
||||
arbitraryData,
|
||||
arbitraryErrors
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Document where PrimaryResourceBody: Arbitrary, IncludeType: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary, APIDescription: Arbitrary {
|
||||
/// Arbitrary Document with data (guaranteed to not
|
||||
/// be an error body).
|
||||
public static var arbitraryData: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>> {
|
||||
return Gen.compose { c in
|
||||
Document(apiDescription: c.generate(),
|
||||
body: c.generate(),
|
||||
includes: c.generate(),
|
||||
meta: c.generate(),
|
||||
links: c.generate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document where MetaType: Arbitrary, LinksType: Arbitrary, Error: Arbitrary, APIDescription: Arbitrary {
|
||||
/// Arbitrary Document with errors (guaranteed to not
|
||||
/// be a data body).
|
||||
public static var arbitraryErrors: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>> {
|
||||
return Gen.compose { c in
|
||||
Document(apiDescription: c.generate(),
|
||||
errors: c.generate(),
|
||||
meta: c.generate(),
|
||||
links: c.generate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document where Error: Arbitrary, APIDescription: Arbitrary {
|
||||
/// Arbitrary Document with errors but no
|
||||
/// metadata or links (also guaranteed to not
|
||||
/// be a data body).
|
||||
public static var arbitraryErrors: Gen<Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>> {
|
||||
return Gen.compose { c in
|
||||
Document(apiDescription: c.generate(),
|
||||
errors: c.generate())
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user