mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Merge pull request #10 from mattpolzin/feature/OpenAPISchema
Feature/open api schema
This commit is contained in:
@@ -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,5 +3,8 @@
|
||||
<pages>
|
||||
<page name='Test Library'/>
|
||||
<page name='Usage'/>
|
||||
<page name='Full Client & Server Example'/>
|
||||
<page name='Full Document Verbose Generation'/>
|
||||
<page name='OpenAPI Documentation'/>
|
||||
</pages>
|
||||
</playground>
|
||||
+28
-1
@@ -1,14 +1,41 @@
|
||||
{
|
||||
"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": "e03e896e23315525702334cfb552bb947d085ae5",
|
||||
"revision": "77f45b8963a51c02d71fc4075eba5cff47ff0d07",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftCheck",
|
||||
"repositoryURL": "https://github.com/typelift/SwiftCheck.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "cf9958085b2ee1643e541e407c3233d1b76c18ff",
|
||||
"version": "0.11.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+29
-9
@@ -10,25 +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/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: ["Poly"]),
|
||||
.target(
|
||||
name: "JSONAPITestLib",
|
||||
dependencies: ["JSONAPI"]),
|
||||
.target(
|
||||
name: "JSONAPITesting",
|
||||
dependencies: ["JSONAPI"]),
|
||||
.target(
|
||||
name: "JSONAPIArbitrary",
|
||||
dependencies: ["JSONAPI", "SwiftCheck"]),
|
||||
.target(
|
||||
name: "JSONAPIOpenAPI",
|
||||
dependencies: ["JSONAPI", "AnyCodable", "JSONAPIArbitrary"]),
|
||||
.testTarget(
|
||||
name: "JSONAPITests",
|
||||
dependencies: ["JSONAPI", "JSONAPITestLib"]),
|
||||
dependencies: ["JSONAPI", "JSONAPITesting"]),
|
||||
.testTarget(
|
||||
name: "JSONAPITestLibTests",
|
||||
dependencies: ["JSONAPI", "JSONAPITestLib"])
|
||||
name: "JSONAPITestingTests",
|
||||
dependencies: ["JSONAPI", "JSONAPITesting"]),
|
||||
.testTarget(
|
||||
name: "JSONAPIArbitraryTests",
|
||||
dependencies: ["JSONAPI", "SwiftCheck", "JSONAPIArbitrary"]),
|
||||
.testTarget(
|
||||
name: "JSONAPIOpenAPITests",
|
||||
dependencies: ["JSONAPI", "JSONAPIOpenAPI"])
|
||||
],
|
||||
swiftLanguageVersions: [.v4_2]
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Optional+ZipWith.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 1/19/19.
|
||||
//
|
||||
|
||||
func zip<X, Y, Z>(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? {
|
||||
return left.flatMap { lft in right.map { rght in fn(lft, rght) }}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Sampleable.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 1/15/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
import AnyCodable
|
||||
|
||||
/// A Sampleable type can provide a sample value.
|
||||
/// This is useful for reflection.
|
||||
public protocol Sampleable {
|
||||
/// Get a sample value of type Self. This can be the
|
||||
/// same value every time, or it can be an arbitrarily random
|
||||
/// value each time.
|
||||
static var sample: Self { get }
|
||||
}
|
||||
|
||||
extension Sampleable {
|
||||
public static func genericObjectOpenAPINode() throws -> JSONNode {
|
||||
let mirror = Mirror(reflecting: Self.sample)
|
||||
let properties: [(String, JSONNode)] = try mirror.children.compactMap { child in
|
||||
|
||||
// see if we can enumerate the possible values
|
||||
let maybeAllCases: [AnyCodable]? = {
|
||||
switch type(of: child.value) {
|
||||
case let valType as AnyJSONCaseIterable.Type:
|
||||
return valType.allCases
|
||||
case let valType as AnyWrappedJSONCaseIterable.Type:
|
||||
return valType.allCases
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
// try to snag an OpenAPI Node
|
||||
let maybeOpenAPINode: JSONNode? = try {
|
||||
switch type(of: child.value) {
|
||||
case let valType as OpenAPINodeType.Type:
|
||||
return try valType.openAPINode()
|
||||
|
||||
case let valType as RawOpenAPINodeType.Type:
|
||||
return try valType.rawOpenAPINode()
|
||||
|
||||
case let valType as WrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
|
||||
case let valType as DoubleWrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
// put it all together
|
||||
let newNode: JSONNode?
|
||||
if let allCases = maybeAllCases,
|
||||
let openAPINode = maybeOpenAPINode {
|
||||
newNode = try openAPINode.with(allowedValues: allCases)
|
||||
} else {
|
||||
newNode = maybeOpenAPINode
|
||||
}
|
||||
|
||||
return zip(child.label, newNode) { ($0, $1) }
|
||||
}
|
||||
|
||||
// There should not be any duplication of keys since these are
|
||||
// property names, but rather than risk runtime exception, we just
|
||||
// fail to the newer value arbitrarily
|
||||
let propertiesDict = Dictionary(properties) { _, value2 in value2 }
|
||||
|
||||
return .object(.init(format: .generic,
|
||||
required: true),
|
||||
.init(properties: propertiesDict))
|
||||
}
|
||||
}
|
||||
|
||||
extension NoAttributes: Sampleable {
|
||||
public static var sample: NoAttributes {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoRelationships: Sampleable {
|
||||
public static var sample: NoRelationships {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoMetadata: Sampleable {
|
||||
public static var sample: NoMetadata {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoLinks: Sampleable {
|
||||
public static var sample: NoLinks {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// PrimitiveTypes.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 01/13/19.
|
||||
//
|
||||
|
||||
import AnyCodable
|
||||
|
||||
/**
|
||||
|
||||
Notable omissions in this library's default offerings:
|
||||
|
||||
Base 64 encoded characters:
|
||||
.string(.byte)
|
||||
|
||||
Any sequence of octets:
|
||||
.string(.binary)
|
||||
|
||||
RFC3339 full-date:
|
||||
.string(.date)
|
||||
|
||||
RFC3339 date-time:
|
||||
.string(.dateTime)
|
||||
|
||||
A hint to UIs to obscure input:
|
||||
.string(.password)
|
||||
|
||||
Any object:
|
||||
.object(.generic)
|
||||
|
||||
**/
|
||||
|
||||
extension Optional: OpenAPINodeType where Wrapped: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return try Wrapped.openAPINode().optionalNode()
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: RawOpenAPINodeType where Wrapped: RawRepresentable, Wrapped.RawValue: OpenAPINodeType {
|
||||
static public func rawOpenAPINode() throws -> JSONNode {
|
||||
return try Wrapped.RawValue.openAPINode().optionalNode()
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: WrappedRawOpenAPIType where Wrapped: RawOpenAPINodeType {
|
||||
static public func wrappedOpenAPINode() throws -> JSONNode {
|
||||
return try Wrapped.rawOpenAPINode().optionalNode()
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: DoubleWrappedRawOpenAPIType where Wrapped: WrappedRawOpenAPIType {
|
||||
static public func wrappedOpenAPINode() throws -> JSONNode {
|
||||
return try Wrapped.wrappedOpenAPINode().optionalNode()
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Codable {
|
||||
public static var allCases: [AnyCodable] {
|
||||
return (try? allCases(from: Array(Wrapped.allCases))) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
extension String: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .string(.init(format: .generic,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
|
||||
extension Bool: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .boolean(.init(format: .generic,
|
||||
required: true))
|
||||
}
|
||||
}
|
||||
|
||||
extension Array: OpenAPINodeType where Element: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .array(.init(format: .generic,
|
||||
required: true),
|
||||
.init(items: try Element.openAPINode()))
|
||||
}
|
||||
}
|
||||
|
||||
extension Double: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .number(.init(format: .double,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
|
||||
extension Float: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .number(.init(format: .float,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .integer(.init(format: .generic,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
|
||||
extension Int32: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .integer(.init(format: .int32,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
|
||||
extension Int64: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
return .integer(.init(format: .int64,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// EntityCheck.swift
|
||||
// JSONAPITestLib
|
||||
// JSONAPITesting
|
||||
//
|
||||
// Created by Mathew Polzin on 11/27/18.
|
||||
//
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user