mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c04d3301b6 | |||
| 5a64bebc99 | |||
| 790b0fbf52 | |||
| 539ecc451a | |||
| 7eb1b05eae | |||
| 3408263c2a | |||
| 1d6e5d3810 | |||
| d68404db36 | |||
| 4f7db98a87 | |||
| 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 jsonType: 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 jsonType: 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 jsonType: 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 jsonType: 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)!)
|
||||
@@ -31,7 +31,7 @@ public typealias ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLink
|
||||
// MARK: - A few resource objects (entities)
|
||||
public enum PersonDescription: EntityDescription {
|
||||
|
||||
public static var type: String { return "people" }
|
||||
public static var jsonType: String { return "people" }
|
||||
|
||||
public struct Attributes: JSONAPI.Attributes {
|
||||
public let name: Attribute<[String]>
|
||||
@@ -70,7 +70,7 @@ public extension Entity where Description == PersonDescription, MetaType == NoMe
|
||||
|
||||
public enum DogDescription: EntityDescription {
|
||||
|
||||
public static var type: String { return "dogs" }
|
||||
public static var jsonType: String { return "dogs" }
|
||||
|
||||
public struct Attributes: JSONAPI.Attributes {
|
||||
public let name: Attribute<String>
|
||||
@@ -93,7 +93,7 @@ public typealias Dog = ExampleEntity<DogDescription>
|
||||
|
||||
public enum AlternativeDogDescription: EntityDescription {
|
||||
|
||||
public static var type: String { return "dogs" }
|
||||
public static var jsonType: String { return "dogs" }
|
||||
|
||||
public struct Attributes: JSONAPI.Attributes {
|
||||
public let name: Attribute<String>
|
||||
@@ -131,7 +131,7 @@ public extension Entity where Description == DogDescription, MetaType == NoMetad
|
||||
|
||||
public enum HouseDescription: EntityDescription {
|
||||
|
||||
public static var type: String { return "houses" }
|
||||
public static var jsonType: String { return "houses" }
|
||||
|
||||
public typealias Attributes = NoAttributes
|
||||
public typealias Relationships = NoRelationships
|
||||
|
||||
@@ -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)))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,6 +471,15 @@ public extension EntityProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Meta-Attribute Access
|
||||
public extension EntityProxy {
|
||||
/// Access an attribute requiring a transformation on the RawValue _and_
|
||||
/// a secondary transformation on this entity (self).
|
||||
subscript<T>(_ path: KeyPath<Description.Attributes, (Self) -> T>) -> T {
|
||||
return attributes[keyPath: path](self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Relationship Access
|
||||
public extension EntityProxy {
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
@@ -505,6 +522,23 @@ public extension EntityProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Meta-Relationship Access
|
||||
public extension EntityProxy {
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
/// This allows you to write `entity ~> \.other` instead
|
||||
/// of `entity.relationships.other.id`.
|
||||
public static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> Identifier>) -> Identifier {
|
||||
return entity.relationships[keyPath: path](entity)
|
||||
}
|
||||
|
||||
/// Access to all Ids of a `ToManyRelationship`.
|
||||
/// This allows you to write `entity ~> \.others` instead
|
||||
/// of `entity.relationships.others.ids`.
|
||||
public static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> [Identifier]>) -> [Identifier] {
|
||||
return entity.relationships[keyPath: path](entity)
|
||||
}
|
||||
}
|
||||
|
||||
infix operator ~>
|
||||
|
||||
// MARK: - Codable
|
||||
@@ -521,7 +555,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 +584,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
|
||||
|
||||
|
||||
@@ -609,11 +609,45 @@ extension EntityTests {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: With a Meta Attribute
|
||||
|
||||
extension EntityTests {
|
||||
func test_MetaEntityAttributeAccessWorks() {
|
||||
let entity1 = TestEntityWithMetaAttribute(id: "even",
|
||||
attributes: .init(),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
let entity2 = TestEntityWithMetaAttribute(id: "odd",
|
||||
attributes: .init(),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
|
||||
XCTAssertEqual(entity1[\.metaAttribute], true)
|
||||
XCTAssertEqual(entity2[\.metaAttribute], false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: With a Meta Relationship
|
||||
|
||||
extension EntityTests {
|
||||
func test_MetaEntityRelationshipAccessWorks() {
|
||||
let entity1 = TestEntityWithMetaRelationship(id: "even",
|
||||
attributes: .none,
|
||||
relationships: .init(),
|
||||
meta: .none,
|
||||
links: .none)
|
||||
|
||||
XCTAssertEqual(entity1 ~> \.metaRelationship, "hello")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
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 +656,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 +668,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 +680,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 +702,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 +714,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 +728,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 +741,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 +759,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 +782,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 +795,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 +807,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>?
|
||||
@@ -790,6 +824,38 @@ extension EntityTests {
|
||||
|
||||
typealias UnidentifiedTestEntityWithMetaAndLinks = NewEntity<UnidentifiedTestEntityType, TestEntityMeta, TestEntityLinks>
|
||||
|
||||
enum TestEntityWithMetaAttributeDescription: EntityDescription {
|
||||
public static var jsonType: String { return "meta_attribute_entity" }
|
||||
|
||||
struct Attributes: JSONAPI.Attributes {
|
||||
var metaAttribute: (TestEntityWithMetaAttribute) -> Bool {
|
||||
return { entity in
|
||||
(entity.id.rawValue.count % 2) == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntityWithMetaAttribute = BasicEntity<TestEntityWithMetaAttributeDescription>
|
||||
|
||||
enum TestEntityWithMetaRelationshipDescription: EntityDescription {
|
||||
public static var jsonType: String { return "meta_relationship_entity" }
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
struct Relationships: JSONAPI.Relationships {
|
||||
var metaRelationship: (TestEntityWithMetaRelationship) -> TestEntity1.Identifier {
|
||||
return { entity in
|
||||
return TestEntity1.Identifier(rawValue: "hello")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntityWithMetaRelationship = BasicEntity<TestEntityWithMetaRelationshipDescription>
|
||||
|
||||
enum IntToString: Transformer {
|
||||
public static func transform(_ from: Int) -> String {
|
||||
return String(from)
|
||||
|
||||
@@ -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