Compare commits

..

31 Commits

Author SHA1 Message Date
Mathew Polzin 65144d27ae Merge pull request #10 from mattpolzin/feature/OpenAPISchema
Feature/open api schema
2019-01-21 11:50:02 -08:00
Mathew Polzin 1ec4b21c34 Add missing case in Sampleable generic OpenAPINode implementation. Add info on new auxiliary frameworks to the README. 2019-01-20 19:18:10 -08:00
Mathew Polzin 15e13e2cc7 Remove unused error and add a bit of code documentation 2019-01-20 18:54:37 -08:00
Mathew Polzin 1d8ceef317 Update README, add a bit more code documentation 2019-01-20 18:52:21 -08:00
Mathew Polzin e546614c15 merge with Arbitrary branch 2019-01-20 18:41:54 -08:00
Mathew Polzin 8939d637d4 Add placeholder test to arbitrary test target. 2019-01-20 18:34:24 -08:00
Mathew Polzin e3b005b89a Update README 2019-01-20 18:25:28 -08:00
Mathew Polzin 2de6580519 Add some tests around Attribute OpenAPI descriptions 2019-01-20 18:18:35 -08:00
Mathew Polzin 875d938b95 Add id and type properties to Entity OpenAPI Schema 2019-01-20 16:19:26 -08:00
Mathew Polzin 1ed34b00dc Add todos for Entity Id and type. Add example usage of OpenAPI entity schema support in Playground 2019-01-20 16:10:31 -08:00
Mathew Polzin 531a7f35bd Cleanup and a bit of documentation 2019-01-20 15:50:46 -08:00
Mathew Polzin dc42ec27fc Not crazy about how I got there, but now it is relatively easy to print arbitrary enum's allCases as the list of possible values in the format specced out by OpenAPI. 2019-01-20 15:39:54 -08:00
Mathew Polzin cf746e182f currently in a pretty broken state with support for enumerations being turned into allowed values via reflection. I think I am going to have to give up type safety if I want to use reflection and keep things open ended 2019-01-19 15:30:09 -08:00
Mathew Polzin 52d2e9819d Update README project status to include OpenAPI and Arbitrary work. 2019-01-15 09:40:49 -08:00
Mathew Polzin 81b78cd749 Add more Arbitrary conformances 2019-01-15 09:27:09 -08:00
Mathew Polzin 3a0ede2372 Get rid of OpenAPI namespace because a compiler bug was making it more hassle than it was worth. 2019-01-14 23:46:39 -08:00
Mathew Polzin cb04a01e71 Add some todos and delete an old file 2019-01-14 23:29:49 -08:00
Mathew Polzin 3c29244540 fill out some of the relationship tests 2019-01-14 23:14:25 -08:00
Mathew Polzin a9e1e49a79 Add test helpers for Open API test target 2019-01-14 21:18:49 -08:00
Mathew Polzin 9e6e713ad2 Add Codable for the parts of OpenAPI that are built so far. 2019-01-14 21:17:07 -08:00
Mathew Polzin 308f168a8c Trying to map out the Open API Schema Object using Swift enums 2019-01-13 23:24:02 -08:00
Mathew Polzin 8726bcc9fa Update all references to JSONAPITestLib to reflect renaming 2019-01-13 18:35:20 -08:00
Mathew Polzin 25bda795a7 Rename JSONAPITestLib to JSONAPITesting to fit with naming convention I am adopting going forward. Added OpenAPI JSON schema types. Added default schema types for a handful of Swift primitives. 2019-01-13 18:28:41 -08:00
Mathew Polzin 14a0256885 Add new targets 2019-01-13 17:27:10 -08:00
Mathew Polzin b2041e9bc8 Update to bleeding edge Poly library 2019-01-13 07:47:20 -08:00
Mathew Polzin fd9a626551 Ditch unused test constant. 2019-01-13 07:42:13 -08:00
Mathew Polzin 69bb40aa00 Move JSONAPITestLib tests into their own test target. 2019-01-13 07:16:46 -08:00
Mathew Polzin 060e0e3898 Move PolyWrapped out of Poly and into JSONAPI. 2019-01-12 21:51:51 -08:00
Mathew Polzin b374367234 Move Poly into its own library. 2019-01-12 21:30:20 -08:00
Mathew Polzin b363db30cd Add MIT license docuemnt 2019-01-12 18:41:04 -08:00
Mathew Polzin 6904416623 Begin making Poly more useful in a broader context than JSONAPI. 2019-01-12 17:08:46 -08:00
56 changed files with 2623 additions and 1324 deletions
@@ -0,0 +1,13 @@
//: [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(personSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
@@ -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
@@ -0,0 +1,16 @@
import Foundation
import JSONAPI
import JSONAPITesting // for the convenience of literal initialization
import JSONAPIOpenAPI
extension PersonDescription.Attributes: Sampleable {
public static var sample: PersonDescription.Attributes {
return .init(name: ["Abbie", "Eibba"], favoriteColor: "Blue")
}
}
extension PersonDescription.Relationships: Sampleable {
public static var sample: PersonDescription.Relationships {
return .init(friends: ["1", "2"], dogs: ["2"], home: "1")
}
}
+3
View File
@@ -3,5 +3,8 @@
<pages>
<page name='Test Library'/>
<page name='Usage'/>
<page name='Full Client &amp; Server Example'/>
<page name='Full Document Verbose Generation'/>
<page name='OpenAPI Documentation'/>
</pages>
</playground>
+7
View File
@@ -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
View File
@@ -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": "master",
"revision": "77f45b8963a51c02d71fc4075eba5cff47ff0d07",
"version": null
}
},
{
"package": "SwiftCheck",
"repositoryURL": "https://github.com/typelift/SwiftCheck.git",
"state": {
"branch": null,
"revision": "cf9958085b2ee1643e541e407c3233d1b76c18ff",
"version": "0.11.0"
}
}
]
},
"version": 1
+29 -5
View File
@@ -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", .branch("master")),
.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]
)
+134 -73
View File
@@ -8,59 +8,62 @@ See the JSON API Spec here: https://jsonapi.org/format/
: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
<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [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)
- [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)
- [JSONAPITestLib](#jsonapitestlib)
- [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)
- [JSONAPI+Testing](#jsonapitesting)
- [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 -->
@@ -92,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
- [ ] Arbitrary
- [ ] OpenAPI
- `included`
- [x] Encoding/Decoding
- [ ] Arbitrary
- [ ] OpenAPI
- `errors`
- [x] Encoding/Decoding
- [ ] Arbitrary
- [ ] OpenAPI
- `meta`
- [x] Encoding/Decoding
- [ ] Arbitrary
- [ ] OpenAPI
- `jsonapi` (i.e. API Information)
- [x] Encoding/Decoding
- [ ] Arbitrary
- [ ] OpenAPI
- `links`
- [x] Encoding/Decoding
- [ ] 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`)
@@ -124,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
### JSONAPI+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.
@@ -781,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 library does not offer full support of all `JSONAPI` types yet. The documentation will grow as the framework becomes more complete.
# 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.
+2
View File
@@ -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
+3 -1
View File
@@ -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: []) }
+1 -1
View File
@@ -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
@@ -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,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 extend TransformedAttribute 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,50 @@
//
// Entity+Arbitrary.swift
// JSONAPIArbitrary
//
// Created by Mathew Polzin on 1/14/19.
//
import SwiftCheck
import JSONAPI
extension NoMetadata: Arbitrary {
public static var arbitrary: Gen<NoMetadata> {
return Gen.pure(.none)
}
}
extension NoLinks: Arbitrary {
public static var arbitrary: Gen<NoLinks> {
return Gen.pure(.none)
}
}
extension NoAttributes: Arbitrary {
public static var arbitrary: Gen<NoAttributes> {
return Gen.pure(.none)
}
}
extension NoRelationships: Arbitrary {
public static var arbitrary: Gen<NoRelationships> {
return Gen.pure(.none)
}
}
// NOTE: Arbitrary conformance for MetaType, LinksType, Description.Attributes,
// and Description.Relationships must all be provided BY YOU for Entity to
// gain Arbitrary conformance (with the exception of NoMetadata, NoLinks,
// NoAttributes, and NoRelationships which all have Arbitrary conformance
// out of the box).
extension Entity: Arbitrary where MetaType: Arbitrary, LinksType: Arbitrary, Description.Attributes: Arbitrary, Description.Relationships: Arbitrary, EntityRawIdType: Arbitrary {
public static var arbitrary: Gen<Entity<Description, MetaType, LinksType, EntityRawIdType>> {
return Gen.compose { c in
Entity(id: c.generate(),
attributes: c.generate(),
relationships: c.generate(),
meta: c.generate(),
links: c.generate())
}
}
}
@@ -0,0 +1,21 @@
//
// Id+Arbitrary.swift
// JSONAPIArbitrary
//
// Created by Mathew Polzin on 1/14/19.
//
import SwiftCheck
import JSONAPI
extension Unidentified: Arbitrary {
public static var arbitrary: Gen<Unidentified> {
return Gen.pure(.init())
}
}
extension Id: Arbitrary where RawType: Arbitrary {
public static var arbitrary: Gen<Id<RawType, IdentifiableType>> {
return RawType.arbitrary.map { Id(rawValue: $0) }
}
}
@@ -0,0 +1,60 @@
//
// Relationship+Arbitrary.swift
// JSONAPIArbitrary
//
// Created by Mathew Polzin on 1/15/19.
//
import SwiftCheck
import JSONAPI
extension ToOneRelationship: Arbitrary where Identifiable.Identifier: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary {
public static var arbitrary: Gen<ToOneRelationship<Identifiable, MetaType, LinksType>> {
return Gen.compose { c in
return .init(id: c.generate(),
meta: c.generate(),
links: c.generate())
}
}
}
extension ToOneRelationship where MetaType: Arbitrary, LinksType: Arbitrary {
/// Create a generator of arbitrary ToOneRelationships that will all
/// point to one of the given entities. This allows you to create
/// arbitrary relationships that make sense in a broader context where
/// the relationship must actually point to another entity.
public static func arbitrary<E: EntityType>(givenEntities: [E]) -> Gen<ToOneRelationship<Identifiable, MetaType, LinksType>> where E.Id == Identifiable.Identifier {
return Gen.compose { c in
let idGen = Gen.fromElements(of: givenEntities).map { $0.id }
return .init(id: c.generate(using: idGen),
meta: c.generate(),
links: c.generate())
}
}
}
extension ToManyRelationship: Arbitrary where Relatable.Identifier: Arbitrary, MetaType: Arbitrary, LinksType: Arbitrary {
public static var arbitrary: Gen<ToManyRelationship<Relatable, MetaType, LinksType>> {
return Gen.compose { c in
return .init(ids: c.generate(),
meta: c.generate(),
links: c.generate())
}
}
}
extension ToManyRelationship where MetaType: Arbitrary, LinksType: Arbitrary {
/// Create a generator of arbitrary ToManyRelationships that will all
/// point to some number of the given entities. This allows you to create
/// arbitrary relationships that make sense in a broader context where
/// the relationship must actually point to other existing entities.
public static func arbitrary<E: EntityType>(givenEntities: [E]) -> Gen<ToManyRelationship<Relatable, MetaType, LinksType>> where E.Id == Relatable.Identifier {
return Gen.compose { c in
let idsGen = Gen.fromElements(of: givenEntities).map { $0.id }.proliferate
return .init(ids: c.generate(using: idsGen),
meta: c.generate(),
links: c.generate())
}
}
}
@@ -0,0 +1,156 @@
//
// JSONAPIOpenAPITypes.swift
// JSONAPIOpenAPI
//
// Created by Mathew Polzin on 1/13/19.
//
import JSONAPI
import AnyCodable
private protocol _Optional {}
extension Optional: _Optional {}
extension Attribute: OpenAPINodeType where RawValue: OpenAPINodeType {
static public func openAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.openAPINode().required {
return try RawValue.openAPINode().requiredNode().nullableNode()
}
return try RawValue.openAPINode()
}
}
extension Attribute: RawOpenAPINodeType where RawValue: RawRepresentable, RawValue.RawValue: OpenAPINodeType {
static public func rawOpenAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.RawValue.openAPINode().required {
return try RawValue.RawValue.openAPINode().requiredNode().nullableNode()
}
return try RawValue.RawValue.openAPINode()
}
}
extension Attribute: WrappedRawOpenAPIType where RawValue: RawOpenAPINodeType {
public static func wrappedOpenAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.rawOpenAPINode().required {
return try RawValue.rawOpenAPINode().requiredNode().nullableNode()
}
return try RawValue.rawOpenAPINode()
}
}
extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable, RawValue: Codable {
public static var allCases: [AnyCodable] {
return (try? allCases(from: Array(RawValue.allCases))) ?? []
}
}
extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseIterable {
public static var allCases: [AnyCodable] {
return RawValue.allCases
}
}
extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType {
static public func openAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.openAPINode().required {
return try RawValue.openAPINode().requiredNode().nullableNode()
}
return try RawValue.openAPINode()
}
}
extension RelationshipType {
static func relationshipNode(nullable: Bool) -> JSONNode {
let propertiesDict: [String: JSONNode] = [
"id": .string(.init(format: .generic,
required: true),
.init()),
"type": .string(.init(format: .generic,
required: true),
.init())
]
return .object(.init(format: .generic,
required: true,
nullable: nullable),
.init(properties: propertiesDict))
}
}
extension ToOneRelationship: OpenAPINodeType {
// TODO: const for json `type`
// TODO: metadata & links
static public func openAPINode() throws -> JSONNode {
let nullable = Identifiable.self is _Optional.Type
return .object(.init(format: .generic,
required: true),
.init(properties: [
"data": ToOneRelationship.relationshipNode(nullable: nullable)
]))
}
}
extension ToManyRelationship: OpenAPINodeType {
// TODO: const for json `type`
// TODO: metadata & links
static public func openAPINode() throws -> JSONNode {
return .object(.init(format: .generic,
required: true),
.init(properties: [
"data": .array(.init(format: .generic,
required: true),
.init(items: ToManyRelationship.relationshipNode(nullable: false)))
]))
}
}
extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
public static func openAPINode() throws -> JSONNode {
// TODO: metadata, links
let idNode = JSONNode.string(.init(format: .generic,
required: true),
.init())
let idProperty = ("id", idNode)
let typeNode = JSONNode.string(.init(format: .generic,
required: true),
.init())
let typeProperty = ("type", typeNode)
let attributesNode: JSONNode? = Description.Attributes.self == NoAttributes.self
? nil
: try Description.Attributes.genericObjectOpenAPINode()
let attributesProperty = attributesNode.map { ("attributes", $0) }
let relationshipsNode: JSONNode? = Description.Relationships.self == NoRelationships.self
? nil
: try Description.Relationships.genericObjectOpenAPINode()
let relationshipsProperty = relationshipsNode.map { ("relationships", $0) }
let propertiesDict = Dictionary([
idProperty,
typeProperty,
attributesProperty,
relationshipsProperty
].compactMap { $0 }) { _, value in value }
return .object(.init(format: .generic,
required: true),
.init(properties: propertiesDict))
}
}
@@ -0,0 +1,176 @@
//
// OpenAPITypes+Codable.swift
// JSONAPIOpenAPI
//
// Created by Mathew Polzin on 1/14/19.
//
extension JSONNode.Context: Encodable {
private enum CodingKeys: String, CodingKey {
case type
case format
case allowedValues = "enum"
case nullable
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(format.jsonType, forKey: .type)
if format != Format.unspecified {
try container.encode(format, forKey: .format)
}
if allowedValues != nil {
try container.encode(allowedValues, forKey: .allowedValues)
}
try container.encode(nullable, forKey: .nullable)
}
}
extension JSONNode.NumericContext: Encodable {
private enum CodingKeys: String, CodingKey {
case multipleOf
case maximum
case exclusiveMaximum
case minimum
case exclusiveMinimum
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if multipleOf != nil {
try container.encode(multipleOf, forKey: .multipleOf)
}
if maximum != nil {
try container.encode(maximum, forKey: .maximum)
}
if exclusiveMaximum != nil {
try container.encode(exclusiveMaximum, forKey: .exclusiveMaximum)
}
if minimum != nil {
try container.encode(minimum, forKey: .minimum)
}
if exclusiveMinimum != nil {
try container.encode(exclusiveMinimum, forKey: .exclusiveMinimum)
}
}
}
extension JSONNode.StringContext: Encodable {
private enum CodingKeys: String, CodingKey {
case maxLength
case minLength
case pattern
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if maxLength != nil {
try container.encode(maxLength, forKey: .maxLength)
}
try container.encode(minLength, forKey: .minLength)
if pattern != nil {
try container.encode(pattern, forKey: .pattern)
}
}
}
extension JSONNode.ArrayContext: Encodable {
private enum CodingKeys: String, CodingKey {
case items
case maxItems
case minItems
case uniqueItems
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(items, forKey: .items)
if maxItems != nil {
try container.encode(maxItems, forKey: .maxItems)
}
try container.encode(minItems, forKey: .minItems)
try container.encode(uniqueItems, forKey: .uniqueItems)
}
}
extension JSONNode.ObjectContext : Encodable{
private enum CodingKeys: String, CodingKey {
case maxProperties
case minProperties
case properties
case additionalProperties
case required
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if maxProperties != nil {
try container.encode(maxProperties, forKey: .maxProperties)
}
try container.encode(properties, forKey: .properties)
if additionalProperties != nil {
try container.encode(additionalProperties, forKey: .additionalProperties)
}
let required = properties.filter { (name, node) in
node.required
}.keys
try container.encode(Array(required), forKey: .required)
try container.encode(max(minProperties, required.count), forKey: .minProperties)
}
}
extension JSONNode: Encodable {
public func encode(to encoder: Encoder) throws {
switch self {
case .boolean(let context):
try context.encode(to: encoder)
case .object(let contextA as Encodable, let contextB as Encodable),
.array(let contextA as Encodable, let contextB as Encodable),
.number(let contextA as Encodable, let contextB as Encodable),
.integer(let contextA as Encodable, let contextB as Encodable),
.string(let contextA as Encodable, let contextB as Encodable):
try contextA.encode(to: encoder)
try contextB.encode(to: encoder)
case .allOf(let nodes):
// TODO
print("TODO")
case .oneOf(let nodes):
// TODO
print("TODO")
case .anyOf(let nodes):
// TODO
print("TODO")
case .not(let node):
// TODO
print("TODO")
}
}
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More