mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 072b081ac3 | |||
| 897410492d | |||
| b46f5166ea |
+173
@@ -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)
|
||||
+213
@@ -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)!)
|
||||
@@ -3,5 +3,7 @@
|
||||
<pages>
|
||||
<page name='Test Library'/>
|
||||
<page name='Usage'/>
|
||||
<page name='Full Document Verbose Generation'/>
|
||||
<page name='Full Client & Server Example'/>
|
||||
</pages>
|
||||
</playground>
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user