Compare commits

...

21 Commits

Author SHA1 Message Date
Mathew Polzin 661ff6eca5 A little renaming and easier access to important types under the JSONAPIDocument protocol 2018-11-27 18:20:01 -08:00
Mathew Polzin e36180c9b9 Make properties of Body.Data public 2018-11-27 18:05:02 -08:00
Mathew Polzin abee0c4d0e Add body variable to JSONAPIDocument protocol 2018-11-27 17:55:56 -08:00
Mathew Polzin 9c8b2fbebb Renamed JSONAPIDocument to Document and created a protocol describing a JSONAPI Document. 2018-11-27 17:35:13 -08:00
Mathew Polzin a9ef71f383 indentation change 2018-11-27 17:15:15 -08:00
Mathew Polzin 232554ec51 Wrap JSONAPIDocument body up a bit nicer for use when the JSONAPIDocument does not represent errors. 2018-11-27 16:12:36 -08:00
Mathew Polzin 8f36bb40e3 Add note about the library being a bit 'opinionated' 2018-11-27 14:04:53 -08:00
Mathew Polzin 55f2f52676 minor README change 2018-11-27 13:59:01 -08:00
Mathew Polzin dd5f3b1737 Added check for nullable array attributes 2018-11-27 13:57:55 -08:00
Mathew Polzin 7d7b3d7f19 Update linuxmain 2018-11-27 13:34:22 -08:00
Mathew Polzin aedd5dc29b Add Entity validation via a function in JSONAPITestLib. 2018-11-27 13:33:55 -08:00
Mathew Polzin 3964202ea2 Mention JSONAPITestLib in the README 2018-11-27 11:53:21 -08:00
Mathew Polzin dcabafd583 Add a playground page to show off JSONAPITestLib and add nil literal expressibility to ToOneRelationship 2018-11-27 11:45:15 -08:00
Mathew Polzin 9df9efc2dc rename NoRelatives to NoRelationships to better pair with NoAttributes and NoLinks 2018-11-27 11:08:53 -08:00
Mathew Polzin a55a4cbed0 Added test library, attribute literal expressibility, and tests 2018-11-27 10:54:51 -08:00
Mathew Polzin 6f6ed87b4c Update included playground 2018-11-27 09:14:47 -08:00
Mathew Polzin 59ff145aa8 Rename the included Error type. 2018-11-27 09:11:13 -08:00
Mathew Polzin 20a685e67b Add goal of making it easier to construct Attributes values 2018-11-26 22:29:31 -08:00
Mathew Polzin 3d8d10584b Add typealiases to make accessing Entity Relationships and Attributes types more convenient 2018-11-26 22:27:10 -08:00
Mathew Polzin b93580c900 Allow ManyResourceBody to use any PrimaryResource type 2018-11-26 18:57:34 -08:00
Mathew Polzin 8927938d56 Rename URL to JSONAPIURL to avoid conflict with Foundation.URL 2018-11-26 18:29:24 -08:00
31 changed files with 845 additions and 170 deletions
@@ -0,0 +1,24 @@
//: [Previous](@previous)
import Foundation
import JSONAPI
import JSONAPITestLib
/*******
Please enjoy these examples, but allow me the forced casting and the lack of error checking for the sake of brevity.
********/
// MARK: - Literal Expressibility
// The JSONAPITestLib 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))
// MARK: - JSON API structure checking
// The JSONAPITestLib 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
// your unit tests that create Entities.
try Dog.check(dog)
@@ -11,7 +11,7 @@ Please enjoy these examples, but allow me the forced casting and the lack of err
// MARK: - Create a request or response body with one Dog in it
let dogFromCode = try! Dog(name: "Buddy", owner: nil)
typealias SingleDogDocument = JSONAPIDocument<SingleResourceBody<Dog>, NoIncludes, BasicJSONAPIError>
typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>
let singleDogDocument = SingleDogDocument(body: SingleResourceBody(entity: dogFromCode))
@@ -19,7 +19,7 @@ let singleDogData = try! JSONEncoder().encode(singleDogDocument)
// MARK: - Parse a request or response body with one Dog in it
let dogResponse = try! JSONDecoder().decode(SingleDogDocument.self, from: singleDogData)
let dogFromData = dogResponse.body.data?.primary.value
let dogFromData = dogResponse.body.primaryData?.value
// MARK: - Create a request or response with multiple people and dogs and houses included
let personIds = [Person.Identifier(), Person.Identifier()]
@@ -27,7 +27,7 @@ let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner
let houses = [House(), House()]
let people = try! [Person(id: personIds[0], name: ["Gary", "Doe"], favoriteColor: "Orange-Red", friends: [], dogs: [dogs[0], dogs[1]], home: houses[0]), Person(id: personIds[1], name: ["Elise", "Joy"], favoriteColor: "Red", friends: [], dogs: [dogs[2]], home: houses[1])]
typealias BatchPeopleDocument = JSONAPIDocument<ManyResourceBody<Person>, Include2<Dog, House>, BasicJSONAPIError>
typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, UnknownJSONAPIError>
let includes = dogs.map { BatchPeopleDocument.Include($0) } + houses.map { BatchPeopleDocument.Include($0) }
let batchPeopleDocument = BatchPeopleDocument(body: .init(entities: people), includes: .init(values: includes))
@@ -36,6 +36,24 @@ let batchPeopleData = try! JSONEncoder().encode(batchPeopleDocument)
// MARK: - Parse a request or response body with multiple people in it and dogs and houses included
let peopleResponse = try! JSONDecoder().decode(BatchPeopleDocument.self, from: batchPeopleData)
let peopleFromData = peopleResponse.body.data?.primary.values
let dogsFromData = peopleResponse.body.data?.included[Dog.self]
let housesFromData = peopleResponse.body.data?.included[House.self]
let peopleFromData = peopleResponse.body.primaryData?.values
let dogsFromData = peopleResponse.body.includes?[Dog.self]
let housesFromData = peopleResponse.body.includes?[House.self]
// MARK: - Pass successfully parsed body to other parts of the code
if case let .data(bodyData) = peopleResponse.body {
print(bodyData)
} else {
print("no body data")
}
// MARK: - Work in the abstract
func process<T: JSONAPIDocument>(document: T) {
guard case let .data(body) = document.body else {
return
}
let x: T.BodyData = body
}
process(document: peopleResponse)
+20 -1
View File
@@ -34,12 +34,23 @@ public enum PersonDescription: EntityDescription {
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<[String]>
public let favoriteColor: Attribute<String>
public init(name: Attribute<[String]>, favoriteColor: Attribute<String>) {
self.name = name
self.favoriteColor = favoriteColor
}
}
public struct Relationships: JSONAPI.Relationships {
public let friends: ToManyRelationship<Person>
public let dogs: ToManyRelationship<Dog>
public let home: ToOneRelationship<House>
public init(friends: ToManyRelationship<Person>, dogs: ToManyRelationship<Dog>, home: ToOneRelationship<House>) {
self.friends = friends
self.dogs = dogs
self.home = home
}
}
}
@@ -57,10 +68,18 @@ public enum DogDescription: EntityDescription {
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
public init(name: Attribute<String>) {
self.name = name
}
}
public struct Relationships: JSONAPI.Relationships {
public let owner: ToOneRelationship<Person?>
public init(owner: ToOneRelationship<Person?>) {
self.owner = owner
}
}
}
@@ -81,7 +100,7 @@ public enum HouseDescription: EntityDescription {
public static var type: String { return "houses" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelatives
public typealias Relationships = NoRelationships
}
public typealias House = ExampleEntity<HouseDescription>
+5 -2
View File
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='macos' executeOnSourceChanges='false'>
<timeline fileName='timeline.xctimeline'/>
<playground version='6.0' target-platform='macos' executeOnSourceChanges='false'>
<pages>
<page name='Test Library'/>
<page name='Usage'/>
</pages>
</playground>
+8 -4
View File
@@ -6,23 +6,27 @@ import PackageDescription
let package = Package(
name: "JSONAPI",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "JSONAPI",
targets: ["JSONAPI"]),
.library(
name: "JSONAPITestLib",
targets: ["JSONAPITestLib"])
],
dependencies: [
// antitypical/Result without the Foundation requirement:
.package(url: "https://github.com/mattpolzin/Result", .branch("master"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "JSONAPI",
dependencies: ["Result"]),
.target(
name: "JSONAPITestLib",
dependencies: ["JSONAPI"]),
.testTarget(
name: "JSONAPITests",
dependencies: ["JSONAPI"]),
dependencies: ["JSONAPITestLib"])
],
swiftLanguageVersions: [.v4_2]
)
+27 -12
View File
@@ -13,6 +13,19 @@ The primary goals of this framework are:
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.
### 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.
## Dev Environment
### Prerequisites
1. Swift 4.2+ and Swift Package Manager
### Xcode project
To create an Xcode project for JSONAPI, run
`swift package generate-xcodeproj`
## Project Status
### Decoding
@@ -67,10 +80,10 @@ The primary goals of this framework are:
- [x] `href`
- [x] `meta`
### EntityDescription Validator (using reflection)
- [ ] Disallow optional array in `Attribute` and `Relationship` (should be empty array, not `null`).
- [ ] Only allow `Attribute` and `TransformAttribute` within `Attributes` struct.
- [ ] Only allow `ToManyRelationship` and `ToOneRelationship` within `Relationships` struct.
### Entity Validator (using reflection)
- [x] Disallow optional array in `Attribute` (should be empty array, not `null`).
- [x] Only allow `TransformedAttribute` and its derivatives within `Attributes` struct.
- [x] Only allow `ToManyRelationship` and `ToOneRelationship` within `Relationships` struct.
### Strict Decoding/Encoding Settings
- [ ] Error (potentially while still encoding/decoding successfully) if an included entity is not related to a primary entity (Turned off by default).
@@ -86,10 +99,9 @@ The primary goals of this framework are:
- [ ] Property-based testing (using `SwiftCheck`)
- [x] Roll my own `Result` or find an alternative that doesn't use `Foundation`.
- [ ] Create more descriptive errors that are easier to use for troubleshooting.
- [x] Make it easier to construct `Attributes` and `Relationships` values in test cases (literal expressibility).
## Usage
### Prerequisites
1. Swift 4.2+ and Swift Package Manager
### `EntityDescription`
@@ -113,7 +125,7 @@ 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".
2. A `struct` of `Attributes` **- OR -** `typealias Attributes = NoAttributes`
3. A `struct` of `Relationships` **- OR -** `typealias Relationships = NoRelatives`
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.
@@ -188,7 +200,7 @@ let nullableRelative: ToOneRelationship<Person?>
An entity that does not have relationships can be described by adding the following to an `EntityDescription`:
```
typealias Relationships = NoRelatives
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):
@@ -250,7 +262,7 @@ The entirety of a JSON API request or response is encoded or decoded from- or to
```
let decoder = JSONDecoder()
let responseStructure = JSONAPIDocument<SingleResourceBody<Person>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self
let responseStructure = JSONAPIDocument<SingleResourceBody<Person>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self
let document = try decoder.decode(responseStructure, from: data)
```
@@ -279,9 +291,9 @@ The second generic type of a `JSONAPIDocument` is a `Meta`. This structure is en
You would then create the following `Meta` type:
```
struct PageMetadata: JSONAPI.Meta {
let total: Int
let limit: Int
let offset: Int
let total: Int
let limit: Int
let offset: Int
}
```
@@ -321,3 +333,6 @@ 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.
+68 -46
View File
@@ -5,6 +5,19 @@
// Created by Mathew Polzin on 11/5/18.
//
public protocol JSONAPIDocument: Codable, Equatable {
associatedtype PrimaryResourceBody: JSONAPI.ResourceBody
associatedtype MetaType: JSONAPI.Meta
associatedtype LinksType: JSONAPI.Links
associatedtype IncludeType: JSONAPI.Include
associatedtype Error: JSONAPIError
typealias Body = Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, Error>.Body
typealias BodyData = Body.Data
var body: Body { get }
}
/// A JSON API Document represents the entire body
/// of a JSON API request or the entire body of
/// a JSON API response.
@@ -12,7 +25,7 @@
/// API uses snake case, you will want to use
/// a conversion such as the one offerred by the
/// Foundation JSONEncoder/Decoder: `KeyDecodingStrategy`
public struct JSONAPIDocument<ResourceBody: JSONAPI.ResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, Error: JSONAPIError>: Equatable {
public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, Error: JSONAPIError>: JSONAPIDocument {
public typealias Include = IncludeType
public let body: Body
@@ -20,7 +33,14 @@ public struct JSONAPIDocument<ResourceBody: JSONAPI.ResourceBody, MetaType: JSON
public enum Body: Equatable {
case errors([Error], meta: MetaType?, links: LinksType?)
case data(primary: ResourceBody, included: Includes<Include>, meta: MetaType, links: LinksType)
case data(Data)
public struct Data: Equatable {
public let primary: PrimaryResourceBody
public let includes: Includes<Include>
public let meta: MetaType
public let links: LinksType
}
public var isError: Bool {
guard case .errors = self else { return false }
@@ -32,20 +52,21 @@ public struct JSONAPIDocument<ResourceBody: JSONAPI.ResourceBody, MetaType: JSON
return errors
}
public var primaryData: ResourceBody? {
guard case let .data(primary: body, included: _, meta: _, links: _) = self else { return nil }
return body
public var primaryData: PrimaryResourceBody? {
guard case let .data(data) = self else { return nil }
return data.primary
}
public var includes: Includes<Include>? {
guard case let .data(primary: _, included: includes, meta: _, links: _) = self else { return nil }
return includes
guard case let .data(data) = self else { return nil }
return data.includes
}
public var meta: MetaType? {
switch self {
case .data(primary: _, included: _, meta: let metadata, links: _),
.errors(_, meta: let metadata?, links: _):
case .data(let data):
return data.meta
case .errors(_, meta: let metadata?, links: _):
return metadata
default:
return nil
@@ -54,8 +75,9 @@ public struct JSONAPIDocument<ResourceBody: JSONAPI.ResourceBody, MetaType: JSON
public var links: LinksType? {
switch self {
case .data(primary: _, included: _, meta: _, links: let links),
.errors(_, meta: _, links: let links?):
case .data(let data):
return data.links
case .errors(_, meta: _, links: let links?):
return links
default:
return nil
@@ -67,54 +89,54 @@ public struct JSONAPIDocument<ResourceBody: JSONAPI.ResourceBody, MetaType: JSON
body = .errors(errors, meta: meta, links: links)
}
public init(body: ResourceBody, includes: Includes<Include>, meta: MetaType, links: LinksType) {
self.body = .data(primary: body, included: includes, meta: meta, links: links)
public init(body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType, links: LinksType) {
self.body = .data(.init(primary: body, includes: includes, meta: meta, links: links))
}
}
extension JSONAPIDocument where IncludeType == NoIncludes {
public init(body: ResourceBody, meta: MetaType, links: LinksType) {
self.body = .data(primary: body, included: .none, meta: meta, links: links)
extension Document where IncludeType == NoIncludes {
public init(body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
self.body = .data(.init(primary: body, includes: .none, meta: meta, links: links))
}
}
extension JSONAPIDocument where MetaType == NoMetadata {
public init(body: ResourceBody, includes: Includes<Include>, links: LinksType) {
self.body = .data(primary: body, included: includes, meta: .none, links: links)
extension Document where MetaType == NoMetadata {
public init(body: PrimaryResourceBody, includes: Includes<Include>, links: LinksType) {
self.body = .data(.init(primary: body, includes: includes, meta: .none, links: links))
}
}
extension JSONAPIDocument where LinksType == NoLinks {
public init(body: ResourceBody, includes: Includes<Include>, meta: MetaType) {
self.body = .data(primary: body, included: includes, meta: meta, links: .none)
extension Document where LinksType == NoLinks {
public init(body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType) {
self.body = .data(.init(primary: body, includes: includes, meta: meta, links: .none))
}
}
extension JSONAPIDocument where IncludeType == NoIncludes, LinksType == NoLinks {
public init(body: ResourceBody, meta: MetaType) {
self.body = .data(primary: body, included: .none, meta: meta, links: .none)
extension Document where IncludeType == NoIncludes, LinksType == NoLinks {
public init(body: PrimaryResourceBody, meta: MetaType) {
self.body = .data(.init(primary: body, includes: .none, meta: meta, links: .none))
}
}
extension JSONAPIDocument where IncludeType == NoIncludes, MetaType == NoMetadata {
public init(body: ResourceBody, links: LinksType) {
self.body = .data(primary: body, included: .none, meta: .none, links: links)
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata {
public init(body: PrimaryResourceBody, links: LinksType) {
self.body = .data(.init(primary: body, includes: .none, meta: .none, links: links))
}
}
extension JSONAPIDocument where MetaType == NoMetadata, LinksType == NoLinks {
public init(body: ResourceBody, includes: Includes<Include>) {
self.body = .data(primary: body, included: includes, meta: .none, links: .none)
extension Document where MetaType == NoMetadata, LinksType == NoLinks {
public init(body: PrimaryResourceBody, includes: Includes<Include>) {
self.body = .data(.init(primary: body, includes: includes, meta: .none, links: .none))
}
}
extension JSONAPIDocument where IncludeType == NoIncludes, MetaType == NoMetadata, LinksType == NoLinks {
public init(body: ResourceBody) {
self.body = .data(primary: body, included: .none, meta: .none, links: .none)
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, LinksType == NoLinks {
public init(body: PrimaryResourceBody) {
self.body = .data(.init(primary: body, includes: .none, meta: .none, links: .none))
}
}
extension JSONAPIDocument: Codable {
extension Document {
private enum RootCodingKeys: String, CodingKey {
case data
case errors
@@ -157,11 +179,11 @@ extension JSONAPIDocument: Codable {
return
}
let data: ResourceBody
if let noData = NoResourceBody() as? ResourceBody {
let data: PrimaryResourceBody
if let noData = NoResourceBody() as? PrimaryResourceBody {
data = noData
} else {
data = try container.decode(ResourceBody.self, forKey: .data)
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
}
let maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
@@ -175,7 +197,7 @@ extension JSONAPIDocument: Codable {
throw JSONAPIEncodingError.missingOrMalformedLinks
}
body = .data(primary: data, included: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal)
body = .data(.init(primary: data, includes: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal))
}
public func encode(to encoder: Encoder) throws {
@@ -199,19 +221,19 @@ extension JSONAPIDocument: Codable {
try container.encode(linksVal, forKey: .links)
}
case .data(primary: let resourceBody, included: let includes, let meta, links: let links):
try container.encode(resourceBody, forKey: .data)
case .data(let data):
try container.encode(data.primary, forKey: .data)
if Include.self != NoIncludes.self {
try container.encode(includes, forKey: .included)
try container.encode(data.includes, forKey: .included)
}
if MetaType.self != NoMetadata.self {
try container.encode(meta, forKey: .meta)
try container.encode(data.meta, forKey: .meta)
}
if LinksType.self != NoLinks.self {
try container.encode(links, forKey: .links)
try container.encode(data.links, forKey: .links)
}
}
}
@@ -219,8 +241,8 @@ extension JSONAPIDocument: Codable {
// MARK: - CustomStringConvertible
extension JSONAPIDocument: CustomStringConvertible {
extension Document: CustomStringConvertible {
public var description: String {
return "JSONAPIDocument(body: \(String(describing: body))"
return "Document(body: \(String(describing: body))"
}
}
+2 -2
View File
@@ -9,7 +9,7 @@ public protocol JSONAPIError: Swift.Error, Equatable, Codable {
static var unknown: Self { get }
}
public enum BasicJSONAPIError: JSONAPIError {
public enum UnknownJSONAPIError: JSONAPIError {
case unknownError
public init(from decoder: Decoder) throws {
@@ -21,7 +21,7 @@ public enum BasicJSONAPIError: JSONAPIError {
try container.encode("unknown")
}
public static var unknown: BasicJSONAPIError {
public static var unknown: UnknownJSONAPIError {
return .unknownError
}
}
+4 -4
View File
@@ -1,5 +1,5 @@
//
// ResourceBody.swift
// PrimaryResourceBody.swift
// JSONAPI
//
// Created by Mathew Polzin on 11/10/18.
@@ -18,7 +18,7 @@ public struct SingleResourceBody<Entity: JSONAPI.PrimaryResource>: ResourceBody
}
}
public struct ManyResourceBody<Entity: JSONAPI.EntityType>: ResourceBody {
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: ResourceBody {
public let values: [Entity]
public init(entities: [Entity]) {
@@ -80,12 +80,12 @@ extension ManyResourceBody {
extension SingleResourceBody: CustomStringConvertible {
public var description: String {
return "ResourceBody(\(String(describing: value)))"
return "PrimaryResourceBody(\(String(describing: value)))"
}
}
extension ManyResourceBody: CustomStringConvertible {
public var description: String {
return "ResourceBody(\(String(describing: values)))"
return "PrimaryResourceBody(\(String(describing: values)))"
}
}
+2 -2
View File
@@ -14,9 +14,9 @@ public struct NoLinks: Links {
public init() {}
}
public protocol URL: Codable, Equatable {}
public protocol JSONAPIURL: Codable, Equatable {}
public struct Link<URL: JSONAPI.URL, Meta: JSONAPI.Meta>: Equatable, Codable {
public struct Link<URL: JSONAPI.JSONAPIURL, Meta: JSONAPI.Meta>: Equatable, Codable {
public let url: URL
public let meta: Meta
}
+13 -1
View File
@@ -5,7 +5,10 @@
// Created by Mathew Polzin on 11/13/18.
//
public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: Codable where Transformer.From == RawValue {
public protocol AttributeType: Codable {
}
public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: AttributeType where Transformer.From == RawValue {
private let rawValue: RawValue
public let value: Transformer.To
@@ -24,6 +27,15 @@ extension TransformedAttribute: CustomStringConvertible {
extension TransformedAttribute: Equatable where Transformer.From: Equatable, Transformer.To: Equatable {}
extension TransformedAttribute where Transformer == IdentityTransformer<RawValue> {
// If we are using the identity transform, we can skip the transform and guarantee no
// error is thrown.
public init(value: RawValue) {
rawValue = value
self.value = value
}
}
public typealias ValidatedAttribute<RawValue: Codable, Validator: JSONAPI.Validator> = TransformedAttribute<RawValue, Validator> where RawValue == Validator.From
public typealias Attribute<T: Codable> = TransformedAttribute<T, IdentityTransformer<T>>
+16 -13
View File
@@ -16,7 +16,7 @@ public typealias Attributes = Codable & Equatable
/// Can be used as `Relationships` Type for Entities that do not
/// have any Relationships.
public struct NoRelatives: Relationships {}
public struct NoRelationships: Relationships {}
/// Can be used as `Attributes` Type for Entities that do not
/// have any Attributes.
@@ -40,6 +40,9 @@ public protocol EntityDescription {
public protocol EntityType: PrimaryResource {
associatedtype Description: EntityDescription
associatedtype Identifier: Equatable & Codable
typealias Attributes = Description.Attributes
typealias Relationships = Description.Relationships
}
/// An `Entity` is a single model type that can be
@@ -97,27 +100,27 @@ extension Entity where Description.Attributes == NoAttributes, Identifier: Creat
}
}
extension Entity where Description.Relationships == NoRelatives {
extension Entity where Description.Relationships == NoRelationships {
public init(id: Identifier, attributes: Description.Attributes) {
self.init(id: id, attributes: attributes, relationships: NoRelatives())
self.init(id: id, attributes: attributes, relationships: NoRelationships())
}
}
extension Entity where Description.Relationships == NoRelatives, Identifier: CreatableIdType {
extension Entity where Description.Relationships == NoRelationships, Identifier: CreatableIdType {
public init(attributes: Description.Attributes) {
self.init(attributes: attributes, relationships: NoRelatives())
self.init(attributes: attributes, relationships: NoRelationships())
}
}
extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelatives {
extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships {
public init(id: Identifier) {
self.init(id: id, attributes: NoAttributes(), relationships: NoRelatives())
self.init(id: id, attributes: NoAttributes(), relationships: NoRelationships())
}
}
extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelatives, Identifier: CreatableIdType {
extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, Identifier: CreatableIdType {
public init() {
self.init(attributes: NoAttributes(), relationships: NoRelatives())
self.init(attributes: NoAttributes(), relationships: NoRelationships())
}
}
@@ -187,7 +190,7 @@ public extension Entity {
try container.encode(Entity.type, forKey: .type)
if Identifier.self != Unidentified.self {
if Identifier.self != Unidentified<Description>.self {
try container.encode(id, forKey: .id)
}
@@ -195,7 +198,7 @@ public extension Entity {
try container.encode(attributes, forKey: .attributes)
}
if Description.Relationships.self != NoRelatives.self {
if Description.Relationships.self != NoRelationships.self {
try container.encode(relationships, forKey: .relationships)
}
}
@@ -210,10 +213,10 @@ public extension Entity {
throw JSONAPIEncodingError.typeMismatch(expected: Description.type, found: type)
}
id = try (Unidentified() as? Identifier) ?? container.decode(Identifier.self, forKey: .id)
id = try (Unidentified<Description>() as? Identifier) ?? container.decode(Identifier.self, forKey: .id)
attributes = try (NoAttributes() as? Description.Attributes) ?? container.decode(Description.Attributes.self, forKey: .attributes)
relationships = try (NoRelatives() as? Description.Relationships) ?? container.decode(Description.Relationships.self, forKey: .relationships)
relationships = try (NoRelationships() as? Description.Relationships) ?? container.decode(Description.Relationships.self, forKey: .relationships)
}
}
+4 -3
View File
@@ -22,16 +22,17 @@ public protocol CreatableRawIdType: RawIdType {
extension String: RawIdType {}
public protocol Identifier: Codable, Equatable {}
public protocol Identifier: Codable, Equatable {
associatedtype EntityDescription: JSONAPI.EntityDescription
}
public struct Unidentified: Identifier, CustomStringConvertible {
public struct Unidentified<EntityDescription: JSONAPI.EntityDescription>: Identifier, CustomStringConvertible {
public init() {}
public var description: String { return "Id(Unidentified)" }
}
public protocol IdType: Identifier, CustomStringConvertible {
associatedtype EntityDescription: JSONAPI.EntityDescription
associatedtype RawType: RawIdType
var rawValue: RawType { get }
+4 -6
View File
@@ -5,17 +5,15 @@
// Created by Mathew Polzin on 8/31/18.
//
public protocol RelationshipType: Codable {}
/// An Entity relationship that can be encoded to or decoded from
/// a JSON API "Resource Linkage."
/// See https://jsonapi.org/format/#document-resource-object-linkage
/// A convenient typealias might make your code much more legible: `One<EntityDescription>`
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable>: Equatable, Codable {
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable>: RelationshipType, Equatable {
public let id: Relatable.WrappedIdentifier
public var ids: [Relatable.WrappedIdentifier] {
return [id]
}
public init(id: Relatable.WrappedIdentifier) {
self.id = id
@@ -38,7 +36,7 @@ extension ToOneRelationship where Relatable.WrappedIdentifier == Optional<Relata
/// a JSON API "Resource Linkage."
/// See https://jsonapi.org/format/#document-resource-object-linkage
/// A convenient typealias might make your code much more legible: `Many<EntityDescription>`
public struct ToManyRelationship<Relatable: JSONAPI.Relatable>: Equatable, Codable {
public struct ToManyRelationship<Relatable: JSONAPI.Relatable>: RelationshipType, Equatable {
public let ids: [Relatable.Identifier]
@@ -0,0 +1,96 @@
import JSONAPI
extension TransformedAttribute: ExpressibleByUnicodeScalarLiteral where RawValue: ExpressibleByUnicodeScalarLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias UnicodeScalarLiteralType = RawValue.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: RawValue.UnicodeScalarLiteralType) {
self.init(value: RawValue(unicodeScalarLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByExtendedGraphemeClusterLiteral where RawValue: ExpressibleByExtendedGraphemeClusterLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias ExtendedGraphemeClusterLiteralType = RawValue.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: RawValue.ExtendedGraphemeClusterLiteralType) {
self.init(value: RawValue(extendedGraphemeClusterLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByStringLiteral where RawValue: ExpressibleByStringLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias StringLiteralType = RawValue.StringLiteralType
public init(stringLiteral value: RawValue.StringLiteralType) {
self.init(value: RawValue(stringLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByNilLiteral where RawValue: ExpressibleByNilLiteral, Transformer == IdentityTransformer<RawValue> {
public init(nilLiteral: ()) {
self.init(value: RawValue(nilLiteral: ()))
}
}
extension TransformedAttribute: ExpressibleByFloatLiteral where RawValue: ExpressibleByFloatLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias FloatLiteralType = RawValue.FloatLiteralType
public init(floatLiteral value: RawValue.FloatLiteralType) {
self.init(value: RawValue(floatLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByBooleanLiteral where RawValue: ExpressibleByBooleanLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias BooleanLiteralType = RawValue.BooleanLiteralType
public init(booleanLiteral value: BooleanLiteralType) {
self.init(value: RawValue(booleanLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByIntegerLiteral where RawValue: ExpressibleByIntegerLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias IntegerLiteralType = RawValue.IntegerLiteralType
public init(integerLiteral value: IntegerLiteralType) {
self.init(value: RawValue(integerLiteral: value))
}
}
// regretably, array and dictionary literals are not so easy because Dictionaries and Arrays
// cannot be turned back into variadic arguments to pass onto the RawValue type's constructor.
// we can still provide a case for the Array and Dictionary types, though.
public protocol DictionaryType {
associatedtype Key: Hashable
associatedtype Value
init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value)
}
extension Dictionary: DictionaryType {}
extension TransformedAttribute: ExpressibleByDictionaryLiteral where RawValue: DictionaryType, Transformer == IdentityTransformer<RawValue> {
public typealias Key = RawValue.Key
public typealias Value = RawValue.Value
public init(dictionaryLiteral elements: (RawValue.Key, RawValue.Value)...) {
// we arbitrarily keep the first value if two values are assigned to the same key
self.init(value: RawValue(elements, uniquingKeysWith: { val, _ in val }))
}
}
public protocol ArrayType {
associatedtype Element
init<S>(_ s: S) where Element == S.Element, S : Sequence
}
extension Array: ArrayType {}
extension ArraySlice: ArrayType {}
extension TransformedAttribute: ExpressibleByArrayLiteral where RawValue: ArrayType, Transformer == IdentityTransformer<RawValue> {
public typealias ArrayLiteralElement = RawValue.Element
public init(arrayLiteral elements: ArrayLiteralElement...) {
self.init(value: RawValue(elements))
}
}
+75
View File
@@ -0,0 +1,75 @@
//
// EntityCheck.swift
// JSONAPITestLib
//
// Created by Mathew Polzin on 11/27/18.
//
import JSONAPI
public enum EntityCheckError: Swift.Error {
case attributesNotStruct
case relationshipsNotStruct
case nonAttribute(named: String)
case nonRelationship(named: String)
case nullArray(named: String)
case badId
}
public struct EntityCheckErrors: Swift.Error {
let problems: [EntityCheckError]
}
private protocol OptionalAttributeType {}
extension Optional: OptionalAttributeType where Wrapped: AttributeType {}
private protocol OptionalArray {}
extension Optional: OptionalArray where Wrapped: ArrayType {}
private protocol AttributeTypeWithOptionalArray {}
extension TransformedAttribute: AttributeTypeWithOptionalArray where RawValue: OptionalArray {}
private protocol OptionalRelationshipType {}
extension Optional: OptionalRelationshipType where Wrapped: RelationshipType {}
public extension Entity {
public static func check(_ entity: Entity) throws {
var problems = [EntityCheckError]()
if Swift.type(of: entity.id).EntityDescription.self != Description.self {
problems.append(.badId)
}
let attributesMirror = Mirror(reflecting: entity.attributes)
if attributesMirror.displayStyle != .`struct` {
problems.append(.attributesNotStruct)
}
for attribute in attributesMirror.children {
if attribute.value as? AttributeType == nil,
attribute.value as? OptionalAttributeType == nil {
problems.append(.nonAttribute(named: attribute.label ?? "unnamed"))
}
if attribute.value as? AttributeTypeWithOptionalArray != nil {
problems.append(.nullArray(named: attribute.label ?? "unnamed"))
}
}
let relationshipsMirror = Mirror(reflecting: entity.relationships)
if relationshipsMirror.displayStyle != .`struct` {
problems.append(.relationshipsNotStruct)
}
for relationship in relationshipsMirror.children {
if relationship.value as? RelationshipType == nil {
problems.append(.nonRelationship(named: relationship.label ?? "unnamed"))
}
}
guard problems.count == 0 else {
throw EntityCheckErrors(problems: problems)
}
}
}
+40
View File
@@ -0,0 +1,40 @@
//
// Id+Literal.swift
// JSONAPI
//
// Created by Mathew Polzin on 11/27/18.
//
import JSONAPI
extension Id: ExpressibleByUnicodeScalarLiteral where RawType: ExpressibleByUnicodeScalarLiteral {
public typealias UnicodeScalarLiteralType = RawType.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: RawType.UnicodeScalarLiteralType) {
self.init(rawValue: RawType(unicodeScalarLiteral: value))
}
}
extension Id: ExpressibleByExtendedGraphemeClusterLiteral where RawType: ExpressibleByExtendedGraphemeClusterLiteral {
public typealias ExtendedGraphemeClusterLiteralType = RawType.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: RawType.ExtendedGraphemeClusterLiteralType) {
self.init(rawValue: RawType(extendedGraphemeClusterLiteral: value))
}
}
extension Id: ExpressibleByStringLiteral where RawType: ExpressibleByStringLiteral {
public typealias StringLiteralType = RawType.StringLiteralType
public init(stringLiteral value: RawType.StringLiteralType) {
self.init(rawValue: RawType(stringLiteral: value))
}
}
extension Id: ExpressibleByIntegerLiteral where RawType: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = RawType.IntegerLiteralType
public init(integerLiteral value: IntegerLiteralType) {
self.init(rawValue: RawType(integerLiteral: value))
}
}
@@ -0,0 +1,14 @@
//
// Relationship+Literal.swift
// JSONAPITestLib
//
// Created by Mathew Polzin on 11/27/18.
//
import JSONAPI
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
}
}
@@ -0,0 +1,21 @@
//
// AttributeTests.swift
// JSONAPITests
//
// Created by Mathew Polzin on 11/27/18.
//
import XCTest
import JSONAPI
class AttributeTests: XCTestCase {
func test_AttributeIsTransformedAttribute() {
XCTAssertEqual(try TransformedAttribute<String, IdentityTransformer<String>>(rawValue: "hello"), try Attribute<String>(rawValue: "hello"))
}
func test_AttributeNonThrowingConstructor() {
XCTAssertEqual(try Attribute<String>(rawValue: "hello"), Attribute<String>(value: "hello"))
}
}
+46 -46
View File
@@ -11,7 +11,7 @@ import JSONAPI
class DocumentTests: XCTestCase {
func test_singleDocumentNull() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_null)
XCTAssertFalse(document.body.isError)
@@ -23,12 +23,12 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNull_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_null)
}
func test_unknownErrorDocumentNoMeta() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
XCTAssertTrue(document.body.isError)
@@ -48,12 +48,12 @@ class DocumentTests: XCTestCase {
}
func test_unknownErrorDocumentNoMeta_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
}
func test_unknownErrorDocumentMissingMeta() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, data: error_document_no_metadata)
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, data: error_document_no_metadata)
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.meta)
@@ -72,11 +72,11 @@ class DocumentTests: XCTestCase {
}
func test_unknownErrorDocumentMissingMeta_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, data: error_document_no_metadata)
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, data: error_document_no_metadata)
}
func test_errorDocumentNoMeta() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, NoMetadata, NoLinks, NoIncludes, TestError>.self,
let document = decoded(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, TestError>.self,
data: error_document_no_metadata)
XCTAssertTrue(document.body.isError)
@@ -96,12 +96,12 @@ class DocumentTests: XCTestCase {
}
func test_errorDocumentNoMeta_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, NoMetadata, NoLinks, NoIncludes, TestError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, TestError>.self,
data: error_document_no_metadata)
}
func test_unknownErrorDocumentWithMeta() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_with_metadata)
XCTAssertTrue(document.body.isError)
@@ -120,12 +120,12 @@ class DocumentTests: XCTestCase {
}
func test_unknownErrorDocumentWithMeta_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_with_metadata)
}
func test_unknownErrorDocumentWithMetaWithLinks() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_with_metadata_with_links)
XCTAssertTrue(document.body.isError)
@@ -148,12 +148,12 @@ class DocumentTests: XCTestCase {
}
func test_unknownErrorDocumentWithMetaWithLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_with_metadata_with_links)
}
func test_unknownErrorDocumentWithLinks() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_with_links)
XCTAssertTrue(document.body.isError)
@@ -174,12 +174,12 @@ class DocumentTests: XCTestCase {
}
func test_unknownErrorDocumentWithLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_with_links)
}
func test_unknownErrorDocumentMissingLinks() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
XCTAssertTrue(document.body.isError)
@@ -197,12 +197,12 @@ class DocumentTests: XCTestCase {
}
func test_unknownErrorDocumentMissingLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
}
func test_metaDataDocument() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: metadata_document)
XCTAssertFalse(document.body.isError)
@@ -213,12 +213,12 @@ class DocumentTests: XCTestCase {
}
func test_metaDataDocument_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: metadata_document)
}
func test_metaDataDocumentWithLinks() {
let document = decoded(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: metadata_document_with_links)
XCTAssertFalse(document.body.isError)
@@ -233,18 +233,18 @@ class DocumentTests: XCTestCase {
}
func test_metaDataDocumentWithLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<NoResourceBody, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: metadata_document_with_links)
}
func test_metaDocumentMissingMeta() {
XCTAssertThrowsError(try JSONDecoder().decode(JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, from: metadata_document_missing_metadata))
XCTAssertThrowsError(try JSONDecoder().decode(Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, from: metadata_document_missing_metadata))
XCTAssertThrowsError(try JSONDecoder().decode(JSONAPIDocument<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, from: metadata_document_missing_metadata2))
XCTAssertThrowsError(try JSONDecoder().decode(Document<NoResourceBody, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, from: metadata_document_missing_metadata2))
}
func test_singleDocumentNoIncludes() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes)
XCTAssertFalse(document.body.isError)
@@ -256,12 +256,12 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNoIncludes_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes)
}
func test_singleDocumentNoIncludesWithMetadata() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_metadata)
XCTAssertFalse(document.body.isError)
@@ -273,12 +273,12 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNoIncludesWithMetadata_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_metadata)
}
func test_singleDocumentNoIncludesWithLinks() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_links)
XCTAssertFalse(document.body.isError)
@@ -295,12 +295,12 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNoIncludesWithLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, NoMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_links)
}
func test_singleDocumentNoIncludesWithMetadataWithLinks() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_metadata_with_links)
XCTAssertFalse(document.body.isError)
@@ -317,20 +317,20 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNoIncludesWithMetadataWithLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_metadata_with_links)
}
func test_singleDocumentNoIncludesWithMetadataMissingLinks() {
XCTAssertThrowsError(try JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, TestLinks, NoIncludes, BasicJSONAPIError>.self, from: single_document_no_includes_with_metadata))
XCTAssertThrowsError(try JSONDecoder().decode(Document<SingleResourceBody<Article>, TestPageMetadata, TestLinks, NoIncludes, UnknownJSONAPIError>.self, from: single_document_no_includes_with_metadata))
}
func test_singleDocumentNoIncludesMissingMetadata() {
XCTAssertThrowsError(try JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, from: single_document_no_includes))
XCTAssertThrowsError(try JSONDecoder().decode(Document<SingleResourceBody<Article>, TestPageMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, from: single_document_no_includes))
}
func test_singleDocumentSomeIncludes() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes)
XCTAssertFalse(document.body.isError)
@@ -343,12 +343,12 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentSomeIncludes_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes)
}
func test_singleDocumentSomeIncludesWithMetadata() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes_with_metadata)
XCTAssertFalse(document.body.isError)
@@ -362,12 +362,12 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentSomeIncludesWithMetadata_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes_with_metadata)
}
func test_singleDocumentNoIncludesWithSomeIncludesWithMetadataWithLinks() {
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, TestLinks, Include1<Author>, BasicJSONAPIError>.self,
let document = decoded(type: Document<SingleResourceBody<Article>, TestPageMetadata, TestLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes_with_metadata_with_links)
XCTAssertFalse(document.body.isError)
@@ -385,24 +385,24 @@ class DocumentTests: XCTestCase {
}
func test_singleDocumentNoIncludesWithSomeIncludesMetadataWithLinks_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Article>, TestPageMetadata, TestLinks, Include1<Author>, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Article>, TestPageMetadata, TestLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: single_document_some_includes_with_metadata_with_links)
}
func test_singleDocument_PolyPrimaryResource() {
let article = Article(id: Id(rawValue: "1"), relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))))
let document = decoded(type: JSONAPIDocument<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, data: single_document_no_includes)
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])
}
func test_singleDocument_PolyPrimaryResource_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self, data: single_document_no_includes)
test_DecodeEncodeEquality(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self, data: single_document_no_includes)
}
func test_manyDocumentNoIncludes() {
let document = decoded(type: JSONAPIDocument<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
let document = decoded(type: Document<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: many_document_no_includes)
XCTAssertFalse(document.body.isError)
@@ -416,12 +416,12 @@ class DocumentTests: XCTestCase {
}
func test_manyDocumentNoIncludes_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self,
data: many_document_no_includes)
}
func test_manyDocumentSomeIncludes() {
let document = decoded(type: JSONAPIDocument<ManyResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, BasicJSONAPIError>.self,
let document = decoded(type: Document<ManyResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: many_document_some_includes)
XCTAssertFalse(document.body.isError)
@@ -439,7 +439,7 @@ class DocumentTests: XCTestCase {
}
func test_manyDocumentSomeIncludes_encode() {
test_DecodeEncodeEquality(type: JSONAPIDocument<ManyResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, BasicJSONAPIError>.self,
test_DecodeEncodeEquality(type: Document<ManyResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, UnknownJSONAPIError>.self,
data: many_document_some_includes)
}
}
@@ -450,7 +450,7 @@ extension DocumentTests {
static var type: String { return "authors" }
typealias Attributes = NoAttributes
typealias Relationships = NoRelatives
typealias Relationships = NoRelationships
}
typealias Author = Entity<AuthorType>
@@ -514,4 +514,4 @@ extension DocumentTests {
}
}
extension String: JSONAPI.URL {}
extension String: JSONAPI.JSONAPIURL {}

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