Compare commits

...

3 Commits

25 changed files with 863 additions and 92 deletions
@@ -0,0 +1,173 @@
import Foundation
import JSONAPI
// MARK: - Preamble (setup)
// We make String a CreatableRawIdType. This is actually done in
// this Playground's Entities.swift file, so it is commented out here.
/*
var GlobalStringId: Int = 0
extension String: CreatableRawIdType {
public static func unique() -> String {
GlobalStringId += 1
return String(GlobalStringId)
}
}
*/
// We create a typealias given that we do not expect JSON:API Resource
// Objects for this particular API to have Metadata or Links associated
// with them. We also expect them to have String Identifiers.
typealias JSONEntity<Description: EntityDescription> = JSONAPI.Entity<Description, NoMetadata, NoLinks, String>
// Similarly, we create a typealias for unidentified entities. JSON:API
// only allows unidentified entities (i.e. no "id" field) for client
// requests that create new entities. In these situations, the server
// is expected to assign the new entity a unique ID.
typealias UnidentifiedJSONEntity<Description: EntityDescription> = JSONAPI.Entity<Description, NoMetadata, NoLinks, Unidentified>
// We create typealiases given that we do not expect JSON:API Relationships
// for this particular API to have Metadata or Links associated
// with them.
typealias ToOneRelationship<Entity: Identifiable> = JSONAPI.ToOneRelationship<Entity, NoMetadata, NoLinks>
typealias ToManyRelationship<Entity: Relatable> = JSONAPI.ToManyRelationship<Entity, NoMetadata, NoLinks>
// We create a typealias for a Document given that we do not expect
// JSON:API Documents for this particular API to have Metadata, Links,
// useful Errors, or a JSON:API Object (i.e. APIDescription).
typealias Document<PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: JSONAPI.Include> = JSONAPI.Document<PrimaryResourceBody, NoMetadata, NoLinks, IncludeType, NoAPIDescription, UnknownJSONAPIError>
// MARK: Entity Definitions
enum AuthorDescription: EntityDescription {
public static var type: String { return "authors" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
}
public typealias Relationships = NoRelationships
}
typealias Author = JSONEntity<AuthorDescription>
enum ArticleDescription: EntityDescription {
public static var type: String { return "articles" }
public struct Attributes: JSONAPI.Attributes {
public let title: Attribute<String>
public let abstract: Attribute<String>
}
public struct Relationships: JSONAPI.Relationships {
public let author: ToOneRelationship<Author>
}
}
typealias Article = JSONEntity<ArticleDescription>
// MARK: Document Definitions
// We create a typealias to represent a document containing one Article
// and including its Author
typealias SingleArticleDocumentWithIncludes = Document<SingleResourceBody<Article>, Include1<Author>>
// ... and a typealias to represent a document containing one Article and
// not including any related entities.
typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoIncludes>
// MARK: - Server Pseudo-example
// Skipping over all the API and database stuff, here's a chunk of code
// that creates a document. Note that this document is the entirety
// of a JSON:API response body.
func articleDocument(includeAuthor: Bool) -> Either<SingleArticleDocument, SingleArticleDocumentWithIncludes> {
// Let's pretend all of this is coming from a database:
let authorId = Author.Identifier(rawValue: "1234")
let article = Article(id: .init(rawValue: "5678"),
attributes: .init(title: .init(value: "JSON:API in Swift"),
abstract: .init(value: "Not yet written")),
relationships: .init(author: .init(id: authorId)),
meta: .none,
links: .none)
let document = SingleArticleDocument(apiDescription: .none,
body: .init(entity: article),
includes: .none,
meta: .none,
links: .none)
switch includeAuthor {
case false:
return .a(document)
case true:
let author = Author(id: authorId,
attributes: .init(name: .init(value: "Janice Bluff")),
relationships: .none,
meta: .none,
links: .none)
let includes: Includes<SingleArticleDocumentWithIncludes.Include> = .init(values: [.init(author)])
return .b(document.including(.init(values: [.init(author)])))
}
}
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
let responseBody = articleDocument(includeAuthor: true)
let responseData = try! encoder.encode(responseBody)
// Next step would be encoding and setting as the HTTP body of a response.
// we will just print it out instead:
print("-----")
print(String(data: responseData, encoding: .utf8)!)
// ... and if we had received a request for an article without
// including the author:
let otherResponseBody = articleDocument(includeAuthor: false)
let otherResponseData = try! encoder.encode(otherResponseBody)
print("-----")
print(String(data: otherResponseData, encoding: .utf8)!)
// MARK: - Client Pseudo-example
enum NetworkError: Swift.Error {
case serverError
case quantityMismatch
}
// Skipping over all the API stuff, here's a chunk of code that will
// decode a document. We will assume we have made a request for a
// single article including the author.
func docode(articleResponseData: Data) throws -> (article: Article, author: Author) {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let articleDocument = try decoder.decode(SingleArticleDocumentWithIncludes.self, from: articleResponseData)
switch articleDocument.body {
case .data(let data):
let authors = data.includes[Author.self]
guard authors.count == 1 else {
throw NetworkError.quantityMismatch
}
return (article: data.primary.value, author: authors[0])
case .errors(let errors, meta: _, links: _):
throw NetworkError.serverError
}
}
let response = try! docode(articleResponseData: responseData)
// Next step would be to do something useful with the article and author but we will print them instead.
print("-----")
print(response.article)
print(response.author)
@@ -0,0 +1,213 @@
import Foundation
import JSONAPI
/*
This is not realistically what JSONAPI code will look like. It makes
every bit of sense to typealias the long-winded generics of this
framework to make working with them more concise and readable.
The purpose of this example, then, is not to show "good" JSONAPI
code, but rather to generate a JSONAPI example document that uses
all the various (often optional) parts of a JSONAPI document.
The JSON example produced in this manner can be used to compare the
nomenclature of this library with the nomenclature of the JSON:API Spec.
*/
// MARK: - Extensions
extension Foundation.URL: JSONAPIURL {}
// extension String: CreatableRawIdType {} found in playground Entities.swift file.
// MARK: - Definitions
/// Metadata associated with entities
struct EntityMetadata: JSONAPI.Meta {
let creationDate: Date
let updatedDate: Date
}
/// Metadata associated with links
struct LinkMeta: JSONAPI.Meta {
/// Don't trust this link past the expiry date.
let expiry: Date
}
/// Links associated with entities
struct EntityLinks: JSONAPI.Links {
let `self`: Link<Foundation.URL, LinkMeta>
init(selfLink: Link<Foundation.URL, LinkMeta>) {
self.`self` = selfLink
}
}
/// Metadata associated with relationships
struct ToManyRelationshipMetadata: JSONAPI.Meta {
/// Pagination for the relationship (to support really large
/// to-many relationships)
let pagination: Pagination
struct Pagination: Codable, Equatable {
let total: Int
let limit: Int
let offset: Int
}
}
/// Links associated with relationships
struct ToManyRelationshipLinks: JSONAPI.Links {
/// next page of relationships.
let next: Link<Foundation.URL, LinkMeta>
}
/// Description of an Author entity.
enum AuthorDescription: EntityDescription {
static var type: String { return "authors" }
struct Attributes: JSONAPI.Attributes {
let name: Attribute<String>
}
struct Relationships: JSONAPI.Relationships {
let articles: ToManyRelationship<Article, ToManyRelationshipMetadata, ToManyRelationshipLinks>
}
}
typealias Author = JSONAPI.Entity<AuthorDescription, EntityMetadata, EntityLinks, String>
/// Description of an Article entity.
enum ArticleDescription: EntityDescription {
static var type: String { return "articles" }
struct Attributes: JSONAPI.Attributes {
let title: Attribute<String>
}
struct Relationships: JSONAPI.Relationships {
/// The primary attributed author of the article.
let primaryAuthor: ToOneRelationship<Author, NoMetadata, NoLinks>
/// All authors excluding the primary author.
/// It is customary to print the primary author's
/// name first, followed by the other authors.
let otherAuthors: ToManyRelationship<Author, ToManyRelationshipMetadata, ToManyRelationshipLinks>
}
}
typealias Article = JSONAPI.Entity<ArticleDescription, EntityMetadata, EntityLinks, String>
/// Metadata associated with the API Description
struct APIDescriptionMetadata: JSONAPI.Meta {
/// A clever codename for this API version.
let codename: String
}
/// Metadata associated with any Document
struct DocumentMetadata: JSONAPI.Meta {
/// Assuming caching is in play, this is when
/// the cached document being returned was created.
let updatedDate: Date
}
/// Links associated with an Article Document
struct SingleArticleDocumentLinks: JSONAPI.Links {
let otherArticlesByPrimaryAuthor: Link<Foundation.URL, LinkMeta>
}
/// Error that can occur when retrieving a Single Article Document
enum ArticleDocumentError: String, JSONAPIError, Codable {
case unknownError = "unknown"
case missing = "missing"
static var unknown: ArticleDocumentError {
return .unknownError
}
}
typealias SingleArticleDocument = JSONAPI.Document<SingleResourceBody<Article>, DocumentMetadata, SingleArticleDocumentLinks, Include1<Author>, APIDescription<APIDescriptionMetadata>, ArticleDocumentError>
// MARK: - Instantiations
let authorId1 = Author.Identifier()
let authorId2 = Author.Identifier()
let authorId3 = Author.Identifier()
let now = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: now)!
let tomorrowEntityMeta = EntityMetadata(creationDate: Calendar.current.date(byAdding: .day, value: -10, to: now)!,
updatedDate: Calendar.current.date(byAdding: .day, value: -2, to: now)!)
let articleLinks = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/article/1234")!,
meta: .init(expiry: tomorrow)))
let article = Article(attributes: .init(title: .init(value: "How to read JSONAPI")),
relationships: .init(primaryAuthor: .init(id: authorId1),
otherAuthors: .init(ids: [authorId2, authorId3],
meta: .init(pagination: .init(total: 2,
limit: 50,
offset: 0)),
links: .init(next: .init(url: URL(string: "https://articles.com/article/1234?otherAuthors[offset]=50")!,
meta: .init(expiry: tomorrow))))),
meta: tomorrowEntityMeta,
links: articleLinks)
let author1Links = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/author/\(authorId1.rawValue)")!,
meta: .init(expiry: tomorrow)))
let author1 = Author(id: authorId1,
attributes: .init(name: .init(value: "James Kinney")),
relationships: .init(articles: .init(ids: [article.id, Article.Identifier(), Article.Identifier()],
meta: .init(pagination: .init(total: 3,
limit: 50,
offset: 0)),
links: .init(next: .init(url: URL(string: "https://articles.com/author/\(authorId1.rawValue)?articles[offset]=50")!, meta: .init(expiry: tomorrow))))),
meta: tomorrowEntityMeta,
links: author1Links)
let author2Links = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/author/\(authorId2.rawValue)")!,
meta: .init(expiry: tomorrow)))
let author2 = Author(id: authorId2,
attributes: .init(name: .init(value: "James Kinney")),
relationships: .init(articles: .init(ids: [article.id, Article.Identifier()],
meta: .init(pagination: .init(total: 2,
limit: 50,
offset: 0)),
links: .init(next: .init(url: URL(string: "https://articles.com/author/\(authorId2.rawValue)?articles[offset]=50")!, meta: .init(expiry: tomorrow))))),
meta: tomorrowEntityMeta,
links: author2Links)
let author3Links = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/author/\(authorId3.rawValue)")!,
meta: .init(expiry: tomorrow)))
let author3 = Author(id: authorId3,
attributes: .init(name: .init(value: "James Kinney")),
relationships: .init(articles: .init(ids: [article.id],
meta: .init(pagination: .init(total: 1,
limit: 50,
offset: 0)),
links: .init(next: .init(url: URL(string: "https://articles.com/author/\(authorId3.rawValue)?articles[offset]=50")!, meta: .init(expiry: tomorrow))))),
meta: tomorrowEntityMeta,
links: author3Links)
let apiDescription = APIDescription<APIDescriptionMetadata>(version: "1.2",
meta: APIDescriptionMetadata(codename: "cobra"))
let documentMetadata = DocumentMetadata(updatedDate: Date())
let documentLinks = SingleArticleDocumentLinks(otherArticlesByPrimaryAuthor: .init(url: URL(string: "https://articles.com/articles?author=\(authorId1.rawValue)")!,
meta: .init(expiry: tomorrow)))
let singleArticleDocument = SingleArticleDocument(apiDescription: apiDescription,
body: .init(entity: article),
includes: .init(values: [.init(author1), .init(author2), .init(author3)]),
meta: documentMetadata,
links: documentLinks)
// MARK: - Encoding
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = .prettyPrinted
let jsonData = try! encoder.encode(singleArticleDocument)
// MARK: - Printing JSON
print(String(data: jsonData, encoding: .utf8)!)
+2
View File
@@ -3,5 +3,7 @@
<pages>
<page name='Test Library'/>
<page name='Usage'/>
<page name='Full Document Verbose Generation'/>
<page name='Full Client &amp; Server Example'/>
</pages>
</playground>
+231 -1
View File
@@ -5,6 +5,61 @@ A Swift package for encoding to- and decoding from **JSON API** compliant reques
See the JSON API Spec here: https://jsonapi.org/format/
## Table of Contents
<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [Table of Contents](#table-of-contents)
- [Primary Goals](#primary-goals)
- [Caveat](#caveat)
- [Dev Environment](#dev-environment)
- [Prerequisites](#prerequisites)
- [Xcode project](#xcode-project)
- [Running the Playground](#running-the-playground)
- [Project Status](#project-status)
- [Encoding/Decoding](#encodingdecoding)
- [Document](#document)
- [Resource Object](#resource-object)
- [Relationship Object](#relationship-object)
- [Links Object](#links-object)
- [Misc](#misc)
- [JSONAPITestLib](#jsonapitestlib)
- [Entity Validator](#entity-validator)
- [Potential Improvements](#potential-improvements)
- [Usage](#usage)
- [`JSONAPI.EntityDescription`](#jsonapientitydescription)
- [`JSONAPI.Entity`](#jsonapientity)
- [`Meta`](#meta)
- [`Links`](#links)
- [`IdType`](#idtype)
- [`MaybeRawId`](#mayberawid)
- [Convenient `typealiases`](#convenient-typealiases)
- [`JSONAPI.Relationships`](#jsonapirelationships)
- [`JSONAPI.Attributes`](#jsonapiattributes)
- [`Transformer`](#transformer)
- [`Validator`](#validator)
- [Computed `Attribute`](#computed-attribute)
- [Copying `Entities`](#copying-entities)
- [`JSONAPI.Document`](#jsonapidocument)
- [`ResourceBody`](#resourcebody)
- [nullable `PrimaryResource`](#nullable-primaryresource)
- [`MetaType`](#metatype)
- [`LinksType`](#linkstype)
- [`IncludeType`](#includetype)
- [`APIDescriptionType`](#apidescriptiontype)
- [`Error`](#error)
- [`JSONAPI.Meta`](#jsonapimeta)
- [`JSONAPI.Links`](#jsonapilinks)
- [`JSONAPI.RawIdType`](#jsonapirawidtype)
- [Custom Attribute or Relationship Key Mapping](#custom-attribute-or-relationship-key-mapping)
- [Custom Attribute Encode/Decode](#custom-attribute-encodedecode)
- [Example](#example)
- [Preamble (Setup shared by server and client)](#preamble-setup-shared-by-server-and-client)
- [Server Pseudo-example](#server-pseudo-example)
- [Client Pseudo-example](#client-pseudo-example)
- [JSONAPITestLib](#jsonapitestlib)
<!-- /TOC -->
## Primary Goals
The primary goals of this framework are:
@@ -62,6 +117,7 @@ Note that Playground support for importing non-system Frameworks is still a bit
### Misc
- [x] Support transforms on `Attributes` values (e.g. to support different representations of `Date`)
- [x] Support validation on `Attributes`.
- [ ] Support sparse fieldsets. At the moment, not sure what this support will look like. A client can likely just define a new model to represent a sparse population of another model in a very specific use case. On the server side, it becomes much more appealing to be able to support arbitrary combinations of omitted fields.
- [ ] Create more descriptive errors that are easier to use for troubleshooting.
### JSONAPITestLib
@@ -73,6 +129,7 @@ Note that Playground support for importing non-system Frameworks is still a bit
### Potential Improvements
- [ ] (Maybe) Use `KeyPath` to specify `Includes` thus creating type safety around the relationship between a primary resource type and the types of included resources.
- [ ] (Maybe) Replace `SingleResourceBody` and `ManyResourceBody` with support at the `Document` level to just interpret `PrimaryResource`, `PrimaryResource?`, or `[PrimaryResource]` as the same decoding/encoding strategies.
- [ ] Support sideposting. JSONAPI spec might become opinionated in the future (https://github.com/json-api/json-api/pull/1197, https://github.com/json-api/json-api/issues/1215, https://github.com/json-api/json-api/issues/1216) but there is also an existing implementation to consider (https://jsonapi-suite.github.io/jsonapi_suite/ruby/writes/nested-writes). At this time, any sidepost implementation would be an awesome tertiary library to be used alongside the primary JSONAPI library. Maybe `JSONAPISideloading`.
- [ ] Property-based testing (using `SwiftCheck`).
- [ ] Error or warning if an included entity is not related to a primary entity or another included entity (Turned off or at least not throwing by default).
@@ -469,5 +526,178 @@ extension EntityDescription1.Attributes {
}
```
# JSONAPITestLib
## Example
The following serves as a sort of pseudo-example. It skips server/client implementation details not related to JSON:API but still gives a more complete picture of what an implementation using this framework might look like. You can play with this example code in the Playground provided with this repo.
### Preamble (Setup shared by server and client)
```
// We make String a CreatableRawIdType.
var GlobalStringId: Int = 0
extension String: CreatableRawIdType {
public static func unique() -> String {
GlobalStringId += 1
return String(GlobalStringId)
}
}
// We create a typealias given that we do not expect JSON:API Resource
// Objects for this particular API to have Metadata or Links associated
// with them. We also expect them to have String Identifiers.
typealias JSONEntity<Description: EntityDescription> = JSONAPI.Entity<Description, NoMetadata, NoLinks, String>
// Similarly, we create a typealias for unidentified entities. JSON:API
// only allows unidentified entities (i.e. no "id" field) for client
// requests that create new entities. In these situations, the server
// is expected to assign the new entity a unique ID.
typealias UnidentifiedJSONEntity<Description: EntityDescription> = JSONAPI.Entity<Description, NoMetadata, NoLinks, Unidentified>
// We create typealiases given that we do not expect JSON:API Relationships
// for this particular API to have Metadata or Links associated
// with them.
typealias ToOneRelationship<Entity: Identifiable> = JSONAPI.ToOneRelationship<Entity, NoMetadata, NoLinks>
typealias ToManyRelationship<Entity: Relatable> = JSONAPI.ToManyRelationship<Entity, NoMetadata, NoLinks>
// We create a typealias for a Document given that we do not expect
// JSON:API Documents for this particular API to have Metadata, Links,
// useful Errors, or a JSON:API Object (i.e. APIDescription).
typealias Document<PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: JSONAPI.Include> = JSONAPI.Document<PrimaryResourceBody, NoMetadata, NoLinks, IncludeType, NoAPIDescription, UnknownJSONAPIError>
// MARK: Entity Definitions
enum AuthorDescription: EntityDescription {
public static var type: String { return "authors" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
}
public typealias Relationships = NoRelationships
}
typealias Author = JSONEntity<AuthorDescription>
enum ArticleDescription: EntityDescription {
public static var type: String { return "articles" }
public struct Attributes: JSONAPI.Attributes {
public let title: Attribute<String>
public let abstract: Attribute<String>
}
public struct Relationships: JSONAPI.Relationships {
public let author: ToOneRelationship<Author>
}
}
typealias Article = JSONEntity<ArticleDescription>
// MARK: Document Definitions
// We create a typealias to represent a document containing one Article
// and including its Author
typealias SingleArticleDocumentWithIncludes = Document<SingleResourceBody<Article>, Include1<Author>>
// ... and a typealias to represent a document containing one Article and
// not including any related entities.
typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoIncludes>
```
### Server Pseudo-example
```
// Skipping over all the API and database stuff, here's a chunk of code
// that creates a document. Note that this document is the entirety
// of a JSON:API response body.
func articleDocument(includeAuthor: Bool) -> Either<SingleArticleDocument, SingleArticleDocumentWithIncludes> {
// Let's pretend all of this is coming from a database:
let authorId = Author.Identifier(rawValue: "1234")
let article = Article(id: .init(rawValue: "5678"),
attributes: .init(title: .init(value: "JSON:API in Swift"),
abstract: .init(value: "Not yet written")),
relationships: .init(author: .init(id: authorId)),
meta: .none,
links: .none)
let document = SingleArticleDocument(apiDescription: .none,
body: .init(entity: article),
includes: .none,
meta: .none,
links: .none)
switch includeAuthor {
case false:
return .a(document)
case true:
let author = Author(id: authorId,
attributes: .init(name: .init(value: "Janice Bluff")),
relationships: .none,
meta: .none,
links: .none)
let includes: Includes<SingleArticleDocumentWithIncludes.Include> = .init(values: [.init(author)])
return .b(document.including(.init(values: [.init(author)])))
}
}
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
let responseBody = articleDocument(includeAuthor: true)
let responseData = try! encoder.encode(responseBody)
// Next step would be encoding and setting as the HTTP body of a response.
// we will just print it out instead:
print("-----")
print(String(data: responseData, encoding: .utf8)!)
// ... and if we had received a request for an article without
// including the author:
let otherResponseBody = articleDocument(includeAuthor: false)
let otherResponseData = try! encoder.encode(otherResponseBody)
print("-----")
print(String(data: otherResponseData, encoding: .utf8)!)
```
### Client Pseudo-example
```
enum NetworkError: Swift.Error {
case serverError
case quantityMismatch
}
// Skipping over all the API stuff, here's a chunk of code that will
// decode a document. We will assume we have made a request for a
// single article including the author.
func docode(articleResponseData: Data) throws -> (article: Article, author: Author) {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let articleDocument = try decoder.decode(SingleArticleDocumentWithIncludes.self, from: articleResponseData)
switch articleDocument.body {
case .data(let data):
let authors = data.includes[Author.self]
guard authors.count == 1 else {
throw NetworkError.quantityMismatch
}
return (article: data.primary.value, author: authors[0])
case .errors(let errors, meta: _, links: _):
throw NetworkError.serverError
}
}
let response = try! docode(articleResponseData: responseData)
// Next step would be to do something useful with the article and author but we will print them instead.
print("-----")
print(response.article)
print(response.author)
```
## JSONAPITestLib
The `JSONAPI` framework is packaged with a test library to help you test your `JSONAPI` integration. The test library is called `JSONAPITestLib`. It provides literal expressibility for `Attribute`, `ToOneRelationship`, and `Id` in many situations so that you can easily write test `Entity` values into your unit tests. It also provides a `check()` function for each `Entity` type that can be used to catch problems with your `JSONAPI` structures that are not caught by Swift's type system. You can see the `JSONAPITestLib` in action in the Playground included with the `JSONAPI` repository.
@@ -14,6 +14,11 @@ public protocol APIDescriptionType: Codable, Equatable {
public struct APIDescription<Meta: JSONAPI.Meta>: APIDescriptionType {
public let version: String
public let meta: Meta
public init(version: String, meta: Meta) {
self.version = version
self.meta = meta
}
}
/// Can be used as `APIDescriptionType` for Documents that do not
+54 -1
View File
@@ -110,11 +110,16 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
self.apiDescription = apiDescription
}
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType, links: LinksType) {
public init(apiDescription: APIDescription,
body: PrimaryResourceBody,
includes: Includes<Include>,
meta: MetaType,
links: LinksType) {
self.body = .data(.init(primary: body, includes: includes, meta: meta, links: links))
self.apiDescription = apiDescription
}
}
/*
extension Document where IncludeType == NoIncludes {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
@@ -208,6 +213,54 @@ extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody,
}
}
extension Document where IncludeType == NoIncludes {
/// Create a new Document with the given includes.
public func including<I: JSONAPI.Include>(_ includes: Includes<I>) -> Document<PrimaryResourceBody, MetaType, LinksType, I, APIDescription, Error> {
// Note that if IncludeType is NoIncludes, then we allow anything
// to be included, but if IncludeType already specifies a type
// of thing to be expected then we lock that down.
// See: Document.including() where IncludeType: _Poly1
switch body {
case .data(let data):
return .init(apiDescription: apiDescription,
body: data.primary,
includes: includes,
meta: data.meta,
links: data.links)
case .errors(let errors, meta: let meta, links: let links):
return .init(apiDescription: apiDescription,
errors: errors,
meta: meta,
links: links)
}
}
}
// extending where _Poly1 means all non-zero _Poly arities are included
extension Document where IncludeType: _Poly1 {
/// Create a new Document adding the given includes. This does not
/// remove existing includes; it is additive.
public func including(_ includes: Includes<IncludeType>) -> Document {
// Note that if IncludeType is NoIncludes, then we allow anything
// to be included, but if IncludeType already specifies a type
// of thing to be expected then we lock that down.
// See: Document.including() where IncludeType == NoIncludes
switch body {
case .data(let data):
return .init(apiDescription: apiDescription,
body: data.primary,
includes: data.includes + includes,
meta: data.meta,
links: data.links)
case .errors(let errors, meta: let meta, links: let links):
return .init(apiDescription: apiDescription,
errors: errors,
meta: meta,
links: links)
}
}
}
// MARK: - Codable
extension Document {
private enum RootCodingKeys: String, CodingKey {
+14 -6
View File
@@ -20,16 +20,24 @@ public struct NoRelationships: Relationships {
public static var none: NoRelationships { return .init() }
}
extension NoRelationships: CustomStringConvertible {
public var description: String { return "No Relationships" }
}
/// Can be used as `Attributes` Type for Entities that do not
/// have any Attributes.
public struct NoAttributes: Attributes {
public static var none: NoAttributes { return .init() }
}
extension NoAttributes: CustomStringConvertible {
public var description: String { return "No Attributes" }
}
/// Something that is JSONTyped provides a String representation
/// of its type.
public protocol JSONTyped {
static var type: String { get }
static var jsonType: String { get }
}
/// An `EntityProxyDescription` is an `EntityDescription`
@@ -73,7 +81,7 @@ public protocol EntityProxy: Equatable, JSONTyped {
extension EntityProxy {
/// The JSON API compliant "type" of this `Entity`.
public static var type: String { return Description.type }
public static var jsonType: String { return Description.jsonType }
}
/// EntityType is the protocol that Entity conforms to. This
@@ -128,7 +136,7 @@ extension Entity: Identifiable, IdentifiableEntityType, Relatable where EntityRa
extension Entity: CustomStringConvertible {
public var description: String {
return "Entity<\(Entity.type)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))"
return "Entity<\(Entity.jsonType)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))"
}
}
@@ -521,7 +529,7 @@ public extension Entity {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ResourceObjectCodingKeys.self)
try container.encode(Entity.type, forKey: .type)
try container.encode(Entity.jsonType, forKey: .type)
if EntityRawIdType.self != Unidentified.self {
try container.encode(id, forKey: .id)
@@ -550,8 +558,8 @@ public extension Entity {
let type = try container.decode(String.self, forKey: .type)
guard Entity.type == type else {
throw JSONAPIEncodingError.typeMismatch(expected: Description.type, found: type)
guard Entity.jsonType == type else {
throw JSONAPIEncodingError.typeMismatch(expected: Description.jsonType, found: type)
}
let maybeUnidentified = Unidentified() as? EntityRawIdType
+26 -22
View File
@@ -17,14 +17,14 @@ public protocol Poly: PrimaryResource {}
// MARK: - Generic Decoding
private func decode<Entity: JSONAPI.EntityType>(_ type: Entity.Type, from container: SingleValueDecodingContainer) throws -> Result<Entity, DecodingError> {
let ret: Result<Entity, DecodingError>
private func decode<Thing: Codable>(_ type: Thing.Type, from container: SingleValueDecodingContainer) throws -> Result<Thing, DecodingError> {
let ret: Result<Thing, DecodingError>
do {
ret = try .success(container.decode(Entity.self))
ret = try .success(container.decode(Thing.self))
} catch (let err as DecodingError) {
ret = .failure(err)
} catch (let err) {
ret = .failure(DecodingError.typeMismatch(Entity.Description.self,
ret = .failure(DecodingError.typeMismatch(Thing.self,
.init(codingPath: container.codingPath,
debugDescription: String(describing: err),
underlyingError: err)))
@@ -47,9 +47,11 @@ public struct Poly0: _Poly0 {
}
}
public typealias PolyWrapped = Codable & Equatable
// MARK: - 1 type
public protocol _Poly1: _Poly0 {
associatedtype A: EntityType
associatedtype A: PolyWrapped
var a: A? { get }
init(_ a: A)
@@ -61,7 +63,7 @@ public extension _Poly1 {
}
}
public enum Poly1<A: EntityType>: _Poly1 {
public enum Poly1<A: PolyWrapped>: _Poly1 {
case a(A)
public var a: A? {
@@ -102,7 +104,7 @@ extension Poly1: CustomStringConvertible {
// MARK: - 2 types
public protocol _Poly2: _Poly1 {
associatedtype B: EntityType
associatedtype B: PolyWrapped
var b: B? { get }
init(_ b: B)
@@ -114,7 +116,9 @@ public extension _Poly2 {
}
}
public enum Poly2<A: EntityType, B: EntityType>: _Poly2 {
public typealias Either = Poly2
public enum Poly2<A: PolyWrapped, B: PolyWrapped>: _Poly2 {
case a(A)
case b(B)
@@ -181,7 +185,7 @@ extension Poly2: CustomStringConvertible {
// MARK: - 3 types
public protocol _Poly3: _Poly2 {
associatedtype C: EntityType
associatedtype C: PolyWrapped
var c: C? { get }
init(_ c: C)
@@ -193,7 +197,7 @@ public extension _Poly3 {
}
}
public enum Poly3<A: EntityType, B: EntityType, C: EntityType>: _Poly3 {
public enum Poly3<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped>: _Poly3 {
case a(A)
case b(B)
case c(C)
@@ -275,7 +279,7 @@ extension Poly3: CustomStringConvertible {
// MARK: - 4 types
public protocol _Poly4: _Poly3 {
associatedtype D: EntityType
associatedtype D: PolyWrapped
var d: D? { get }
init(_ d: D)
@@ -287,7 +291,7 @@ public extension _Poly4 {
}
}
public enum Poly4<A: EntityType, B: EntityType, C: EntityType, D: EntityType>: _Poly4 {
public enum Poly4<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped>: _Poly4 {
case a(A)
case b(B)
case c(C)
@@ -384,7 +388,7 @@ extension Poly4: CustomStringConvertible {
// MARK: - 5 types
public protocol _Poly5: _Poly4 {
associatedtype E: EntityType
associatedtype E: PolyWrapped
var e: E? { get }
init(_ e: E)
@@ -396,7 +400,7 @@ public extension _Poly5 {
}
}
public enum Poly5<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType>: _Poly5 {
public enum Poly5<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped>: _Poly5 {
case a(A)
case b(B)
case c(C)
@@ -508,7 +512,7 @@ extension Poly5: CustomStringConvertible {
// MARK: - 6 types
public protocol _Poly6: _Poly5 {
associatedtype F: EntityType
associatedtype F: PolyWrapped
var f: F? { get }
init(_ f: F)
@@ -520,7 +524,7 @@ public extension _Poly6 {
}
}
public enum Poly6<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType>: _Poly6 {
public enum Poly6<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped>: _Poly6 {
case a(A)
case b(B)
case c(C)
@@ -647,7 +651,7 @@ extension Poly6: CustomStringConvertible {
// MARK: - 7 types
public protocol _Poly7: _Poly6 {
associatedtype G: EntityType
associatedtype G: PolyWrapped
var g: G? { get }
init(_ g: G)
@@ -659,7 +663,7 @@ public extension _Poly7 {
}
}
public enum Poly7<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType, G: EntityType>: _Poly7 {
public enum Poly7<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped>: _Poly7 {
case a(A)
case b(B)
case c(C)
@@ -802,7 +806,7 @@ extension Poly7: CustomStringConvertible {
// MARK: - 8 types
public protocol _Poly8: _Poly7 {
associatedtype H: EntityType
associatedtype H: PolyWrapped
var h: H? { get }
init(_ h: H)
@@ -814,7 +818,7 @@ public extension _Poly8 {
}
}
public enum Poly8<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType, G: EntityType, H: EntityType>: _Poly8 {
public enum Poly8<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped>: _Poly8 {
case a(A)
case b(B)
case c(C)
@@ -973,7 +977,7 @@ extension Poly8: CustomStringConvertible {
// MARK: - 9 types
public protocol _Poly9: _Poly8 {
associatedtype I: EntityType
associatedtype I: PolyWrapped
var i: I? { get }
init(_ i: I)
@@ -985,7 +989,7 @@ public extension _Poly9 {
}
}
public enum Poly9<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType, G: EntityType, H: EntityType, I: EntityType>: _Poly9 {
public enum Poly9<A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped>: _Poly9 {
case a(A)
case b(B)
case c(C)
+7 -7
View File
@@ -134,7 +134,7 @@ public protocol OptionalRelatable: Identifiable where Identifier == Wrapped.Iden
extension Optional: Identifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
public typealias Identifier = Wrapped.Identifier?
public static var type: String { return Wrapped.type }
public static var jsonType: String { return Wrapped.jsonType }
}
// MARK: Codable
@@ -180,8 +180,8 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Identifiable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.type, found: type)
guard type == Identifiable.jsonType else {
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.jsonType, found: type)
}
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
@@ -214,7 +214,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
try identifier.encode(id.rawValue, forKey: .id)
try identifier.encode(Identifiable.type, forKey: .entityType)
try identifier.encode(Identifiable.jsonType, forKey: .entityType)
}
}
@@ -242,8 +242,8 @@ extension ToManyRelationship: Codable {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.type, found: type)
guard type == Relatable.jsonType else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.jsonType, found: type)
}
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
@@ -268,7 +268,7 @@ extension ToManyRelationship: Codable {
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
try identifier.encode(id.rawValue, forKey: .id)
try identifier.encode(Relatable.type, forKey: .entityType)
try identifier.encode(Relatable.jsonType, forKey: .entityType)
}
}
}
@@ -38,7 +38,7 @@ class Attribute_FunctorTests: XCTestCase {
// MARK: Test types
extension Attribute_FunctorTests {
enum TestTypeDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public struct Attributes: JSONAPI.Attributes {
let name: Attribute<String>
@@ -46,7 +46,7 @@ class ComputedPropertiesTests: XCTestCase {
// MARK: Test types
extension ComputedPropertiesTests {
public enum TestTypeDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
@@ -40,7 +40,7 @@ class CustomAttributesTests: XCTestCase {
// MARK: - Test Types
extension CustomAttributesTests {
enum CustomAttributeEntityDescription: EntityDescription {
public static var type: String { return "test1" }
public static var jsonType: String { return "test1" }
public struct Attributes: JSONAPI.Attributes {
let firstName: Attribute<String>
@@ -58,7 +58,7 @@ extension CustomAttributesTests {
typealias CustomAttributeEntity = BasicEntity<CustomAttributeEntityDescription>
enum CustomKeysEntityDescription: EntityDescription {
public static var type: String { return "test1" }
public static var jsonType: String { return "test1" }
public struct Attributes: JSONAPI.Attributes {
public let firstNameSilly: Attribute<String>
@@ -88,6 +88,42 @@ extension DocumentTests {
XCTAssertEqual(errors.meta, NoMetadata())
}
func test_unknownErrorDocumentAddIncludingType() {
let author = Author(id: "1",
attributes: .none,
relationships: .none,
meta: .none,
links: .none)
let document = decoded(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
let documentWithIncludes = document.including(Includes<Include1<Author>>(values: [.init(author)]))
XCTAssertEqual(document.body.errors, documentWithIncludes.body.errors)
XCTAssertEqual(document.body.meta, documentWithIncludes.body.meta)
XCTAssertEqual(document.body.links, documentWithIncludes.body.links)
XCTAssertNil(documentWithIncludes.body.includes)
}
func test_unknownErrorDocumentAddIncludes() {
let author = Author(id: "1",
attributes: .none,
relationships: .none,
meta: .none,
links: .none)
let document = decoded(type: Document<NoResourceBody, NoMetadata, NoLinks, Include1<Author>, NoAPIDescription, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
let documentWithIncludes = document.including(.init(values: [.init(author)]))
XCTAssertEqual(document.body.errors, documentWithIncludes.body.errors)
XCTAssertEqual(document.body.meta, documentWithIncludes.body.meta)
XCTAssertEqual(document.body.links, documentWithIncludes.body.links)
XCTAssertNil(documentWithIncludes.body.includes)
}
func test_unknownErrorDocumentNoMeta_encode() {
test_DecodeEncodeEquality(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.self,
data: error_document_no_metadata)
@@ -536,6 +572,25 @@ extension DocumentTests {
data: single_document_no_includes)
}
func test_singleDocumentNoIncludesAddIncludingType() {
let author = Author(id: "1",
attributes: .none,
relationships: .none,
meta: .none,
links: .none)
let document = decoded(type: Document<NoResourceBody, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.self,
data: single_document_no_includes)
let documentWithIncludes = document.including(Includes<Include1<Author>>(values: [.init(author)]))
XCTAssertEqual(document.body.errors, documentWithIncludes.body.errors)
XCTAssertEqual(document.body.meta, documentWithIncludes.body.meta)
XCTAssertEqual(document.body.links, documentWithIncludes.body.links)
XCTAssertEqual(document.body.includes, Includes<NoIncludes>.none)
XCTAssertEqual(documentWithIncludes.body.includes?[Author.self], [author])
}
func test_singleDocumentNoIncludesWithAPIDescription() {
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, TestAPIDescription, UnknownJSONAPIError>.self,
data: single_document_no_includes_with_api_description)
@@ -740,6 +795,30 @@ extension DocumentTests {
data: single_document_some_includes)
}
func test_singleDocumentSomeIncludesAddIncludes() {
let existingAuthor = Author(id: "33",
attributes: .none,
relationships: .none,
meta: .none,
links: .none)
let newAuthor = Author(id: "1",
attributes: .none,
relationships: .none,
meta: .none,
links: .none)
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, NoAPIDescription, UnknownJSONAPIError>.self,
data: single_document_some_includes)
let documentWithIncludes = document.including(.init(values: [.init(newAuthor)]))
XCTAssertEqual(document.body.errors, documentWithIncludes.body.errors)
XCTAssertEqual(document.body.meta, documentWithIncludes.body.meta)
XCTAssertEqual(document.body.links, documentWithIncludes.body.links)
XCTAssertEqual(documentWithIncludes.body.includes?[Author.self], [existingAuthor, newAuthor])
}
func test_singleDocumentSomeIncludesWithAPIDescription() {
let document = decoded(type: Document<SingleResourceBody<Article>, NoMetadata, NoLinks, Include1<Author>, TestAPIDescription, UnknownJSONAPIError>.self,
data: single_document_some_includes_with_api_description)
@@ -1015,7 +1094,7 @@ extension DocumentTests {
// MARK: - Test Types
extension DocumentTests {
enum AuthorType: EntityDescription {
static var type: String { return "authors" }
static var jsonType: String { return "authors" }
typealias Attributes = NoAttributes
typealias Relationships = NoRelationships
@@ -1024,7 +1103,7 @@ extension DocumentTests {
typealias Author = BasicEntity<AuthorType>
enum ArticleType: EntityDescription {
static var type: String { return "articles" }
static var jsonType: String { return "articles" }
typealias Attributes = NoAttributes
+12 -12
View File
@@ -613,7 +613,7 @@ extension EntityTests {
extension EntityTests {
enum TestEntityType1: EntityDescription {
static var type: String { return "test_entities"}
static var jsonType: String { return "test_entities"}
typealias Attributes = NoAttributes
typealias Relationships = NoRelationships
@@ -622,7 +622,7 @@ extension EntityTests {
typealias TestEntity1 = BasicEntity<TestEntityType1>
enum TestEntityType2: EntityDescription {
static var type: String { return "second_test_entities"}
static var jsonType: String { return "second_test_entities"}
typealias Attributes = NoAttributes
@@ -634,7 +634,7 @@ extension EntityTests {
typealias TestEntity2 = BasicEntity<TestEntityType2>
enum TestEntityType3: EntityDescription {
static var type: String { return "third_test_entities"}
static var jsonType: String { return "third_test_entities"}
typealias Attributes = NoAttributes
@@ -646,7 +646,7 @@ extension EntityTests {
typealias TestEntity3 = BasicEntity<TestEntityType3>
enum TestEntityType4: EntityDescription {
static var type: String { return "fourth_test_entities"}
static var jsonType: String { return "fourth_test_entities"}
struct Relationships: JSONAPI.Relationships {
let other: ToOneRelationship<TestEntity2, NoMetadata, NoLinks>
@@ -668,7 +668,7 @@ extension EntityTests {
typealias TestEntity4WithMetaAndLinks = Entity<TestEntityType4, TestEntityMeta, TestEntityLinks>
enum TestEntityType5: EntityDescription {
static var type: String { return "fifth_test_entities"}
static var jsonType: String { return "fifth_test_entities"}
typealias Relationships = NoRelationships
@@ -680,7 +680,7 @@ extension EntityTests {
typealias TestEntity5 = BasicEntity<TestEntityType5>
enum TestEntityType6: EntityDescription {
static var type: String { return "sixth_test_entities" }
static var jsonType: String { return "sixth_test_entities" }
typealias Relationships = NoRelationships
@@ -694,7 +694,7 @@ extension EntityTests {
typealias TestEntity6 = BasicEntity<TestEntityType6>
enum TestEntityType7: EntityDescription {
static var type: String { return "seventh_test_entities" }
static var jsonType: String { return "seventh_test_entities" }
typealias Relationships = NoRelationships
@@ -707,7 +707,7 @@ extension EntityTests {
typealias TestEntity7 = BasicEntity<TestEntityType7>
enum TestEntityType8: EntityDescription {
static var type: String { return "eighth_test_entities" }
static var jsonType: String { return "eighth_test_entities" }
typealias Relationships = NoRelationships
@@ -725,7 +725,7 @@ extension EntityTests {
typealias TestEntity8 = BasicEntity<TestEntityType8>
enum TestEntityType9: EntityDescription {
public static var type: String { return "ninth_test_entities" }
public static var jsonType: String { return "ninth_test_entities" }
typealias Attributes = NoAttributes
@@ -748,7 +748,7 @@ extension EntityTests {
typealias TestEntity9 = BasicEntity<TestEntityType9>
enum TestEntityType10: EntityDescription {
public static var type: String { return "tenth_test_entities" }
public static var jsonType: String { return "tenth_test_entities" }
typealias Attributes = NoAttributes
@@ -761,7 +761,7 @@ extension EntityTests {
typealias TestEntity10 = BasicEntity<TestEntityType10>
enum TestEntityType11: EntityDescription {
public static var type: String { return "eleventh_test_entities" }
public static var jsonType: String { return "eleventh_test_entities" }
public struct Attributes: JSONAPI.Attributes {
let number: ValidatedAttribute<Int, IntOver10>
@@ -773,7 +773,7 @@ extension EntityTests {
typealias TestEntity11 = BasicEntity<TestEntityType11>
enum UnidentifiedTestEntityType: EntityDescription {
public static var type: String { return "unidentified_test_entities" }
public static var jsonType: String { return "unidentified_test_entities" }
struct Attributes: JSONAPI.Attributes {
let me: Attribute<String>?
@@ -198,7 +198,7 @@ extension IncludedTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity1" }
public static var jsonType: String { return "test_entity1" }
public struct Attributes: JSONAPI.Attributes {
let foo: Attribute<String>
@@ -210,7 +210,7 @@ extension IncludedTests {
enum TestEntityType2: EntityDescription {
public static var type: String { return "test_entity2" }
public static var jsonType: String { return "test_entity2" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
@@ -228,7 +228,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity3" }
public static var jsonType: String { return "test_entity3" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
@@ -244,7 +244,7 @@ extension IncludedTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity4" }
public static var jsonType: String { return "test_entity4" }
}
typealias TestEntity4 = BasicEntity<TestEntityType4>
@@ -255,7 +255,7 @@ extension IncludedTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity5" }
public static var jsonType: String { return "test_entity5" }
}
typealias TestEntity5 = BasicEntity<TestEntityType5>
@@ -264,7 +264,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity6" }
public static var jsonType: String { return "test_entity6" }
struct Relationships: JSONAPI.Relationships {
let entity4: ToOneRelationship<TestEntity4, NoMetadata, NoLinks>
@@ -277,7 +277,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity7" }
public static var jsonType: String { return "test_entity7" }
typealias Relationships = NoRelationships
}
@@ -288,7 +288,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity8" }
public static var jsonType: String { return "test_entity8" }
typealias Relationships = NoRelationships
}
@@ -299,7 +299,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity9" }
public static var jsonType: String { return "test_entity9" }
typealias Relationships = NoRelationships
}
@@ -41,7 +41,7 @@ class EntityCheckTests: XCTestCase {
// MARK: - Test types
extension EntityCheckTests {
enum OkDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -50,7 +50,7 @@ extension EntityCheckTests {
public typealias OkEntity = BasicEntity<OkDescription>
enum OtherOkDescription: EntityDescription {
public static var type: String { return "hmm" }
public static var jsonType: String { return "hmm" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -59,7 +59,7 @@ extension EntityCheckTests {
public typealias OtherOkEntity = BasicEntity<OtherOkDescription>
enum EnumAttributesDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public enum Attributes: JSONAPI.Attributes {
case hello
@@ -78,7 +78,7 @@ extension EntityCheckTests {
public typealias EnumAttributesEntity = BasicEntity<EnumAttributesDescription>
enum EnumRelationshipsDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public typealias Attributes = NoAttributes
@@ -97,7 +97,7 @@ extension EntityCheckTests {
public typealias EnumRelationshipsEntity = BasicEntity<EnumRelationshipsDescription>
enum BadAttributeDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public struct Attributes: JSONAPI.Attributes {
let x: Attribute<String>
@@ -110,7 +110,7 @@ extension EntityCheckTests {
public typealias BadAttributeEntity = BasicEntity<BadAttributeDescription>
enum BadRelationshipDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public typealias Attributes = NoAttributes
@@ -123,7 +123,7 @@ extension EntityCheckTests {
public typealias BadRelationshipEntity = BasicEntity<BadRelationshipDescription>
enum OptionalArrayAttributeDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public struct Attributes: JSONAPI.Attributes {
let x: Attribute<[String]>
@@ -25,7 +25,7 @@ class Id_LiteralTests: XCTestCase {
// MARK: - Test types
extension Id_LiteralTests {
enum TestDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -28,7 +28,7 @@ class Relationship_LiteralTests: XCTestCase {
// MARK: - Test types
extension Relationship_LiteralTests {
enum TestDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -53,7 +53,7 @@ class NonJSONAPIRelatableTests: XCTestCase {
// MARK: - Test Types
extension NonJSONAPIRelatableTests {
enum TestEntityDescription: EntityDescription {
static var type: String { return "test" }
static var jsonType: String { return "test" }
typealias Attributes = NoAttributes
@@ -66,7 +66,7 @@ extension NonJSONAPIRelatableTests {
typealias TestEntity = JSONAPI.Entity<TestEntityDescription, NoMetadata, NoLinks, String>
enum TestEntity2Description: EntityDescription {
static var type: String { return "test" }
static var jsonType: String { return "test" }
typealias Attributes = NoAttributes
@@ -81,7 +81,7 @@ extension NonJSONAPIRelatableTests {
typealias TestEntity2 = JSONAPI.Entity<TestEntity2Description, NoMetadata, NoLinks, String>
struct NonJSONAPIEntity: Relatable, JSONTyped {
static var type: String { return "other" }
static var jsonType: String { return "other" }
typealias Identifier = NonJSONAPIEntity.Id
+4 -4
View File
@@ -11,7 +11,7 @@ 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")
XCTAssertEqual(User.jsonType, "users")
}
func test_UserADecode() {
@@ -65,7 +65,7 @@ public class PolyProxyTests: XCTestCase {
// MARK: - Test types
public extension PolyProxyTests {
public enum UserDescription1: EntityDescription {
public static var type: String { return "users" }
public static var jsonType: String { return "users" }
public struct Attributes: JSONAPI.Attributes {
let firstName: Attribute<String>
@@ -76,7 +76,7 @@ public extension PolyProxyTests {
}
public enum UserDescription2: EntityDescription {
public static var type: String { return "users" }
public static var jsonType: String { return "users" }
public struct Attributes: JSONAPI.Attributes {
let name: Attribute<[String]>
@@ -124,7 +124,7 @@ extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == Po
}
public enum SharedUserDescription: EntityProxyDescription {
public static var type: String { return A.type }
public static var jsonType: String { return A.jsonType }
public struct Attributes: Equatable {
let name: Attribute<String>

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