Compare commits

..

11 Commits

17 changed files with 494 additions and 73 deletions
@@ -58,6 +58,6 @@ func process<T: JSONAPIDocument>(document: T) {
guard case let .data(body) = document.body else {
return
}
let x: T.BodyData = body
let x: T.Body.Data = body
}
process(document: peopleResponse)
+50 -23
View File
@@ -1,14 +1,14 @@
# JSONAPI
[![MIT license](http://img.shields.io/badge/license-MIT-lightgrey.svg)](http://opensource.org/licenses/MIT) [![Swift 4.2](http://img.shields.io/badge/Swift-4.2-blue.svg)](https://swift.org) [![Build Status](https://app.bitrise.io/app/c8295b9589aa401e/status.svg?token=vzcyqWD5bQ4xqQfZsaVzNw&branch=master)](https://app.bitrise.io/app/c8295b9589aa401e)
A Swift package for encoding to- and decoding from *JSON API* compliant requests and responses.
A Swift package for encoding to- and decoding from **JSON API** compliant requests and responses.
See the JSON API Spec here: https://jsonapi.org/format/
## Primary Goals
The primary goals of this framework are:
1. Allow creation of Swift types that are easy to use in code but also can be encoded to- or decoded from *JSON API* compliant payloads without lots of boilerplate code.
1. Allow creation of Swift types that are easy to use in code but also can be encoded to- or decoded from **JSON API v1.0 Spec** compliant payloads without lots of boilerplate code.
2. Leverage `Codable` to avoid additional outside dependencies and get operability with non-JSON encoders/decoders for free.
3. Do not sacrifice type safety.
4. Be platform agnostic so that Swift code can be written once and used by both the client and the server.
@@ -16,7 +16,7 @@ The primary goals of this framework are:
### Caveat
The big caveat is that, although the aim is to support the JSON API spec, this framework ends up being _naturally_ opinionated about certain things that the API Spec does not specify. These caveats are largely a side effect of attempting to write the library in a "Swifty" way.
If you find that something in the JSON API v1.0 Spec is not explicitly missing from the **Project Status** checklist but this library does not support it, please let me know! I want to keep working towards a library implementation that is useful in any application.
If you find something wrong with this library and it isn't already mentioned under **Project Status**, let me know! I want to keep working towards a library implementation that is useful in any application.
## Dev Environment
### Prerequisites
@@ -26,6 +26,11 @@ If you find that something in the JSON API v1.0 Spec is not explicitly missing f
To create an Xcode project for JSONAPI, run
`swift package generate-xcodeproj`
### Running the Playground
To run the included Playground files, create an Xcode project using Swift Package Manager, then create an Xcode Workspace in the root of the repository and add both the generated Xcode project and the playground to the Workspace.
Note that Playground support for importing non-system Frameworks is still a bit touchy as of Swift 4.2. Sometimes building, cleaning and building, or commenting out and then uncommenting import statements (especially in the Entities.swift Playground Source file) can get things working for me when I am getting an error about JSONAPI not being found.
## Project Status
### Decoding
@@ -104,9 +109,11 @@ To create an Xcode project for JSONAPI, run
## Usage
In this documentation, in order to draw attention to the difference between the `JSONAPI` framework (this Swift library) and the **JSON API Spec** (the specification this library helps you follow), the specification will consistently be referred to below as simply the **SPEC**.
### `EntityDescription`
An `EntityDescription` is the `JSONAPI` framework's specification for what the JSON API spec calls a *Resource Object*. You might create the following `EntityDescription` to represent a person in a network of friends:
An `EntityDescription` is the `JSONAPI` framework's representation of what the **SPEC** calls a *Resource Object*. You might create the following `EntityDescription` to represent a person in a network of friends:
```
enum PersonDescription: IdentifiedEntityDescription {
@@ -124,13 +131,13 @@ enum PersonDescription: IdentifiedEntityDescription {
```
The requirements of an `EntityDescription` are:
1. A static `var` "type" that matches the JSON type; The JSON spec requires every *Resource Object* to have a "type".
1. A static `var` "type" that matches the JSON type; The **SPEC** requires every *Resource Object* to have a "type".
2. A `struct` of `Attributes` **- OR -** `typealias Attributes = NoAttributes`
3. A `struct` of `Relationships` **- OR -** `typealias Relationships = NoRelationships`
Note that an `enum` type is used here for the `EntityDescription`; it could have been a `struct`, but `EntityDescription`s do not ever need to be created so an `enum` with no `case`s is a nice fit for the job.
This readme doesn't go into detail on the JSON API Spec, but the following JSON API *Resource Object* would be described by the above `PersonDescription`:
This readme doesn't go into detail on the **SPEC**, but the following *Resource Object* would be described by the above `PersonDescription`:
```
{
@@ -162,15 +169,17 @@ This readme doesn't go into detail on the JSON API Spec, but the following JSON
### `Entity`
Once you have an `EntityDescription`, you _create_, _encode_, and _decode_ `Entity`s that "fit the description". If you have a `CreatableRawIdType` (see the section on `RawIdType`s below) then you can create new `Entity`s that will automatically be given unique Ids, but even without a `CreatableRawIdType` you can encode, decode and work with entities.
Once you have an `EntityDescription`, you _create_, _encode_, and _decode_ `Entities` that "fit the description". If you have a `CreatableRawIdType` (see the section on `RawIdType`s below) then you can create new `Entities` that will automatically be given unique Ids, but even without a `CreatableRawIdType` you can encode, decode and work with entities.
The `Entity` and `EntityDescription` together embody the rules and properties of a JSON API *Resource Object*.
An `Entity` needs to be specialized on two generic types. The first is the `EntityDescription` described above. The second is the type of Id to use for the `Entity`.
#### `IdType`
An `IdType` packages up two pieces of information: A unique identifier of a given `RawIdType` and the `Entity` type that the Id identifies. Having the `Entity` type associated with the Id makes it easy to store all of your entities in a local hash broken out by `Entity` type; You can pass Ids around and always know where to look for the `Entity` to which the Id refers. `RawIdType`s are documented below.
An `Entity` needs to be specialized on two generic types. The first is the `EntityDescription` described above. The second is the raw type of `Id` to use for the `Entity`. The actual `Id` of the `Entity` will not be a `RawIdType`, though. The `Id` will package a value of `RawIdType` with a specialized reference back to the `Entity` type it identifies. This just looks like `Id<RawIdType, Entity<EntityDescription, RawIdType>>`.
Having the `Entity` type associated with the `Id` makes it easy to store all of your entities in a hash broken out by `Entity` type; You can pass `Ids` around and always know where to look for the `Entity` to which the `Id` refers.
A `RawIdType` is the underlying type that uniquely identifies an `Entity`. This is often a `String` or a `UUID`.
#### Convenient `typealiases`
@@ -188,13 +197,13 @@ typealias Person = Entity<PersonDescription>
typealias NewPerson = NewEntity<PersonDescription>
```
Note that I am assuming an unidentified person is a "new" person. I suspect that is generally an acceptable conflation because the only time JSON API spec allows a *Resource Object* to be encoded without an Id is when a client is requesting the given *Resource Object* be created by the server and the client wants the server to create the Id for that object.
Note that I am assuming an unidentified person is a "new" person. I suspect that is generally an acceptable conflation because the only time the **SPEC** allows a *Resource Object* to be encoded without an `Id` is when a client is requesting the given *Resource Object* be created by the server and the client wants the server to create the `Id` for that object.
### `Relationships`
There are two types of `Relationship`s: `ToOneRelationship` and `ToManyRelationship`. An `EntityDescription`'s `Relationships` type can contain any number of `Relationship`s of either of these types. Do not store anything other than `Relationship`s in the `Relationships` struct of an `EntityDescription`.
There are two types of `Relationships`: `ToOneRelationship` and `ToManyRelationship`. An `EntityDescription`'s `Relationships` type can contain any number of `Relationship` properties of either of these types. Do not store anything other than `Relationship` properties in the `Relationships` struct of an `EntityDescription`.
To describe a relationship that may be omitted (i.e. the key is not even present in the JSON object), you make the entire `ToOneRelationship` or `ToManyRelationship` optional. However, this is not recommended because you can also represent optional relationships as nullable which means the key is always present. A `ToManyRelationship` can naturally represent no related objects exist with an empty array, so `ToManyRelationship` does not support nullability at all. A `ToOneRelationship` can be marked as nullable (i.e. the value might be `null` or it might be a resource identifier) like this:
To describe a relationship that may be omitted (i.e. the key is not even present in the JSON object), you make the entire `ToOneRelationship` or `ToManyRelationship` optional. However, this is not recommended because you can also represent optional relationships as nullable which means the key is always present. A `ToManyRelationship` can naturally represent the absence of related values with an empty array, so `ToManyRelationship` does not support nullability at all. A `ToOneRelationship` can be marked as nullable (i.e. the value might be `null` or it might be a resource identifier) like this:
```
let nullableRelative: ToOneRelationship<Person?>
```
@@ -204,14 +213,14 @@ An entity that does not have relationships can be described by adding the follow
typealias Relationships = NoRelationships
```
`Relationship`s boil down to Ids of other entities. To access the Id of a related entity, you can use the shorthand `~>` operator with the `KeyPath` of the `Relationship` from which you want the Id. The friends of the above `Person` entity could be accessed as follows (type annotations for clarity):
`Relationship` values boil down to `Ids` of other entities. To access the `Id` of a related `Entity`, you can use the custom `~>` operator with the `KeyPath` of the `Relationship` from which you want the `Id`. The friends of the above `Person` `Entity` can be accessed as follows (type annotations for clarity):
```
let friendIds: [Person.Identifier] = person ~> \.friends
```
### `Attributes`
The `Attributes` of an `EntityDescription` can contain any JSON encodable/decodable types as long as they are wrapped in an `Attribute` or `TransformAttribute` `struct`. This is the place to store all attributes of an entity.
The `Attributes` of an `EntityDescription` can contain any JSON encodable/decodable types as long as they are wrapped in an `Attribute`, `ValidatedAttribute`, or `TransformedAttribute` `struct`.
To describe an attribute that may be omitted (i.e. the key might not even be in the JSON object), you make the entire `Attribute` optional:
```
@@ -235,12 +244,12 @@ let favoriteColor: String = person[\.favoriteColor]
#### `Transformer`
Sometimes you need to use a type that does not encode or decode itself in the way you need to represent it as a serialized JSON object. For example, the Swift `Foundation` type `Date` can encode/decode itself to `Double` out of the box, but you might want to represent dates as ISO 8601 compliant `String`s instead. To do this, you create a `Transformer`.
Sometimes you need to use a type that does not encode or decode itself in the way you need to represent it as a serialized JSON object. For example, the Swift `Foundation` type `Date` can encode/decode itself to `Double` out of the box, but you might want to represent dates as ISO 8601 compliant `String`s instead. The Foundation library `JSONDecoder` has a setting to make this adjustment, but for the sake of an example, you could create a `Transformer`.
A `Transformer` just provides one static function that transforms one type to another. You might define one for an ISO 8601 compliant `Date` like this:
```
enum ISODateTransformer: Transformer {
public static func transform(_ from: String) throws -> Date {
public static func transform(_ value: String) throws -> Date {
// parse Date out of input and return
}
}
@@ -253,9 +262,21 @@ let date: TransformedAttribute<String, ISODateTransformer>
Note that the first generic parameter of `TransformAttribute` is the type you expect to decode from JSON, not the type you want to end up with after transformation.
If you make your `Transformer` a `ReversibleTransformer` then your life will be a bit easier when you construct `TransformedAttributes` because you have access to initializers for both the pre- and post-transformed value types. Continuing with the above example of a `ISODateTransformer`:
```
extension ISODateTransformer: ReversibleTransformer {
public static func reverse(_ value: Date) throws -> String {
// serialize Date to a String
}
}
let exampleAttribute = try? TransformedAttribute<String, ISODateTransformer>(transformedValue: Date())
let otherAttribute = try? TransformedAttribute<String, ISODateTransformer>(rawValue: "2018-12-01 09:06:41 +0000")
```
#### `Validator`
You can also creator `Validator`s and `ValidatedAttribute`s. A `Validator` is just a `Transformer` that by convention does not perform a transformation. It simply `throws` if an attribute value is invalid.
You can also creator `Validators` and `ValidatedAttribute`s. A `Validator` is just a `Transformer` that by convention does not perform a transformation. It simply `throws` if an attribute value is invalid.
#### Computed `Attribute`
@@ -278,17 +299,23 @@ let responseStructure = JSONAPIDocument<SingleResourceBody<Person>, NoMetadata,
let document = try decoder.decode(responseStructure, from: data)
```
This document is guaranteed by the JSON API spec to be "data", "metadata", or "errors." If it is "data", it may also contain "metadata" and/or other "included" resources. If it is "errors," it may also contain "metadata."
A JSON API Document is guaranteed by the **SPEC** to be "data", "metadata", or "errors." If it is "data", it may also contain "metadata" and/or other "included" resources. If it is "errors," it may also contain "metadata."
#### `ResourceBody`
The first generic type of a `JSONAPIDocument` is a `ResourceBody`. This can either be a `SingleResourceBody` or a `ManyResourceBody`. You will find zero or one `Entity` values in a JSON API document that has a `SingleResourceBody` and you will find zero or more `Entity` values in a JSON API document that has a `ManyResourceBody`. You can use the `Poly` types (`Poly1` through `Poly6`) to specify that a `ResourceBody` will be one of a few different types of `Entity`. These `Poly` types work in the same way as the `Include` types described below.
The first generic type of a `JSONAPIDocument` is a `ResourceBody`. This can either be a `SingleResourceBody<PrimaryResource>` or a `ManyResourceBody<PrimaryResource>`. You will find zero or one `PrimaryResource` values in a JSON API document that has a `SingleResourceBody` and you will find zero or more `PrimaryResource` values in a JSON API document that has a `ManyResourceBody`. You can use the `Poly` types (`Poly1` through `Poly6`) to specify that a `ResourceBody` will be one of a few different types of `Entity`. These `Poly` types work in the same way as the `Include` types described below.
If you expect a response to not have a "data" top-level key at all, then use `NoResourceBody` instead.
##### nullable `PrimaryResource`
If you expect a `SingleResourceBody` to sometimes come back `null`, you should make your `PrimaryResource` optional. If you do not make your `PrimaryResource` optional then a `null` primary resource will be considered an error when parsing the JSON.
You cannot, however, use an optional `PrimaryResource` with a `ManyResourceBody` because the **SPEC** requires that an empty document in that case be represented by an empty array rather than `null`.
#### `MetaType`
The second generic type of a `JSONAPIDocument` is a `Meta`. This structure is entirely open-ended. As an example, the JSON API document may contain the following pagination info in its meta entry:
The second generic type of a `JSONAPIDocument` is a `Meta`. This structure is entirely open-ended. As an example, the JSON API document could, as an example, contain the following pagination info in its meta entry:
```
{
"meta": {
@@ -326,7 +353,7 @@ To specify that we expect friends of a person to be included in the above exampl
#### `Error`
The final generic type of a `JSONAPIDocument` is the `Error`. You should create an error type that can decode all the errors you expect your `JSONAPIDocument` to be able to decode. As prescribed by the JSON API Spec, these errors will be found in the root document member `errors`.
The final generic type of a `JSONAPIDocument` is the `Error`. You should create an error type that can decode all the errors you expect your `JSONAPIDocument` to be able to decode. As prescribed by the **SPEC**, these errors will be found in the root document member `errors`.
### `RawIdType`
@@ -346,4 +373,4 @@ extension String: CreatableRawIdType {
```
# JSONAPITestLib
JSONAPI comes with a test library to help you test your JSON API 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.
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.
+7 -1
View File
@@ -13,7 +13,6 @@ public protocol JSONAPIDocument: Codable, Equatable {
associatedtype Error: JSONAPIError
typealias Body = Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, Error>.Body
typealias BodyData = Body.Data
var body: Body { get }
}
@@ -40,6 +39,13 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
public let includes: Includes<Include>
public let meta: MetaType
public let links: LinksType
public init(primary: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType, links: LinksType) {
self.primary = primary
self.includes = includes
self.meta = meta
self.links = links
}
}
public var isError: Bool {
+19 -8
View File
@@ -5,15 +5,24 @@
// Created by Mathew Polzin on 11/10/18.
//
public protocol PrimaryResource: Equatable, Codable {}
public protocol MaybePrimaryResource: Equatable, Codable {}
/// A PrimaryResource is a type that can be used in the body of a JSON API
/// document as the primary resource.
public protocol PrimaryResource: MaybePrimaryResource {}
extension Optional: MaybePrimaryResource where Wrapped: PrimaryResource {}
/// A ResourceBody is a representation of the body of the JSON API Document.
/// It can either be one resource (which can be specified as optional or not)
/// or it can contain many resources (and array with zero or more entries).
public protocol ResourceBody: Codable, Equatable {
}
public struct SingleResourceBody<Entity: JSONAPI.PrimaryResource>: ResourceBody {
public let value: Entity?
public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: ResourceBody {
public let value: Entity
public init(entity: Entity?) {
public init(entity: Entity) {
self.value = entity
}
}
@@ -32,13 +41,15 @@ public struct NoResourceBody: ResourceBody {
public static var none: NoResourceBody { return NoResourceBody() }
}
// MARK: Decodable
// MARK: Codable
extension SingleResourceBody {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
value = nil
let anyNil: Any? = nil
if container.decodeNil(),
let val = anyNil as? Entity {
value = val
return
}
@@ -48,7 +59,7 @@ extension SingleResourceBody {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if value == nil {
if (value as Any?) == nil {
try container.encodeNil()
return
}
+7
View File
@@ -19,6 +19,13 @@ public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Trans
}
}
extension TransformedAttribute where Transformer: ReversibleTransformer {
public init(transformedValue: Transformer.To) throws {
self.value = transformedValue
rawValue = try Transformer.reverse(value)
}
}
extension TransformedAttribute: CustomStringConvertible {
public var description: String {
return "Attribute<\(String(describing: Transformer.From.self)) -> \(String(describing: Transformer.To.self))>(\(String(describing: value)))"
+41 -16
View File
@@ -16,11 +16,15 @@ public typealias Attributes = Codable & Equatable
/// Can be used as `Relationships` Type for Entities that do not
/// have any Relationships.
public struct NoRelationships: Relationships {}
public struct NoRelationships: Relationships {
public static var none: NoRelationships { return .init() }
}
/// Can be used as `Attributes` Type for Entities that do not
/// have any Attributes.
public struct NoAttributes: Attributes {}
public struct NoAttributes: Attributes {
public static var none: NoAttributes { return .init() }
}
/// An `EntityDescription` describes a JSON API
/// Resource Object. The Resource Object
@@ -34,10 +38,10 @@ public protocol EntityDescription {
static var type: String { get }
}
/// EntityType is the protocol that Entity conforms to. This
/// protocol lets other types accept any Entity as a generic
/// specialization.
public protocol EntityType: PrimaryResource {
/// EntityProxy is a protocol that can be used to create
/// types that _act_ like Entities but cannot be encoded
/// or decoded as Entities.
public protocol EntityProxy: Equatable {
associatedtype Description: EntityDescription
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
@@ -59,6 +63,17 @@ public protocol EntityType: PrimaryResource {
var relationships: Relationships { get }
}
extension EntityProxy {
/// The JSON API compliant "type" of this `Entity`.
public static var type: String { return Description.type }
}
/// EntityType is the protocol that Entity conforms to. This
/// protocol lets other types accept any Entity as a generic
/// specialization.
public protocol EntityType: EntityProxy, PrimaryResource {
}
public protocol IdentifiableEntityType: EntityType, Relatable where EntityRawIdType: JSONAPI.RawIdType {}
/// An `Entity` is a single model type that can be
@@ -66,10 +81,6 @@ public protocol IdentifiableEntityType: EntityType, Relatable where EntityRawIdT
/// "Resource Object."
/// See https://jsonapi.org/format/#document-resource-objects
public struct Entity<Description: JSONAPI.EntityDescription, EntityRawIdType: JSONAPI.MaybeRawId>: EntityType {
/// The JSON API compliant "type" of this `Entity`.
public static var type: String { return Description.type }
/// The `Entity`'s Id. This can be of type `Unidentified` if
/// the entity is being created clientside and the
/// server is being asked to create a unique Id. Otherwise,
@@ -109,6 +120,14 @@ extension Entity where EntityRawIdType: CreatableRawIdType {
}
}
extension Entity where EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships) {
self.id = .unidentified
self.attributes = attributes
self.relationships = relationships
}
}
extension Entity where Description.Attributes == NoAttributes {
public init(id: Entity.Id, relationships: Description.Relationships) {
self.init(id: id, attributes: NoAttributes(), relationships: relationships)
@@ -133,6 +152,12 @@ extension Entity where Description.Relationships == NoRelationships, EntityRawId
}
}
extension Entity where Description.Relationships == NoRelationships, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes) {
self.init(attributes: attributes, relationships: NoRelationships())
}
}
extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships {
public init(id: Entity.Id) {
self.init(id: id, attributes: NoAttributes(), relationships: NoRelationships())
@@ -155,21 +180,21 @@ public extension Entity where EntityRawIdType: JSONAPI.RawIdType {
}
// MARK: Attribute Access
public extension Entity {
public extension EntityProxy {
/// Access the attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
subscript<T, TFRM: Transformer>(_ path: KeyPath<Description.Attributes, TransformedAttribute<T, TFRM>>) -> TFRM.To {
return attributes[keyPath: path].value
}
/// Access the attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
subscript<T, TFRM: Transformer>(_ path: KeyPath<Description.Attributes, TransformedAttribute<T, TFRM>?>) -> TFRM.To? {
return attributes[keyPath: path]?.value
}
/// Access the attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
@@ -179,18 +204,18 @@ public extension Entity {
}
// MARK: Relationship Access
public extension Entity {
public extension EntityProxy {
/// Access to an Id of a `ToOneRelationship`.
/// This allows you to write `entity ~> \.other` instead
/// of `entity.relationships.other.id`.
public static func ~><OtherEntity: OptionalRelatable>(entity: Entity, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity>>) -> OtherEntity.WrappedIdentifier {
public static func ~><OtherEntity: OptionalRelatable>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity>>) -> OtherEntity.WrappedIdentifier {
return entity.relationships[keyPath: path].id
}
/// Access to all Ids of a `ToManyRelationship`.
/// This allows you to write `entity ~> \.others` instead
/// of `entity.relationships.others.ids`.
public static func ~><OtherEntity: Relatable>(entity: Entity, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity>>) -> [OtherEntity.Identifier] {
public static func ~><OtherEntity: Relatable>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity>>) -> [OtherEntity.Identifier] {
return entity.relationships[keyPath: path].ids
}
}
+2 -2
View File
@@ -35,7 +35,7 @@ public struct Unidentified: MaybeRawId, CustomStringConvertible {
}
public protocol MaybeId: Codable {
associatedtype EntityType: JSONAPI.EntityType
associatedtype EntityType: JSONAPI.EntityProxy
associatedtype RawType: MaybeRawId
}
@@ -53,7 +53,7 @@ public protocol CreatableIdType: IdType {
/// An Entity ID. These IDs can be encoded to or decoded from
/// JSON API IDs.
public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityType>: Codable, Equatable, MaybeId {
public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityProxy>: Codable, Equatable, MaybeId {
public let rawValue: RawType
+19 -5
View File
@@ -9,11 +9,16 @@ public protocol Transformer {
associatedtype From
associatedtype To
static func transform(_ from: From) throws -> To
static func transform(_ value: From) throws -> To
}
public enum IdentityTransformer<T>: Transformer {
public static func transform(_ from: T) throws -> T { return from }
public protocol ReversibleTransformer: Transformer {
static func reverse(_ value: To) throws -> From
}
public enum IdentityTransformer<T>: ReversibleTransformer {
public static func transform(_ value: T) throws -> T { return value }
public static func reverse(_ value: T) throws -> T { return value }
}
// MARK: - Validator
@@ -22,10 +27,19 @@ public enum IdentityTransformer<T>: Transformer {
/// is passed to it but it does not change the type of the value. Any
/// Transformer will perform validation in one sense so a Validator is
/// really just semantic sugar (it can provide clarity in its use).
public protocol Validator: Transformer where From == To {}
/// To enforce the semantics, any change of the value in your implementation
/// of `Validator.transform()` will be ignored.
public protocol Validator: ReversibleTransformer where From == To {
}
extension Validator {
public static func reverse(_ value: To) throws -> To {
let _ = try transform(value)
return value
}
public static func validate(_ value: To) throws -> To {
return try transform(value)
let _ = try transform(value)
return value
}
}
+14 -5
View File
@@ -8,12 +8,25 @@
import JSONAPI
public enum EntityCheckError: Swift.Error {
/// The attributes should live in a struct, not
/// another type class.
case attributesNotStruct
/// The relationships should live in a struct, not
/// another type class.
case relationshipsNotStruct
/// All stored properties on an Attributes struct should
/// be one of the supplied Attribute types.
case nonAttribute(named: String)
/// All stored properties on a Relationships struct should
/// be one of the supplied Relationship types.
case nonRelationship(named: String)
/// It is explicitly stated by the JSON spec
/// a "none" value for arrays is an empty array, not `nil`.
case nullArray(named: String)
case badId
}
public struct EntityCheckErrors: Swift.Error {
@@ -36,10 +49,6 @@ public extension Entity {
public static func check(_ entity: Entity) throws {
var problems = [EntityCheckError]()
if Swift.type(of: entity.id).EntityType.Description.self != Description.self {
problems.append(.badId)
}
let attributesMirror = Mirror(reflecting: entity.attributes)
if attributesMirror.displayStyle != .`struct` {
@@ -0,0 +1,30 @@
//
// Optional+Literal.swift
// JSONAPITestLib
//
// Created by Mathew Polzin on 11/29/18.
//
extension Optional: ExpressibleByUnicodeScalarLiteral where Wrapped: ExpressibleByUnicodeScalarLiteral {
public typealias UnicodeScalarLiteralType = Wrapped.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self = .some(Wrapped(unicodeScalarLiteral: value))
}
}
extension Optional: ExpressibleByExtendedGraphemeClusterLiteral where Wrapped: ExpressibleByExtendedGraphemeClusterLiteral {
public typealias ExtendedGraphemeClusterLiteralType = Wrapped.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self = .some(Wrapped(extendedGraphemeClusterLiteral: value))
}
}
extension Optional: ExpressibleByStringLiteral where Wrapped: ExpressibleByStringLiteral {
public typealias StringLiteralType = Wrapped.StringLiteralType
public init(stringLiteral value: StringLiteralType) {
self = .some(Wrapped(stringLiteral: value))
}
}
@@ -9,10 +9,35 @@ import JSONAPI
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
}
}
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral {
public typealias UnicodeScalarLiteralType = Relatable.WrappedIdentifier.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(id: Relatable.WrappedIdentifier(unicodeScalarLiteral: value))
}
}
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral {
public typealias ExtendedGraphemeClusterLiteralType = Relatable.WrappedIdentifier.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(id: Relatable.WrappedIdentifier(extendedGraphemeClusterLiteral: value))
}
}
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral {
public typealias StringLiteralType = Relatable.WrappedIdentifier.StringLiteralType
public init(stringLiteral value: StringLiteralType) {
self.init(id: Relatable.WrappedIdentifier(stringLiteral: value))
}
}
extension ToManyRelationship: ExpressibleByArrayLiteral {
public typealias ArrayLiteralElement = Relatable.Identifier
@@ -18,4 +18,31 @@ class AttributeTests: XCTestCase {
XCTAssertEqual(try Attribute<String>(rawValue: "hello"), Attribute<String>(value: "hello"))
}
func test_TransformedAttributeNoThrow() {
XCTAssertNoThrow(try TransformedAttribute<String, TestTransformer>(rawValue: "10"))
}
func test_TransformedAttributeThrows() {
XCTAssertThrowsError(try TransformedAttribute<String, TestTransformer>(rawValue: "10.3"))
}
func test_TransformedAttributeReversNoThrow() {
XCTAssertNoThrow(try TransformedAttribute<String, TestTransformer>(transformedValue: 10))
}
}
// MARK: Test types
extension AttributeTests {
enum TestTransformer: ReversibleTransformer {
public static func transform(_ value: String) throws -> Int {
guard let ret = Int(value) else {
throw DecodingError.typeMismatch(Int.self, .init(codingPath: [], debugDescription: "Expected Int from String."))
}
return ret
}
public static func reverse(_ value: Int) throws -> String {
return String(value)
}
}
}
+50 -12
View File
@@ -11,7 +11,7 @@ import JSONAPI
class DocumentTests: XCTestCase {
func test_singleDocumentNull() {
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article?>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_null)
XCTAssertFalse(document.body.isError)
@@ -23,10 +23,18 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNull_encode() {
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article?>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_null)
}
func test_singleDocumentNonOptionalFailsOnNull() {
XCTAssertThrowsError(try JSONDecoder().decode(Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
from: single_document_null))
}
}
// MARK: - Error Document Tests
extension DocumentTests {
func test_unknownErrorDocumentNoMeta() {
let document = decoded(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
@@ -200,7 +208,10 @@ class DocumentTests: XCTestCase {
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
}
}
// MARK: - Meta Document Tests
extension DocumentTests {
func test_metaDataDocument() {
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: metadata_document)
@@ -242,7 +253,11 @@ class DocumentTests: XCTestCase {
XCTAssertThrowsError(try JSONDecoder().decode(Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, from: metadata_document_missing_metadata2))
}
}
// MARK: Single Document Tests
extension DocumentTests {
func test_singleDocumentNoIncludes() {
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes)
@@ -250,7 +265,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
}
@@ -260,6 +275,23 @@ class DocumentTests: XCTestCase {
data: single_document_no_includes)
}
func test_singleDocumentNoIncludesOptionalNotNull() {
let document = decoded(type: Document<SingleResourceBody<Article?>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes)
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
}
func test_singleDocumentNoIncludesOptionalNotNull_encode() {
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article?>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes)
}
func test_singleDocumentNoIncludesWithMetadata() {
let document = decoded(type: Document<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_metadata)
@@ -267,7 +299,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
}
@@ -284,7 +316,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
@@ -306,7 +338,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
@@ -336,7 +368,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 1)
XCTAssertEqual(document.body.includes?[Author.self].count, 1)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -354,7 +386,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 1)
XCTAssertEqual(document.body.includes?[Author.self].count, 1)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -373,7 +405,7 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
XCTAssertEqual(document.body.links?.link.meta, NoMetadata())
@@ -388,19 +420,25 @@ class DocumentTests: XCTestCase {
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, TestPageMetadata, TestLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes_with_metadata_with_links)
}
}
// MARK: Poly PrimaryResource Tests
extension DocumentTests {
func test_singleDocument_PolyPrimaryResource() {
let article = Article(id: Id(rawValue: "1"), relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))))
let document = decoded(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, data: single_document_no_includes)
XCTAssertEqual(document.body.primaryData?.value?[Article.self], article)
XCTAssertNil(document.body.primaryData?.value?[Author.self])
XCTAssertEqual(document.body.primaryData?.value[Article.self], article)
XCTAssertNil(document.body.primaryData?.value[Author.self])
}
func test_singleDocument_PolyPrimaryResource_encode() {
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, data: single_document_no_includes)
}
}
// MARK: - ManyResourceBody Tests
extension DocumentTests {
func test_manyDocumentNoIncludes() {
let document = decoded(type: Document<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: many_document_no_includes)
@@ -40,6 +40,24 @@ class EntityTests: XCTestCase {
XCTAssertEqual(entity2.relationships.other.id, entity1.id)
}
func test_initialization() {
let entity1 = TestEntity1(id: .init(rawValue: "wow"))
let entity2 = TestEntity2(id: .init(rawValue: "cool"), relationships: .init(other: .init(entity: entity1)))
let _ = TestEntity3(id: .init(rawValue: "3"), relationships: .init(others: .init(ids: [.init(rawValue: "10"), .init(rawValue: "20"), entity1.id])))
let _ = TestEntity4(id: .init(rawValue: "4"), attributes: .init(word: .init(value: "hello"), number: .init(value: 10), array: .init(value: [10.2, 10.3])), relationships: .init(other: entity2.pointer))
let _ = TestEntity5(id: .init(rawValue: "5"), attributes: .init(floater: .init(value: 10.2)))
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)))
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")))
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil))))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil))
let e10id1 = TestEntity10.Identifier(rawValue: "hello")
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
let _ = TestEntity10(id: .init(rawValue: "10"), relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])))
XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11))))
let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")))
}
}
// MARK: - Encode/Decode
@@ -18,6 +18,11 @@ class Relationship_LiteralTests: XCTestCase {
func test_ArrayLiteral() {
XCTAssertEqual(ToManyRelationship<TestEntity>(ids: ["1", "2", "3"]), ["1", "2", "3"])
}
func test_StringLiteral() {
XCTAssertEqual(ToOneRelationship<TestEntity>(id: "123"), "123")
XCTAssertEqual(ToOneRelationship<TestEntity?>(id: "123"), "123")
}
}
// MARK: - Test types
@@ -0,0 +1,159 @@
//
// PolyProxyTests.swift
// JSONAPITests
//
// Created by Mathew Polzin on 11/29/18.
//
import XCTest
import JSONAPI
public class PolyProxyTests: XCTestCase {
func test_generalReasonableness() {
XCTAssertNotEqual(decoded(type: User.self, data: poly_user_stub_1), decoded(type: User.self, data: poly_user_stub_2))
XCTAssertEqual(User.type, "users")
}
func test_UserADecode() {
let polyUserA = decoded(type: User.self, data: poly_user_stub_1)
let userA = decoded(type: UserA.self, data: poly_user_stub_1)
XCTAssertEqual(polyUserA.userA, userA)
XCTAssertNil(polyUserA.userB)
XCTAssertEqual(polyUserA[\.name], "Ken Moore")
XCTAssertEqual(polyUserA.id, "1")
XCTAssertEqual(polyUserA.relationships, .none)
}
func test_UserAAndBEncodeEquality() {
test_DecodeEncodeEquality(type: User.self, data: poly_user_stub_1)
test_DecodeEncodeEquality(type: User.self, data: poly_user_stub_2)
}
func test_AsymmetricEncodeDecodeUserA() {
let userA = decoded(type: UserA.self, data: poly_user_stub_1)
let polyUserA = decoded(type: User.self, data: poly_user_stub_1)
let encodedPoly = try! JSONEncoder().encode(polyUserA)
XCTAssertEqual(decoded(type: UserA.self, data: encodedPoly), userA)
}
func test_AsymmetricEncodeDecodeUserB() {
let userB = decoded(type: UserB.self, data: poly_user_stub_2)
let polyUserB = decoded(type: User.self, data: poly_user_stub_2)
let encodedPoly = try! JSONEncoder().encode(polyUserB)
XCTAssertEqual(decoded(type: UserB.self, data: encodedPoly), userB)
}
func test_UserBDecode() {
let polyUserB = decoded(type: User.self, data: poly_user_stub_2)
let userB = decoded(type: UserB.self, data: poly_user_stub_2)
XCTAssertEqual(polyUserB.userB, userB)
XCTAssertNil(polyUserB.userA)
XCTAssertEqual(polyUserB[\.name], "Ken Less")
XCTAssertEqual(polyUserB.id, "2")
XCTAssertEqual(polyUserB.relationships, .none)
}
}
// MARK: - Test types
public extension PolyProxyTests {
public enum UserDescription1: EntityDescription {
public static var type: String { return "users" }
public struct Attributes: JSONAPI.Attributes {
let firstName: Attribute<String>
let lastName: Attribute<String>
}
public typealias Relationships = NoRelationships
}
public enum UserDescription2: EntityDescription {
public static var type: String { return "users" }
public struct Attributes: JSONAPI.Attributes {
let name: Attribute<[String]>
}
public typealias Relationships = NoRelationships
}
public typealias UserA = Entity<UserDescription1>
public typealias UserB = Entity<UserDescription2>
public typealias User = Poly2<UserA, UserB>
}
extension Poly2: EntityProxy where A == PolyProxyTests.UserA, B == PolyProxyTests.UserB {
public var userA: PolyProxyTests.UserA? {
return a
}
public var userB: PolyProxyTests.UserB? {
return b
}
public var id: Id<EntityRawIdType, PolyProxyTests.User> {
switch self {
case .a(let a):
return Id(rawValue: a.id.rawValue)
case .b(let b):
return Id(rawValue: b.id.rawValue)
}
}
public var attributes: SharedUserDescription.Attributes {
switch self {
case .a(let a):
return .init(name: .init(value: "\(a[\.firstName]) \(a[\.lastName])"))
case .b(let b):
return .init(name: .init(value: b[\.name].joined(separator: " ")))
}
}
public var relationships: NoRelationships {
return .none
}
public enum SharedUserDescription: EntityDescription {
public static var type: String { return A.type }
public struct Attributes: JSONAPI.Attributes {
let name: Attribute<String>
}
public typealias Relationships = NoRelationships
}
public typealias Description = SharedUserDescription
public typealias EntityRawIdType = A.EntityRawIdType
}
// MARK: - Test stubs
private let poly_user_stub_1 = """
{
"id": "1",
"type": "users",
"attributes": {
"firstName": "Ken",
"lastName": "Moore"
}
}
""".data(using: .utf8)!
private let poly_user_stub_2 = """
{
"id": "2",
"type": "users",
"attributes": {
"name": ["Ken", "Less"]
}
}
""".data(using: .utf8)!
+20
View File
@@ -4,6 +4,9 @@ extension AttributeTests {
static let __allTests = [
("test_AttributeIsTransformedAttribute", test_AttributeIsTransformedAttribute),
("test_AttributeNonThrowingConstructor", test_AttributeNonThrowingConstructor),
("test_TransformedAttributeNoThrow", test_TransformedAttributeNoThrow),
("test_TransformedAttributeReversNoThrow", test_TransformedAttributeReversNoThrow),
("test_TransformedAttributeThrows", test_TransformedAttributeThrows),
]
}
@@ -54,6 +57,8 @@ extension DocumentTests {
("test_singleDocumentNoIncludes", test_singleDocumentNoIncludes),
("test_singleDocumentNoIncludes_encode", test_singleDocumentNoIncludes_encode),
("test_singleDocumentNoIncludesMissingMetadata", test_singleDocumentNoIncludesMissingMetadata),
("test_singleDocumentNoIncludesOptionalNotNull", test_singleDocumentNoIncludesOptionalNotNull),
("test_singleDocumentNoIncludesOptionalNotNull_encode", test_singleDocumentNoIncludesOptionalNotNull_encode),
("test_singleDocumentNoIncludesWithLinks", test_singleDocumentNoIncludesWithLinks),
("test_singleDocumentNoIncludesWithLinks_encode", test_singleDocumentNoIncludesWithLinks_encode),
("test_singleDocumentNoIncludesWithMetadata", test_singleDocumentNoIncludesWithMetadata),
@@ -63,6 +68,7 @@ extension DocumentTests {
("test_singleDocumentNoIncludesWithMetadataWithLinks_encode", test_singleDocumentNoIncludesWithMetadataWithLinks_encode),
("test_singleDocumentNoIncludesWithSomeIncludesMetadataWithLinks_encode", test_singleDocumentNoIncludesWithSomeIncludesMetadataWithLinks_encode),
("test_singleDocumentNoIncludesWithSomeIncludesWithMetadataWithLinks", test_singleDocumentNoIncludesWithSomeIncludesWithMetadataWithLinks),
("test_singleDocumentNonOptionalFailsOnNull", test_singleDocumentNonOptionalFailsOnNull),
("test_singleDocumentNull", test_singleDocumentNull),
("test_singleDocumentNull_encode", test_singleDocumentNull_encode),
("test_singleDocumentSomeIncludes", test_singleDocumentSomeIncludes),
@@ -113,6 +119,7 @@ extension EntityTests {
("test_EntitySomeRelationshipsNoAttributes_encode", test_EntitySomeRelationshipsNoAttributes_encode),
("test_EntitySomeRelationshipsSomeAttributes", test_EntitySomeRelationshipsSomeAttributes),
("test_EntitySomeRelationshipsSomeAttributes_encode", test_EntitySomeRelationshipsSomeAttributes_encode),
("test_initialization", test_initialization),
("test_IntOver10_encode", test_IntOver10_encode),
("test_IntOver10_failure", test_IntOver10_failure),
("test_IntOver10_success", test_IntOver10_success),
@@ -180,6 +187,17 @@ extension LinksTests {
]
}
extension PolyProxyTests {
static let __allTests = [
("test_AsymmetricEncodeDecodeUserA", test_AsymmetricEncodeDecodeUserA),
("test_AsymmetricEncodeDecodeUserB", test_AsymmetricEncodeDecodeUserB),
("test_generalReasonableness", test_generalReasonableness),
("test_UserAAndBEncodeEquality", test_UserAAndBEncodeEquality),
("test_UserADecode", test_UserADecode),
("test_UserBDecode", test_UserBDecode),
]
}
extension PolyTests {
static let __allTests = [
("test_init_Poly0", test_init_Poly0),
@@ -223,6 +241,7 @@ extension Relationship_LiteralTests {
static let __allTests = [
("test_ArrayLiteral", test_ArrayLiteral),
("test_NilLiteral", test_NilLiteral),
("test_StringLiteral", test_StringLiteral),
]
}
@@ -250,6 +269,7 @@ public func __allTests() -> [XCTestCaseEntry] {
testCase(Id_LiteralTests.__allTests),
testCase(IncludedTests.__allTests),
testCase(LinksTests.__allTests),
testCase(PolyProxyTests.__allTests),
testCase(PolyTests.__allTests),
testCase(RelationshipTests.__allTests),
testCase(Relationship_LiteralTests.__allTests),