diff --git a/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..c1b6cc9
--- /dev/null
+++ b/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift
@@ -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")
diff --git a/JSONAPI.playground/Pages/Test Library.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Test Library.xcplaygroundpage/Contents.swift
index a857014..fa71086 100644
--- a/JSONAPI.playground/Pages/Test Library.xcplaygroundpage/Contents.swift
+++ b/JSONAPI.playground/Pages/Test Library.xcplaygroundpage/Contents.swift
@@ -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
diff --git a/JSONAPI.playground/Sources/OpenAPISupport.swift b/JSONAPI.playground/Sources/OpenAPISupport.swift
new file mode 100644
index 0000000..5b7e895
--- /dev/null
+++ b/JSONAPI.playground/Sources/OpenAPISupport.swift
@@ -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")
+ }
+}
diff --git a/JSONAPI.playground/contents.xcplayground b/JSONAPI.playground/contents.xcplayground
index 8df8fa3..a1ec109 100644
--- a/JSONAPI.playground/contents.xcplayground
+++ b/JSONAPI.playground/contents.xcplayground
@@ -3,5 +3,8 @@
+
+
+
\ No newline at end of file
diff --git a/Package.resolved b/Package.resolved
index e498677..69dfbfa 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -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"
+ }
}
]
},
diff --git a/Package.swift b/Package.swift
index 2797017..3622d82 100644
--- a/Package.swift
+++ b/Package.swift
@@ -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]
)
diff --git a/README.md b/README.md
index 6002dfa..a27f196 100644
--- a/README.md
+++ b/README.md
@@ -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
-
+
-- [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)
@@ -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.
diff --git a/Sources/JSONAPI/Resource/Attribute.swift b/Sources/JSONAPI/Resource/Attribute.swift
index 670dca5..a43c368 100644
--- a/Sources/JSONAPI/Resource/Attribute.swift
+++ b/Sources/JSONAPI/Resource/Attribute.swift
@@ -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: AttributeType where Transformer.From == RawValue {
- let rawValue: RawValue
+ public let rawValue: RawValue
public let value: Transformer.To
diff --git a/Sources/JSONAPIArbitrary/Attribute+Arbitrary.swift b/Sources/JSONAPIArbitrary/Attribute+Arbitrary.swift
new file mode 100644
index 0000000..5c3ddb5
--- /dev/null
+++ b/Sources/JSONAPIArbitrary/Attribute+Arbitrary.swift
@@ -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> {
+ 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.
diff --git a/Sources/JSONAPIArbitrary/Entity+Arbitrary.swift b/Sources/JSONAPIArbitrary/Entity+Arbitrary.swift
new file mode 100644
index 0000000..ac22b5f
--- /dev/null
+++ b/Sources/JSONAPIArbitrary/Entity+Arbitrary.swift
@@ -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 {
+ return Gen.pure(.none)
+ }
+}
+
+extension NoLinks: Arbitrary {
+ public static var arbitrary: Gen {
+ return Gen.pure(.none)
+ }
+}
+
+extension NoAttributes: Arbitrary {
+ public static var arbitrary: Gen {
+ return Gen.pure(.none)
+ }
+}
+
+extension NoRelationships: Arbitrary {
+ public static var arbitrary: Gen {
+ 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> {
+ return Gen.compose { c in
+ Entity(id: c.generate(),
+ attributes: c.generate(),
+ relationships: c.generate(),
+ meta: c.generate(),
+ links: c.generate())
+ }
+ }
+}
diff --git a/Sources/JSONAPIArbitrary/Id+Arbitrary.swift b/Sources/JSONAPIArbitrary/Id+Arbitrary.swift
new file mode 100644
index 0000000..fbf6c32
--- /dev/null
+++ b/Sources/JSONAPIArbitrary/Id+Arbitrary.swift
@@ -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 {
+ return Gen.pure(.init())
+ }
+}
+
+extension Id: Arbitrary where RawType: Arbitrary {
+ public static var arbitrary: Gen> {
+ return RawType.arbitrary.map { Id(rawValue: $0) }
+ }
+}
diff --git a/Sources/JSONAPIArbitrary/Relationship+Arbitrary.swift b/Sources/JSONAPIArbitrary/Relationship+Arbitrary.swift
new file mode 100644
index 0000000..a1a8255
--- /dev/null
+++ b/Sources/JSONAPIArbitrary/Relationship+Arbitrary.swift
@@ -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> {
+ 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(givenEntities: [E]) -> Gen> 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> {
+ 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(givenEntities: [E]) -> Gen> 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())
+ }
+ }
+}
diff --git a/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift b/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift
new file mode 100644
index 0000000..6afd065
--- /dev/null
+++ b/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift
@@ -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))
+ }
+}
diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift
new file mode 100644
index 0000000..7b04957
--- /dev/null
+++ b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift
@@ -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")
+ }
+ }
+}
diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift
new file mode 100644
index 0000000..79bf009
--- /dev/null
+++ b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift
@@ -0,0 +1,512 @@
+//
+// OpenAPITypes.swift
+// JSONAPIOpenAPI
+//
+// Created by Mathew Polzin on 1/13/19.
+//
+
+import AnyCodable
+import Foundation
+
+// MARK: Node (i.e. schema) Protocols
+
+/// Anything conforming to `OpenAPINodeType` can provide an
+/// OpenAPI schema representing itself.
+public protocol OpenAPINodeType {
+ static func openAPINode() throws -> JSONNode
+}
+
+/// Anything conforming to `RawOpenAPINodeType` can provide an
+/// OpenAPI schema representing itself. This second protocol is
+/// necessary so that one type can conditionally provide a
+/// schema and then (under different conditions) provide a
+/// different schema. The "different" conditions have to do
+/// with Raw Representability, hence the name of this protocol.
+public protocol RawOpenAPINodeType {
+ static func rawOpenAPINode() throws -> JSONNode
+}
+
+/// Anything conforming to `RawOpenAPINodeType` can provide an
+/// OpenAPI schema representing itself. This third protocol is
+/// necessary so that one type can conditionally provide a
+/// schema and then (under different conditions) provide a
+/// different schema. The "different" conditions have to do
+/// with Optionality, hence the name of this protocol.
+public protocol WrappedRawOpenAPIType {
+ static func wrappedOpenAPINode() throws -> JSONNode
+}
+
+/// Anything conforming to `RawOpenAPINodeType` can provide an
+/// OpenAPI schema representing itself. This third protocol is
+/// necessary so that one type can conditionally provide a
+/// schema and then (under different conditions) provide a
+/// different schema. The "different" conditions have to do
+/// with Optionality, hence the name of this protocol.
+public protocol DoubleWrappedRawOpenAPIType {
+ // NOTE: This is definitely a rabbit hole... hopefully I
+ // will realize I've been missing something obvious
+ // and dig my way back out at some point...
+ static func wrappedOpenAPINode() throws -> JSONNode
+}
+
+/// Anything conforming to `AnyJSONCaseIterable` can provide a
+/// list of its possible values.
+public protocol AnyJSONCaseIterable {
+ static var allCases: [AnyCodable] { get }
+}
+
+extension AnyJSONCaseIterable {
+ /// Given an array of Codable values, retrieve an array of AnyCodables.
+ static func allCases(from input: [T]) throws -> [AnyCodable] {
+ if let alreadyGoodToGo = input as? [AnyCodable] {
+ return alreadyGoodToGo
+ }
+
+ // The following is messy, but it does get us the intended result:
+ // Given any array of things that can be encoded, we want
+ // to map to an array of AnyCodable so we can store later. We need to
+ // muck with JSONSerialization because something like an `enum` may
+ // very well be encoded as a string, and therefore representable
+ // by AnyCodable, but AnyCodable wants it to actually BE a String
+ // upon initialization.
+
+ guard let arrayOfCodables = try JSONSerialization.jsonObject(with: JSONEncoder().encode(input), options: []) as? [Any] else {
+ throw OpenAPICodableError.allCasesArrayNotCodable
+ }
+ return arrayOfCodables.map(AnyCodable.init)
+ }
+}
+
+/// Anything conforming to `AnyJSONCaseIterable` can provide a
+/// list of its possible values. This second protocol is
+/// necessary so that one type can conditionally provide a
+/// list of possible values and then (under different conditions)
+/// provide a different list of possible values.
+/// The "different" conditions have to do
+/// with Optionality, hence the name of this protocol.
+public protocol AnyWrappedJSONCaseIterable {
+ static var allCases: [AnyCodable] { get }
+}
+
+public protocol SwiftTyped {
+ associatedtype SwiftType: Codable, Equatable
+}
+
+public protocol OpenAPIFormat: SwiftTyped, Codable, Equatable {
+ static var unspecified: Self { get }
+
+ var jsonType: JSONType { get }
+}
+
+public protocol JSONNodeContext {
+ var required: Bool { get }
+}
+
+public enum JSONType: String, Codable {
+ case boolean = "boolean"
+ case object = "object"
+ case array = "array"
+ case number = "number"
+ case integer = "integer"
+ case string = "string"
+}
+
+public enum JSONTypeFormat: Equatable {
+ case boolean(BooleanFormat)
+ case object(ObjectFormat)
+ case array(ArrayFormat)
+ case number(NumberFormat)
+ case integer(IntegerFormat)
+ case string(StringFormat)
+
+ public enum BooleanFormat: String, Equatable, OpenAPIFormat {
+ case generic = ""
+
+ public typealias SwiftType = Bool
+
+ public static var unspecified: BooleanFormat {
+ return .generic
+ }
+
+ public var jsonType: JSONType {
+ return .boolean
+ }
+ }
+
+ public enum ObjectFormat: String, Equatable, OpenAPIFormat {
+ case generic = ""
+
+ public typealias SwiftType = AnyCodable
+
+ public static var unspecified: ObjectFormat {
+ return .generic
+ }
+
+ public var jsonType: JSONType {
+ return .object
+ }
+ }
+
+ public enum ArrayFormat: String, Equatable, OpenAPIFormat {
+ case generic = ""
+
+ public typealias SwiftType = [AnyCodable]
+
+ public static var unspecified: ArrayFormat {
+ return .generic
+ }
+
+ public var jsonType: JSONType {
+ return .array
+ }
+ }
+
+ public enum NumberFormat: String, Equatable, OpenAPIFormat {
+ case generic = ""
+ case float = "float"
+ case double = "double"
+
+ public typealias SwiftType = Double
+
+ public static var unspecified: NumberFormat {
+ return .generic
+ }
+
+ public var jsonType: JSONType {
+ return .number
+ }
+ }
+
+ public enum IntegerFormat: String, Equatable, OpenAPIFormat {
+ case generic = ""
+ case int32 = "int32"
+ case int64 = "int64"
+
+ public typealias SwiftType = Int
+
+ public static var unspecified: IntegerFormat {
+ return .generic
+ }
+
+ public var jsonType: JSONType {
+ return .integer
+ }
+ }
+
+ public enum StringFormat: String, Equatable, OpenAPIFormat {
+ case generic = ""
+ case byte = "byte"
+ case binary = "binary"
+ case date = "date"
+ case dateTime = "date-time"
+ case password = "password"
+
+ public typealias SwiftType = String
+
+ public static var unspecified: StringFormat {
+ return .generic
+ }
+
+ public var jsonType: JSONType {
+ return .string
+ }
+ }
+
+ public var jsonType: JSONType {
+ switch self {
+ case .boolean:
+ return .boolean
+ case .object:
+ return .object
+ case .array:
+ return .array
+ case .number:
+ return .number
+ case .integer:
+ return .integer
+ case .string:
+ return .string
+ }
+ }
+}
+
+/// A JSON Node is what OpenAPI calls a
+/// "Schema Object"
+public enum JSONNode: Equatable {
+ case boolean(Context)
+ indirect case object(Context, ObjectContext)
+ indirect case array(Context, ArrayContext)
+ case number(Context, NumericContext)
+ case integer(Context, NumericContext)
+ case string(Context, StringContext)
+ indirect case allOf([JSONNode])
+ indirect case oneOf([JSONNode])
+ indirect case anyOf([JSONNode])
+ indirect case not(JSONNode)
+
+ public struct Context: JSONNodeContext, Equatable {
+ public let format: Format
+ public let required: Bool
+ public let nullable: Bool
+
+ /// The OpenAPI spec calls this "enum"
+ /// If not specified, it is assumed that any
+ /// value of the given format is allowed.
+ /// NOTE: I would like the array of allowed
+ /// values to have the type `Format.SwiftType`
+ /// but this is not tractable because I also
+ /// want to be able to automatically turn any
+ /// Swift type that will get _encoded as
+ /// something compatible with_ `Format.SwiftType`
+ /// into an allowed value.
+ public let allowedValues: [AnyCodable]?
+
+ public init(format: Format,
+ required: Bool,
+ nullable: Bool = false,
+ allowedValues: [AnyCodable]? = nil) {
+ self.format = format
+ self.required = required
+ self.nullable = nullable
+ self.allowedValues = allowedValues
+ }
+
+ /// Return the optional version of this Context
+ public func optionalContext() -> Context {
+ return .init(format: format,
+ required: false,
+ nullable: nullable,
+ allowedValues: allowedValues)
+ }
+
+ /// Return the required version of this context
+ public func requiredContext() -> Context {
+ return .init(format: format,
+ required: true,
+ nullable: nullable,
+ allowedValues: allowedValues)
+ }
+
+ /// Return the nullable version of this context
+ public func nullableContext() -> Context {
+ return .init(format: format,
+ required: required,
+ nullable: true,
+ allowedValues: allowedValues)
+ }
+
+ /// Return this context with the given list of possible values
+ public func with(allowedValues: [AnyCodable]?) -> Context {
+ return .init(format: format,
+ required: required,
+ nullable: nullable,
+ allowedValues: allowedValues)
+ }
+ }
+
+ public struct NumericContext: Equatable {
+ /// A numeric instance is valid only if division by this keyword's value results in an integer. Defaults to nil.
+ public let multipleOf: Double?
+ public let maximum: Double?
+ public let exclusiveMaximum: Double?
+ public let minimum: Double?
+ public let exclusiveMinimum: Double?
+
+ public init(multipleOf: Double? = nil,
+ maximum: Double? = nil,
+ exclusiveMaximum: Double? = nil,
+ minimum: Double? = nil,
+ exclusiveMinimum: Double? = nil) {
+ self.multipleOf = multipleOf
+ self.maximum = maximum
+ self.exclusiveMaximum = exclusiveMaximum
+ self.minimum = minimum
+ self.exclusiveMinimum = exclusiveMinimum
+ }
+ }
+
+ public struct StringContext: Equatable {
+ public let maxLength: Int?
+ public let minLength: Int
+
+ /// Regular expression
+ public let pattern: String?
+
+ public init(maxLength: Int? = nil,
+ minLength: Int = 0,
+ pattern: String? = nil) {
+ self.maxLength = maxLength
+ self.minLength = minLength
+ self.pattern = pattern
+ }
+ }
+
+ public struct ArrayContext: Equatable {
+ /// A JSON Type Node that describes
+ /// the type of each element in the array.
+ public let items: JSONNode
+
+ /// Maximum number of items in array.
+ public let maxItems: Int?
+
+ /// Minimum number of items in array.
+ /// Defaults to 0.
+ public let minItems: Int
+
+ /// Setting to true indicates all
+ /// elements of the array are expected
+ /// to be unique. Defaults to false.
+ public let uniqueItems: Bool
+
+ public init(items: JSONNode,
+ maxItems: Int? = nil,
+ minItems: Int = 0,
+ uniqueItems: Bool = false) {
+ self.items = items
+ self.maxItems = maxItems
+ self.minItems = minItems
+ self.uniqueItems = uniqueItems
+ }
+ }
+
+ public struct ObjectContext: Equatable {
+ public let maxProperties: Int?
+ public let minProperties: Int
+ public let properties: [String: JSONNode]
+ public let additionalProperties: [String: JSONNode]?
+
+ /*
+ // NOTE that an object's required properties
+ // array is determined by looking at its properties'
+ // required Bool.
+ public let required: [String]
+ */
+
+ public init(properties: [String: JSONNode],
+ additionalProperties: [String: JSONNode]? = nil,
+ maxProperties: Int? = nil,
+ minProperties: Int = 0) {
+ self.properties = properties
+ self.additionalProperties = additionalProperties
+ self.maxProperties = maxProperties
+ self.minProperties = minProperties
+ }
+ }
+
+ public var jsonTypeFormat: JSONTypeFormat? {
+ switch self {
+ case .boolean(let context):
+ return .boolean(context.format)
+ case .object(let context, _):
+ return .object(context.format)
+ case .array(let context, _):
+ return .array(context.format)
+ case .number(let context, _):
+ return .number(context.format)
+ case .integer(let context, _):
+ return .integer(context.format)
+ case .string(let context, _):
+ return .string(context.format)
+ case .allOf, .oneOf, .anyOf, .not:
+ return nil
+ }
+ }
+
+ public var required: Bool {
+ switch self {
+ case .boolean(let contextA as JSONNodeContext),
+ .object(let contextA as JSONNodeContext, _),
+ .array(let contextA as JSONNodeContext, _),
+ .number(let contextA as JSONNodeContext, _),
+ .integer(let contextA as JSONNodeContext, _),
+ .string(let contextA as JSONNodeContext, _):
+ return contextA.required
+ case .allOf, .oneOf, .anyOf, .not:
+ return true
+ }
+ }
+
+ /// Return the optional version of this JSONNode
+ public func optionalNode() -> JSONNode {
+ switch self {
+ case .boolean(let context):
+ return .boolean(context.optionalContext())
+ case .object(let contextA, let contextB):
+ return .object(contextA.optionalContext(), contextB)
+ case .array(let contextA, let contextB):
+ return .array(contextA.optionalContext(), contextB)
+ case .number(let context, let contextB):
+ return .number(context.optionalContext(), contextB)
+ case .integer(let context, let contextB):
+ return .integer(context.optionalContext(), contextB)
+ case .string(let context, let contextB):
+ return .string(context.optionalContext(), contextB)
+ case .allOf, .oneOf, .anyOf, .not:
+ return self
+ }
+ }
+
+ /// Return the required version of this JSONNode
+ public func requiredNode() -> JSONNode {
+ switch self {
+ case .boolean(let context):
+ return .boolean(context.requiredContext())
+ case .object(let contextA, let contextB):
+ return .object(contextA.requiredContext(), contextB)
+ case .array(let contextA, let contextB):
+ return .array(contextA.requiredContext(), contextB)
+ case .number(let context, let contextB):
+ return .number(context.requiredContext(), contextB)
+ case .integer(let context, let contextB):
+ return .integer(context.requiredContext(), contextB)
+ case .string(let context, let contextB):
+ return .string(context.requiredContext(), contextB)
+ case .allOf, .oneOf, .anyOf, .not:
+ return self
+ }
+ }
+
+ /// Return the nullable version of this JSONNode
+ public func nullableNode() -> JSONNode {
+ switch self {
+ case .boolean(let context):
+ return .boolean(context.nullableContext())
+ case .object(let contextA, let contextB):
+ return .object(contextA.nullableContext(), contextB)
+ case .array(let contextA, let contextB):
+ return .array(contextA.nullableContext(), contextB)
+ case .number(let context, let contextB):
+ return .number(context.nullableContext(), contextB)
+ case .integer(let context, let contextB):
+ return .integer(context.nullableContext(), contextB)
+ case .string(let context, let contextB):
+ return .string(context.nullableContext(), contextB)
+ case .allOf, .oneOf, .anyOf, .not:
+ return self
+ }
+ }
+
+ public func with(allowedValues: [AnyCodable]) throws -> JSONNode {
+
+ switch self {
+ case .boolean(let context):
+ return .boolean(context.with(allowedValues: allowedValues))
+ case .object(let contextA, let contextB):
+ return .object(contextA.with(allowedValues: allowedValues), contextB)
+ case .array(let contextA, let contextB):
+ return .array(contextA.with(allowedValues: allowedValues), contextB)
+ case .number(let context, let contextB):
+ return .number(context.with(allowedValues: allowedValues), contextB)
+ case .integer(let context, let contextB):
+ return .integer(context.with(allowedValues: allowedValues), contextB)
+ case .string(let context, let contextB):
+ return .string(context.with(allowedValues: allowedValues), contextB)
+ case .allOf, .oneOf, .anyOf, .not:
+ return self
+ }
+ }
+}
+
+public enum OpenAPICodableError: Swift.Error {
+ case allCasesArrayNotCodable
+}
diff --git a/Sources/JSONAPIOpenAPI/Optional+ZipWith.swift b/Sources/JSONAPIOpenAPI/Optional+ZipWith.swift
new file mode 100644
index 0000000..cbe113e
--- /dev/null
+++ b/Sources/JSONAPIOpenAPI/Optional+ZipWith.swift
@@ -0,0 +1,10 @@
+//
+// Optional+ZipWith.swift
+// JSONAPIOpenAPI
+//
+// Created by Mathew Polzin on 1/19/19.
+//
+
+func zip(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? {
+ return left.flatMap { lft in right.map { rght in fn(lft, rght) }}
+}
diff --git a/Sources/JSONAPIOpenAPI/Sampleable.swift b/Sources/JSONAPIOpenAPI/Sampleable.swift
new file mode 100644
index 0000000..328080e
--- /dev/null
+++ b/Sources/JSONAPIOpenAPI/Sampleable.swift
@@ -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
+ }
+}
diff --git a/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift b/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift
new file mode 100644
index 0000000..ae03f6a
--- /dev/null
+++ b/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift
@@ -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())
+ }
+}
diff --git a/Sources/JSONAPITestLib/Attribute+Literal.swift b/Sources/JSONAPITesting/Attribute+Literal.swift
similarity index 100%
rename from Sources/JSONAPITestLib/Attribute+Literal.swift
rename to Sources/JSONAPITesting/Attribute+Literal.swift
diff --git a/Sources/JSONAPITestLib/EntityCheck.swift b/Sources/JSONAPITesting/EntityCheck.swift
similarity index 99%
rename from Sources/JSONAPITestLib/EntityCheck.swift
rename to Sources/JSONAPITesting/EntityCheck.swift
index 2a91e31..1b7bd1f 100644
--- a/Sources/JSONAPITestLib/EntityCheck.swift
+++ b/Sources/JSONAPITesting/EntityCheck.swift
@@ -1,6 +1,6 @@
//
// EntityCheck.swift
-// JSONAPITestLib
+// JSONAPITesting
//
// Created by Mathew Polzin on 11/27/18.
//
diff --git a/Sources/JSONAPITestLib/Id+Literal.swift b/Sources/JSONAPITesting/Id+Literal.swift
similarity index 100%
rename from Sources/JSONAPITestLib/Id+Literal.swift
rename to Sources/JSONAPITesting/Id+Literal.swift
diff --git a/Sources/JSONAPITestLib/Optional+Literal.swift b/Sources/JSONAPITesting/Optional+Literal.swift
similarity index 98%
rename from Sources/JSONAPITestLib/Optional+Literal.swift
rename to Sources/JSONAPITesting/Optional+Literal.swift
index 7c9087f..e0d81b5 100644
--- a/Sources/JSONAPITestLib/Optional+Literal.swift
+++ b/Sources/JSONAPITesting/Optional+Literal.swift
@@ -1,6 +1,6 @@
//
// Optional+Literal.swift
-// JSONAPITestLib
+// JSONAPITesting
//
// Created by Mathew Polzin on 11/29/18.
//
diff --git a/Sources/JSONAPITestLib/Relationship+Literal.swift b/Sources/JSONAPITesting/Relationship+Literal.swift
similarity index 99%
rename from Sources/JSONAPITestLib/Relationship+Literal.swift
rename to Sources/JSONAPITesting/Relationship+Literal.swift
index 61c4c92..9af692c 100644
--- a/Sources/JSONAPITestLib/Relationship+Literal.swift
+++ b/Sources/JSONAPITesting/Relationship+Literal.swift
@@ -1,6 +1,6 @@
//
// Relationship+Literal.swift
-// JSONAPITestLib
+// JSONAPITesting
//
// Created by Mathew Polzin on 11/27/18.
//
diff --git a/Tests/JSONAPIArbitraryTests/PlaceholderTests.swift b/Tests/JSONAPIArbitraryTests/PlaceholderTests.swift
new file mode 100644
index 0000000..61ceaa0
--- /dev/null
+++ b/Tests/JSONAPIArbitraryTests/PlaceholderTests.swift
@@ -0,0 +1,15 @@
+//
+// PlaceholderTests.swift
+// JSONAPIArbitraryTests
+//
+// Created by Mathew Polzin on 12/7/18.
+//
+
+import XCTest
+import JSONAPIArbitrary
+
+class PlaceholderTests: XCTestCase {
+ func test_Placeholder() {
+
+ }
+}
diff --git a/Tests/JSONAPIArbitraryTests/XCTestManifests.swift b/Tests/JSONAPIArbitraryTests/XCTestManifests.swift
new file mode 100644
index 0000000..acb4b36
--- /dev/null
+++ b/Tests/JSONAPIArbitraryTests/XCTestManifests.swift
@@ -0,0 +1,15 @@
+import XCTest
+
+extension PlaceholderTests {
+ static let __allTests = [
+ ("test_Placeholder", test_Placeholder),
+ ]
+}
+
+#if !os(macOS)
+public func __allTests() -> [XCTestCaseEntry] {
+ return [
+ testCase(PlaceholderTests.__allTests),
+ ]
+}
+#endif
diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift
new file mode 100644
index 0000000..726f99a
--- /dev/null
+++ b/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift
@@ -0,0 +1,513 @@
+//
+// JSONAPIAttributeOpenAPITests.swift
+// JSONAPIOpenAPITests
+//
+// Created by Mathew Polzin on 1/20/19.
+//
+
+import XCTest
+import JSONAPI
+import JSONAPIOpenAPI
+import AnyCodable
+
+class JSONAPIAttributeOpenAPITests: XCTestCase {
+}
+
+// MARK: - Boolean
+extension JSONAPIAttributeOpenAPITests {
+ func test_BooleanAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .boolean(.generic))
+
+ guard case .boolean(let contextA) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+ }
+
+ func test_NullableBooleanAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .boolean(.generic))
+
+ guard case .boolean(let contextA) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+ }
+
+ func test_OptionalBooleanAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .boolean(.generic))
+
+ guard case .boolean(let contextA) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+ }
+
+ func test_OptionalNullableBooleanAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .boolean(.generic))
+
+ guard case .boolean(let contextA) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: true,
+ allowedValues: nil))
+ }
+}
+
+// MARK: - Array of Strings
+extension JSONAPIAttributeOpenAPITests {
+ func test_Arrayttribute() {
+ let node = try! Attribute<[String]>.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .array(.generic))
+
+ guard case .array(let contextA, let arrayContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ let stringNode = JSONNode.string(.init(format: .generic,
+ required: true),
+ .init())
+
+ XCTAssertEqual(arrayContext, .init(items: stringNode))
+ }
+
+ func test_NullableArrayAttribute() {
+ let node = try! Attribute<[String]?>.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .array(.generic))
+
+ guard case .array(let contextA, let arrayContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ let stringNode = JSONNode.string(.init(format: .generic,
+ required: true),
+ .init())
+
+ XCTAssertEqual(arrayContext, .init(items: stringNode))
+ }
+
+ func test_OptionalArrayAttribute() {
+ let node = try! Attribute<[String]>?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .array(.generic))
+
+ guard case .array(let contextA, let arrayContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ let stringNode = JSONNode.string(.init(format: .generic,
+ required: true),
+ .init())
+
+ XCTAssertEqual(arrayContext, .init(items: stringNode))
+ }
+
+ func test_OptionalNullableArrayAttribute() {
+ let node = try! Attribute<[String]?>?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .array(.generic))
+
+ guard case .array(let contextA, let arrayContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: true,
+ allowedValues: nil))
+
+ let stringNode = JSONNode.string(.init(format: .generic,
+ required: true),
+ .init())
+
+ XCTAssertEqual(arrayContext, .init(items: stringNode))
+ }
+}
+
+// MARK: - Number
+extension JSONAPIAttributeOpenAPITests {
+ func test_NumberAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .number(.double))
+
+ guard case .number(let contextA, let numberContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .double,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(numberContext, .init())
+ }
+
+ func test_NullableNumberAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .number(.double))
+
+ guard case .number(let contextA, let numberContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .double,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(numberContext, .init())
+ }
+
+ func test_OptionalNumberAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .number(.double))
+
+ guard case .number(let contextA, let numberContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .double,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(numberContext, .init())
+ }
+
+ func test_OptionalNullableNumberAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .number(.double))
+
+ guard case .number(let contextA, let numberContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .double,
+ required: false,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(numberContext, .init())
+ }
+
+ func test_FloatNumberAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertEqual(node.jsonTypeFormat, .number(.float))
+ }
+}
+
+// MARK: - Integer
+extension JSONAPIAttributeOpenAPITests {
+ func test_IntegerAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .integer(.generic))
+
+ guard case .integer(let contextA, let intContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(intContext, .init())
+ }
+
+ func test_NullableIntegerAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .integer(.generic))
+
+ guard case .integer(let contextA, let intContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(intContext, .init())
+ }
+
+ func test_OptionalIntegerAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .integer(.generic))
+
+ guard case .integer(let contextA, let intContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(intContext, .init())
+ }
+
+ func test_OptionalNullableIntegerAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .integer(.generic))
+
+ guard case .integer(let contextA, let intContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(intContext, .init())
+ }
+}
+
+// MARK: - String
+extension JSONAPIAttributeOpenAPITests {
+ func test_StringAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+
+ func test_NullableStringAttribute() {
+ let node = try! Attribute.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+
+ func test_OptionalStringAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+
+ func test_OptionalNullableStringAttribute() {
+ let node = try! Attribute?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+}
+
+// MARK: - Enum
+// NOTE: `enum` Attributes only gain the automatic support for allowed values
+// (`enum` property in the OpenAPI Spec) at the Entity scope. These attributes
+// will all still have `allowedValues: nil` at the attribute scope.
+extension JSONAPIAttributeOpenAPITests {
+ func test_EnumAttribute() {
+ let node = try! Attribute.rawOpenAPINode()
+ print(EnumAttribute.allCases)
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+
+ func test_NullableEnumAttribute() {
+ let node = try! Attribute.wrappedOpenAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+
+ func test_OptionalEnumAttribute() {
+ let node = try! Attribute?.wrappedOpenAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+
+ func test_OptionalNullableEnumAttribute() {
+ let node = try! Attribute?.wrappedOpenAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
+
+ guard case .string(let contextA, let stringContext) = node else {
+ XCTFail("Expected string Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertEqual(stringContext, .init())
+ }
+}
+
+// MARK: - Test Types
+extension JSONAPIAttributeOpenAPITests {
+ enum EnumAttribute: String, Codable, CaseIterable {
+ case one
+ case two
+ }
+}
diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift
new file mode 100644
index 0000000..298100c
--- /dev/null
+++ b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift
@@ -0,0 +1,73 @@
+//
+// JSONAPIEntityOpenAPITests.swift
+// JSONAPIOpenAPITests
+//
+// Created by Mathew Polzin on 1/15/19.
+//
+
+import XCTest
+import JSONAPI
+import JSONAPIOpenAPI
+
+class JSONAPIEntityOpenAPITests: XCTestCase {
+ func test_EmptyEntity() {
+ let node = try! TestType1.openAPINode()
+
+ // TODO: Write test
+
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = .prettyPrinted
+ let string = String(data: try! encoder.encode(node), encoding: .utf8)!
+ print(string)
+ }
+
+ func test_AttributesEntity() {
+ let node = try! TestType2.openAPINode()
+
+ // TODO: Write test
+
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = .prettyPrinted
+ let string = String(data: try! encoder.encode(node), encoding: .utf8)!
+ print(string)
+ }
+}
+
+// MARK: Test Types
+extension JSONAPIEntityOpenAPITests {
+ enum TestType1Description: EntityDescription {
+ public static var jsonType: String { return "test1" }
+
+ public typealias Attributes = NoAttributes
+
+ public typealias Relationships = NoRelationships
+ }
+
+ typealias TestType1 = BasicEntity
+
+ enum TestType2Description: EntityDescription {
+ public static var jsonType: String { return "test1" }
+
+ public enum EnumType: String, CaseIterable, Codable, Equatable {
+ case one
+ case two
+ }
+
+ public struct Attributes: JSONAPI.Attributes, Sampleable {
+ let stringProperty: Attribute
+ let enumProperty: Attribute
+ var computedProperty: Attribute {
+ return enumProperty
+ }
+
+ public static var sample: Attributes {
+ return Attributes(stringProperty: .init(value: "hello"),
+ enumProperty: .init(value: .one))
+ }
+ }
+
+ public typealias Relationships = NoRelationships
+ }
+
+ typealias TestType2 = BasicEntity
+}
diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIRelationshipsOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIRelationshipsOpenAPITests.swift
new file mode 100644
index 0000000..ca51f8d
--- /dev/null
+++ b/Tests/JSONAPIOpenAPITests/JSONAPIRelationshipsOpenAPITests.swift
@@ -0,0 +1,257 @@
+//
+// JSONAPIRelationshipsOpenAPITests.swift
+// JSONAPI
+//
+// Created by Mathew Polzin on 1/14/19.
+//
+
+import Foundation
+import XCTest
+import JSONAPI
+import JSONAPITesting
+import JSONAPIOpenAPI
+
+class JSONAPIRelationshipsOpenAPITests: XCTestCase {
+
+ func test_ToOne() {
+ let node = try! ToOneRelationship.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
+
+ guard case .object(let contextA, let objectContext1) = node else {
+ XCTFail("Expected object Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext1.additionalProperties)
+ XCTAssertEqual(Array(objectContext1.properties.keys), ["data"])
+
+ guard case .object(let contextB, let objectContext2)? = objectContext1.properties["data"] else {
+ XCTFail("Expected object node within properties")
+ return
+ }
+
+ XCTAssertEqual(contextB, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext2.additionalProperties)
+ XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "type"]))
+ }
+
+ func test_OptionalToOne() {
+ let node = try! ToOneRelationship?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
+
+ guard case .object(let contextA, let objectContext1) = node else {
+ XCTFail("Expected object Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext1.additionalProperties)
+ XCTAssertEqual(Array(objectContext1.properties.keys), ["data"])
+
+ guard case .object(let contextB, let objectContext2)? = objectContext1.properties["data"] else {
+ XCTFail("Expected object node within properties")
+ return
+ }
+
+ XCTAssertEqual(contextB, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext2.additionalProperties)
+ XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "type"]))
+ }
+
+ func test_NullableToOne() {
+ let node = try! ToOneRelationship.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
+
+ guard case .object(let contextA, let objectContext1) = node else {
+ XCTFail("Expected object Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext1.additionalProperties)
+ XCTAssertEqual(Array(objectContext1.properties.keys), ["data"])
+
+ guard case .object(let contextB, let objectContext2)? = objectContext1.properties["data"] else {
+ XCTFail("Expected object node within properties")
+ return
+ }
+
+ XCTAssertEqual(contextB, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext2.additionalProperties)
+ XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "type"]))
+ }
+
+ func test_OptionalNullableToOne() {
+ let node = try! ToOneRelationship?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
+
+ guard case .object(let contextA, let objectContext1) = node else {
+ XCTFail("Expected object Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext1.additionalProperties)
+ XCTAssertEqual(Array(objectContext1.properties.keys), ["data"])
+
+ guard case .object(let contextB, let objectContext2)? = objectContext1.properties["data"] else {
+ XCTFail("Expected object node within properties")
+ return
+ }
+
+ XCTAssertEqual(contextB, .init(format: .generic,
+ required: true,
+ nullable: true,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext2.additionalProperties)
+ XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "type"]))
+ }
+
+ func test_ToMany() {
+ let node = try! ToManyRelationship.openAPINode()
+
+ XCTAssertTrue(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
+
+ guard case .object(let contextA, let objectContext1) = node else {
+ XCTFail("Expected object Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext1.additionalProperties)
+ XCTAssertEqual(Array(objectContext1.properties.keys), ["data"])
+
+ guard case .array(let contextB, let arrayContext)? = objectContext1.properties["data"] else {
+ XCTFail("Expected array node within properties")
+ return
+ }
+
+ XCTAssertEqual(contextB, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ guard case .object(let contextC, let objectContext2) = arrayContext.items else {
+ XCTFail("Expected object node within items")
+ return
+ }
+
+ XCTAssertEqual(contextC, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext2.additionalProperties)
+ XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "type"]))
+ }
+
+ func test_OptionalToMany() {
+ let node = try! ToManyRelationship?.openAPINode()
+
+ XCTAssertFalse(node.required)
+ XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
+
+ guard case .object(let contextA, let objectContext1) = node else {
+ XCTFail("Expected object Node")
+ return
+ }
+
+ XCTAssertEqual(contextA, .init(format: .generic,
+ required: false,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext1.additionalProperties)
+ XCTAssertEqual(Array(objectContext1.properties.keys), ["data"])
+
+ guard case .array(let contextB, let arrayContext)? = objectContext1.properties["data"] else {
+ XCTFail("Expected array node within properties")
+ return
+ }
+
+ XCTAssertEqual(contextB, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ guard case .object(let contextC, let objectContext2) = arrayContext.items else {
+ XCTFail("Expected object node within items")
+ return
+ }
+
+ XCTAssertEqual(contextC, .init(format: .generic,
+ required: true,
+ nullable: false,
+ allowedValues: nil))
+
+ XCTAssertNil(objectContext2.additionalProperties)
+ XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "type"]))
+ }
+}
+
+// MARK: Test Types
+extension JSONAPIRelationshipsOpenAPITests {
+ enum TestEntityType1: EntityDescription {
+ static var jsonType: String { return "test_entities"}
+
+ typealias Attributes = NoAttributes
+ typealias Relationships = NoRelationships
+ }
+
+ typealias TestEntity1 = BasicEntity
+
+ enum TestEntityType2: EntityDescription {
+ static var jsonType: String { return "second_test_entities"}
+
+ typealias Attributes = NoAttributes
+
+ struct Relationships: JSONAPI.Relationships {
+ let other: ToOneRelationship
+ }
+ }
+
+ typealias TestEntity2 = BasicEntity
+}
diff --git a/Tests/JSONAPITestLibTests/Test Helpers/EntityTestTypes.swift b/Tests/JSONAPIOpenAPITests/Test Helpers/EntityTestTypes.swift
similarity index 100%
rename from Tests/JSONAPITestLibTests/Test Helpers/EntityTestTypes.swift
rename to Tests/JSONAPIOpenAPITests/Test Helpers/EntityTestTypes.swift
diff --git a/Tests/JSONAPITestLibTests/Test Helpers/String+CreatableRawIdType.swift b/Tests/JSONAPIOpenAPITests/Test Helpers/String+CreatableRawIdType.swift
similarity index 100%
rename from Tests/JSONAPITestLibTests/Test Helpers/String+CreatableRawIdType.swift
rename to Tests/JSONAPIOpenAPITests/Test Helpers/String+CreatableRawIdType.swift
diff --git a/Tests/JSONAPIOpenAPITests/XCTestManifests.swift b/Tests/JSONAPIOpenAPITests/XCTestManifests.swift
new file mode 100644
index 0000000..b05e1af
--- /dev/null
+++ b/Tests/JSONAPIOpenAPITests/XCTestManifests.swift
@@ -0,0 +1,59 @@
+import XCTest
+
+extension JSONAPIAttributeOpenAPITests {
+ static let __allTests = [
+ ("test_Arrayttribute", test_Arrayttribute),
+ ("test_BooleanAttribute", test_BooleanAttribute),
+ ("test_EnumAttribute", test_EnumAttribute),
+ ("test_FloatNumberAttribute", test_FloatNumberAttribute),
+ ("test_IntegerAttribute", test_IntegerAttribute),
+ ("test_NullableArrayAttribute", test_NullableArrayAttribute),
+ ("test_NullableBooleanAttribute", test_NullableBooleanAttribute),
+ ("test_NullableEnumAttribute", test_NullableEnumAttribute),
+ ("test_NullableIntegerAttribute", test_NullableIntegerAttribute),
+ ("test_NullableNumberAttribute", test_NullableNumberAttribute),
+ ("test_NullableStringAttribute", test_NullableStringAttribute),
+ ("test_NumberAttribute", test_NumberAttribute),
+ ("test_OptionalArrayAttribute", test_OptionalArrayAttribute),
+ ("test_OptionalBooleanAttribute", test_OptionalBooleanAttribute),
+ ("test_OptionalEnumAttribute", test_OptionalEnumAttribute),
+ ("test_OptionalIntegerAttribute", test_OptionalIntegerAttribute),
+ ("test_OptionalNullableArrayAttribute", test_OptionalNullableArrayAttribute),
+ ("test_OptionalNullableBooleanAttribute", test_OptionalNullableBooleanAttribute),
+ ("test_OptionalNullableEnumAttribute", test_OptionalNullableEnumAttribute),
+ ("test_OptionalNullableIntegerAttribute", test_OptionalNullableIntegerAttribute),
+ ("test_OptionalNullableNumberAttribute", test_OptionalNullableNumberAttribute),
+ ("test_OptionalNullableStringAttribute", test_OptionalNullableStringAttribute),
+ ("test_OptionalNumberAttribute", test_OptionalNumberAttribute),
+ ("test_OptionalStringAttribute", test_OptionalStringAttribute),
+ ("test_StringAttribute", test_StringAttribute),
+ ]
+}
+
+extension JSONAPIEntityOpenAPITests {
+ static let __allTests = [
+ ("test_AttributesEntity", test_AttributesEntity),
+ ("test_EmptyEntity", test_EmptyEntity),
+ ]
+}
+
+extension JSONAPIRelationshipsOpenAPITests {
+ static let __allTests = [
+ ("test_NullableToOne", test_NullableToOne),
+ ("test_OptionalNullableToOne", test_OptionalNullableToOne),
+ ("test_OptionalToMany", test_OptionalToMany),
+ ("test_OptionalToOne", test_OptionalToOne),
+ ("test_ToMany", test_ToMany),
+ ("test_ToOne", test_ToOne),
+ ]
+}
+
+#if !os(macOS)
+public func __allTests() -> [XCTestCaseEntry] {
+ return [
+ testCase(JSONAPIAttributeOpenAPITests.__allTests),
+ testCase(JSONAPIEntityOpenAPITests.__allTests),
+ testCase(JSONAPIRelationshipsOpenAPITests.__allTests),
+ ]
+}
+#endif
diff --git a/Tests/JSONAPITestLibTests/Attribute+LiteralTests.swift b/Tests/JSONAPITestingTests/Attribute+LiteralTests.swift
similarity index 99%
rename from Tests/JSONAPITestLibTests/Attribute+LiteralTests.swift
rename to Tests/JSONAPITestingTests/Attribute+LiteralTests.swift
index a213cf7..da1f00a 100644
--- a/Tests/JSONAPITestLibTests/Attribute+LiteralTests.swift
+++ b/Tests/JSONAPITestingTests/Attribute+LiteralTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
class Attribute_LiteralTests: XCTestCase {
diff --git a/Tests/JSONAPITestLibTests/EntityCheckTests.swift b/Tests/JSONAPITestingTests/EntityCheckTests.swift
similarity index 99%
rename from Tests/JSONAPITestLibTests/EntityCheckTests.swift
rename to Tests/JSONAPITestingTests/EntityCheckTests.swift
index c59ac1d..10d5a48 100644
--- a/Tests/JSONAPITestLibTests/EntityCheckTests.swift
+++ b/Tests/JSONAPITestingTests/EntityCheckTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
// Successes are fairly well-checked by the EntityTests in the JSONAPITests target.
// We will confirm failure cases are working in this file.
diff --git a/Tests/JSONAPITestLibTests/Id+LiteralTests.swift b/Tests/JSONAPITestingTests/Id+LiteralTests.swift
similarity index 96%
rename from Tests/JSONAPITestLibTests/Id+LiteralTests.swift
rename to Tests/JSONAPITestingTests/Id+LiteralTests.swift
index a6812be..2390fe0 100644
--- a/Tests/JSONAPITestLibTests/Id+LiteralTests.swift
+++ b/Tests/JSONAPITestingTests/Id+LiteralTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
extension Int: RawIdType {}
diff --git a/Tests/JSONAPITestLibTests/Relationship+LiteralTests.swift b/Tests/JSONAPITestingTests/Relationship+LiteralTests.swift
similarity index 97%
rename from Tests/JSONAPITestLibTests/Relationship+LiteralTests.swift
rename to Tests/JSONAPITestingTests/Relationship+LiteralTests.swift
index 87e7f4f..9a2d7c9 100644
--- a/Tests/JSONAPITestLibTests/Relationship+LiteralTests.swift
+++ b/Tests/JSONAPITestingTests/Relationship+LiteralTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
class Relationship_LiteralTests: XCTestCase {
diff --git a/Tests/JSONAPITestingTests/Test Helpers/EntityTestTypes.swift b/Tests/JSONAPITestingTests/Test Helpers/EntityTestTypes.swift
new file mode 100644
index 0000000..23e6b7d
--- /dev/null
+++ b/Tests/JSONAPITestingTests/Test Helpers/EntityTestTypes.swift
@@ -0,0 +1,14 @@
+//
+// EntityTestTypes.swift
+// JSONAPITests
+//
+// Created by Mathew Polzin on 11/15/18.
+//
+
+import JSONAPI
+
+public typealias Entity = JSONAPI.Entity
+
+public typealias BasicEntity = Entity
+
+public typealias NewEntity = JSONAPI.Entity
diff --git a/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift b/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift
new file mode 100644
index 0000000..dd3c8f7
--- /dev/null
+++ b/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift
@@ -0,0 +1,17 @@
+//
+// String+CreatableRawIdType.swift
+// JSONAPITests
+//
+// Created by Mathew Polzin on 11/12/18.
+//
+
+import JSONAPI
+
+private var uniqueStringCounter = 0
+
+extension String: CreatableRawIdType {
+ public static func unique() -> String {
+ uniqueStringCounter += 1
+ return String(uniqueStringCounter)
+ }
+}
diff --git a/Tests/JSONAPITestLibTests/XCTestManifests.swift b/Tests/JSONAPITestingTests/XCTestManifests.swift
similarity index 100%
rename from Tests/JSONAPITestLibTests/XCTestManifests.swift
rename to Tests/JSONAPITestingTests/XCTestManifests.swift
diff --git a/Tests/JSONAPITests/Attribute/Attribute+FunctorTests.swift b/Tests/JSONAPITests/Attribute/Attribute+FunctorTests.swift
index 4274015..a87f72a 100644
--- a/Tests/JSONAPITests/Attribute/Attribute+FunctorTests.swift
+++ b/Tests/JSONAPITests/Attribute/Attribute+FunctorTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
class Attribute_FunctorTests: XCTestCase {
func test_mapGuaranteed() {
diff --git a/Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift b/Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift
index f80839a..aaf626d 100644
--- a/Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift
+++ b/Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
class ComputedPropertiesTests: XCTestCase {
func test_DecodeIgnoresComputed() {
diff --git a/Tests/JSONAPITests/Custom Attributes Tests/CustomAttributesTests.swift b/Tests/JSONAPITests/Custom Attributes Tests/CustomAttributesTests.swift
index 3282b4e..91c46bd 100644
--- a/Tests/JSONAPITests/Custom Attributes Tests/CustomAttributesTests.swift
+++ b/Tests/JSONAPITests/Custom Attributes Tests/CustomAttributesTests.swift
@@ -7,7 +7,7 @@
import XCTest
@testable import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
class CustomAttributesTests: XCTestCase {
func test_customDecode() {
diff --git a/Tests/JSONAPITests/Entity/EntityTests.swift b/Tests/JSONAPITests/Entity/EntityTests.swift
index 6142247..ab75f07 100644
--- a/Tests/JSONAPITests/Entity/EntityTests.swift
+++ b/Tests/JSONAPITests/Entity/EntityTests.swift
@@ -7,7 +7,7 @@
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
class EntityTests: XCTestCase {
diff --git a/Tests/JSONAPITests/Test Helpers/EncodedAttributeTest.swift b/Tests/JSONAPITests/Test Helpers/EncodedAttributeTest.swift
index 55f0c2f..56070a0 100644
--- a/Tests/JSONAPITests/Test Helpers/EncodedAttributeTest.swift
+++ b/Tests/JSONAPITests/Test Helpers/EncodedAttributeTest.swift
@@ -8,7 +8,7 @@
import Foundation
import XCTest
@testable import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
private struct TransformedWrapper: Codable where Value == Transform.From {
let x: TransformedAttribute
diff --git a/Tests/JSONAPITests/Test Helpers/EncodedEntityPropertyTest.swift b/Tests/JSONAPITests/Test Helpers/EncodedEntityPropertyTest.swift
index d2ce1bc..e7de8b7 100644
--- a/Tests/JSONAPITests/Test Helpers/EncodedEntityPropertyTest.swift
+++ b/Tests/JSONAPITests/Test Helpers/EncodedEntityPropertyTest.swift
@@ -8,7 +8,7 @@
import Foundation
import XCTest
import JSONAPI
-import JSONAPITestLib
+import JSONAPITesting
func testEncoded(entity: E) {
let encodedEntityData = encoded(value: entity)
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
index ea07a7d..791d328 100644
--- a/Tests/LinuxMain.swift
+++ b/Tests/LinuxMain.swift
@@ -1,10 +1,14 @@
import XCTest
import JSONAPITests
-import JSONAPITestLibTests
+import JSONAPITestingTests
+import JSONAPIOpenAPITests
+import JSONAPIArbitraryTests
var tests = [XCTestCaseEntry]()
tests += JSONAPITests.__allTests()
-tests += JSONAPITestLibTests.__allTests()
+tests += JSONAPITestingTests.__allTests()
+tests += JSONAPIOpenAPITests.__allTests()
+tests += JSONAPIArbitraryTests.__allTests()
XCTMain(tests)