mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f41521e33b | |||
| 45ec7ba753 | |||
| 6ab4237c97 | |||
| 75711648a4 | |||
| 2b4209ccb1 | |||
| ebd11df104 | |||
| 96da1b4e21 | |||
| c7696d83fa | |||
| 8ee04d8932 | |||
| 4a7a14b1b0 | |||
| a6b7d7a94a | |||
| 1010489a02 | |||
| ae855c85ee | |||
| 440b649577 | |||
| 54551617b4 | |||
| 8c3a82ec23 | |||
| e9a3b35dc7 | |||
| 2eecf95995 | |||
| 4dc30ddc1c | |||
| 455ff64326 | |||
| 0b4baf35d5 | |||
| 11ef050d58 | |||
| 86344ef93f | |||
| 7fabe2574e | |||
| 19636a47f0 | |||
| 0538de48cb | |||
| 024fe2d452 | |||
| 832161628b | |||
| f37f44cfda | |||
| ae7e0f528a | |||
| 9f42e5fa3f | |||
| 3e96adf775 | |||
| 83233a7e1a | |||
| adcc6bfb10 | |||
| 6f9a2df605 | |||
| c24b70b49f | |||
| 7b56e35a91 | |||
| e6f82c6052 | |||
| d6f01d6c1d | |||
| a7f6ed5845 | |||
| 87271b93f9 | |||
| 706346e3a6 | |||
| 33a5ff41a0 | |||
| 0fe5c53ada | |||
| f7bfa91ccc |
+1
-1
@@ -37,7 +37,7 @@ typealias ToManyRelationship<Entity: Relatable> = JSONAPI.ToManyRelationship<Ent
|
|||||||
// JSON:API Documents for this particular API to have Metadata, Links,
|
// JSON:API Documents for this particular API to have Metadata, Links,
|
||||||
// useful Errors, or an APIDescription (The *SPEC* calls this
|
// useful Errors, or an APIDescription (The *SPEC* calls this
|
||||||
// "API Description" the "JSON:API Object").
|
// "API Description" the "JSON:API Object").
|
||||||
typealias Document<PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: JSONAPI.Include> = JSONAPI.Document<PrimaryResourceBody, NoMetadata, NoLinks, IncludeType, NoAPIDescription, BasicJSONAPIError<String>>
|
typealias Document<PrimaryResourceBody: JSONAPI.CodableResourceBody, IncludeType: JSONAPI.Include> = JSONAPI.Document<PrimaryResourceBody, NoMetadata, NoLinks, IncludeType, NoAPIDescription, BasicJSONAPIError<String>>
|
||||||
|
|
||||||
// MARK: Entity Definitions
|
// MARK: Entity Definitions
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ if case let .data(bodyData) = peopleResponse.body {
|
|||||||
|
|
||||||
// MARK: - Work in the abstract
|
// MARK: - Work in the abstract
|
||||||
print("-----")
|
print("-----")
|
||||||
func process<T: JSONAPIDocument>(document: T) {
|
func process<T: CodableJSONAPIDocument>(document: T) {
|
||||||
guard case let .data(body) = document.body else {
|
guard let body = document.body.data else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let x: T.Body.Data = body
|
let x: T.BodyData = body
|
||||||
}
|
}
|
||||||
process(document: peopleResponse)
|
process(document: peopleResponse)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
|
|||||||
#
|
#
|
||||||
|
|
||||||
spec.name = "MP-JSONAPI"
|
spec.name = "MP-JSONAPI"
|
||||||
spec.version = "2.5.0"
|
spec.version = "3.0.0"
|
||||||
spec.summary = "Swift Codable JSON API framework."
|
spec.summary = "Swift Codable JSON API framework."
|
||||||
|
|
||||||
# This description is used to generate tags and improve search results.
|
# This description is used to generate tags and improve search results.
|
||||||
@@ -136,6 +136,6 @@ See the JSON API Spec here: https://jsonapi.org/format/
|
|||||||
# spec.requires_arc = true
|
# spec.requires_arc = true
|
||||||
|
|
||||||
# spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
|
# spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
|
||||||
spec.dependency "Poly", "~> 2.2"
|
spec.dependency "Poly", "~> 2.3.1"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
"repositoryURL": "https://github.com/mattpolzin/Poly.git",
|
"repositoryURL": "https://github.com/mattpolzin/Poly.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "b24fd3b41bf3126d4c6dede3708135182172af60",
|
"revision": "0c9c08204142babc480938d704a23513d11420e5",
|
||||||
"version": "2.2.0"
|
"version": "2.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ let package = Package(
|
|||||||
targets: ["JSONAPITesting"])
|
targets: ["JSONAPITesting"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.2.0")),
|
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.3.1")),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// JSONAPIDocument.swift
|
// Document.swift
|
||||||
// JSONAPI
|
// JSONAPI
|
||||||
//
|
//
|
||||||
// Created by Mathew Polzin on 11/5/18.
|
// Created by Mathew Polzin on 11/5/18.
|
||||||
@@ -7,52 +7,157 @@
|
|||||||
|
|
||||||
import Poly
|
import Poly
|
||||||
|
|
||||||
/// An `EncodableJSONAPIDocument` supports encoding but not decoding.
|
public protocol DocumentBodyDataContext {
|
||||||
/// It is actually more restrictive than `JSONAPIDocument` which supports both
|
|
||||||
/// encoding and decoding.
|
|
||||||
public protocol EncodableJSONAPIDocument: Equatable, Encodable {
|
|
||||||
associatedtype PrimaryResourceBody: JSONAPI.EncodableResourceBody
|
associatedtype PrimaryResourceBody: JSONAPI.EncodableResourceBody
|
||||||
associatedtype MetaType: JSONAPI.Meta
|
associatedtype MetaType: JSONAPI.Meta
|
||||||
associatedtype LinksType: JSONAPI.Links
|
associatedtype LinksType: JSONAPI.Links
|
||||||
associatedtype IncludeType: JSONAPI.Include
|
associatedtype IncludeType: JSONAPI.Include
|
||||||
associatedtype APIDescription: APIDescriptionType
|
|
||||||
associatedtype Error: JSONAPIError
|
|
||||||
|
|
||||||
typealias Body = Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body
|
|
||||||
|
|
||||||
var body: Body { get }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `JSONAPIDocument` supports encoding and decoding of a JSON:API
|
public protocol DocumentBodyContext: DocumentBodyDataContext {
|
||||||
/// compliant Document.
|
associatedtype Error: JSONAPIError
|
||||||
public protocol JSONAPIDocument: EncodableJSONAPIDocument, Decodable where PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: Decodable {}
|
associatedtype BodyData: DocumentBodyData
|
||||||
|
where
|
||||||
|
BodyData.PrimaryResourceBody == PrimaryResourceBody,
|
||||||
|
BodyData.MetaType == MetaType,
|
||||||
|
BodyData.LinksType == LinksType,
|
||||||
|
BodyData.IncludeType == IncludeType
|
||||||
|
}
|
||||||
|
|
||||||
/// A JSON API Document represents the entire body
|
public protocol DocumentBodyData: DocumentBodyDataContext {
|
||||||
/// of a JSON API request or the entire body of
|
/// The document's primary resource body
|
||||||
/// a JSON API response.
|
/// (contains one or many resource objects)
|
||||||
/// Note that this type uses Camel case. If your
|
var primary: PrimaryResourceBody { get }
|
||||||
/// API uses snake case, you will want to use
|
|
||||||
/// a conversion such as the one offerred by the
|
|
||||||
/// Foundation JSONEncoder/Decoder: `KeyDecodingStrategy`
|
|
||||||
public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, APIDescription: APIDescriptionType, Error: JSONAPIError>: EncodableJSONAPIDocument {
|
|
||||||
public typealias Include = IncludeType
|
|
||||||
|
|
||||||
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
/// The document's included objects
|
||||||
/// and metadata information about the API itself.
|
var includes: Includes<IncludeType> { get }
|
||||||
public let apiDescription: APIDescription
|
var meta: MetaType { get }
|
||||||
|
var links: LinksType { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol DocumentBody: DocumentBodyContext {
|
||||||
|
/// `true` if the document represents one or more errors. `false` if the
|
||||||
|
/// document represents JSON:API data and/or metadata.
|
||||||
|
var isError: Bool { get }
|
||||||
|
|
||||||
|
/// Get all errors in the document, if any.
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is _not_ an error response. Otherwise,
|
||||||
|
/// an array containing all errors.
|
||||||
|
var errors: [Error]? { get }
|
||||||
|
|
||||||
|
/// Get the document data
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is an error response. Otherwise,
|
||||||
|
/// a structure containing the primary resource, any included
|
||||||
|
/// resources, metadata, and links.
|
||||||
|
var data: BodyData? { get }
|
||||||
|
|
||||||
|
/// Quick access to the `data`'s primary resource.
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is an error document. Otherwise,
|
||||||
|
/// the primary resource body, which will contain zero/one, one/many
|
||||||
|
/// resources dependening on the `PrimaryResourceBody` type.
|
||||||
|
///
|
||||||
|
/// See `SingleResourceBody` and `ManyResourceBody`.
|
||||||
|
var primaryResource: PrimaryResourceBody? { get }
|
||||||
|
|
||||||
|
/// Quick access to the `data`'s includes.
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is an error document. Otherwise,
|
||||||
|
/// zero or more includes.
|
||||||
|
var includes: Includes<IncludeType>? { get }
|
||||||
|
|
||||||
|
/// The metadata for the error or data document or `nil` if
|
||||||
|
/// no metadata is found.
|
||||||
|
var meta: MetaType? { get }
|
||||||
|
|
||||||
|
/// The links for the error or data document or `nil` if
|
||||||
|
/// no links are found.
|
||||||
|
var links: LinksType? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An `EncodableJSONAPIDocument` supports encoding but not decoding.
|
||||||
|
/// It is actually more restrictive than `JSONAPIDocument` which supports both
|
||||||
|
/// encoding and decoding.
|
||||||
|
public protocol EncodableJSONAPIDocument: Equatable, Encodable, DocumentBodyContext {
|
||||||
|
associatedtype APIDescription: APIDescriptionType
|
||||||
|
associatedtype Body: DocumentBody
|
||||||
|
where
|
||||||
|
Body.PrimaryResourceBody == PrimaryResourceBody,
|
||||||
|
Body.MetaType == MetaType,
|
||||||
|
Body.LinksType == LinksType,
|
||||||
|
Body.IncludeType == IncludeType,
|
||||||
|
Body.Error == Error,
|
||||||
|
Body.BodyData == BodyData
|
||||||
|
|
||||||
/// The Body of the Document. This body is either one or more errors
|
/// The Body of the Document. This body is either one or more errors
|
||||||
/// with links and metadata attempted to parse but not guaranteed or
|
/// with links and metadata attempted to parse but not guaranteed or
|
||||||
/// it is a successful data struct containing all the primary and
|
/// it is a successful data struct containing all the primary and
|
||||||
/// included resources, the metadata, and the links that this
|
/// included resources, the metadata, and the links that this
|
||||||
/// document type specifies.
|
/// document type specifies.
|
||||||
|
var body: Body { get }
|
||||||
|
|
||||||
|
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
||||||
|
/// and metadata information about the API itself.
|
||||||
|
var apiDescription: APIDescription { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `CodableJSONAPIDocument` supports encoding and decoding of a JSON:API
|
||||||
|
/// compliant Document.
|
||||||
|
public protocol CodableJSONAPIDocument: EncodableJSONAPIDocument, Decodable where PrimaryResourceBody: JSONAPI.CodableResourceBody, IncludeType: Decodable {}
|
||||||
|
|
||||||
|
/// A JSON API Document represents the entire body
|
||||||
|
/// of a JSON API request or the entire body of
|
||||||
|
/// a JSON API response.
|
||||||
|
///
|
||||||
|
/// Note that this type uses Camel case. If your
|
||||||
|
/// API uses snake case, you will want to use
|
||||||
|
/// a conversion such as the one offerred by the
|
||||||
|
/// Foundation JSONEncoder/Decoder: `KeyDecodingStrategy`
|
||||||
|
public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, APIDescription: APIDescriptionType, Error: JSONAPIError>: EncodableJSONAPIDocument {
|
||||||
|
public typealias Include = IncludeType
|
||||||
|
public typealias BodyData = Body.Data
|
||||||
|
|
||||||
|
// See `EncodableJSONAPIDocument` for documentation.
|
||||||
|
public let apiDescription: APIDescription
|
||||||
|
|
||||||
|
// See `EncodableJSONAPIDocument` for documentation.
|
||||||
public let body: Body
|
public let body: Body
|
||||||
|
|
||||||
public enum Body: Equatable {
|
public init(apiDescription: APIDescription,
|
||||||
|
errors: [Error],
|
||||||
|
meta: MetaType? = nil,
|
||||||
|
links: LinksType? = nil) {
|
||||||
|
body = .errors(errors, meta: meta, links: links)
|
||||||
|
self.apiDescription = apiDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
public enum Body: DocumentBody, Equatable {
|
||||||
case errors([Error], meta: MetaType?, links: LinksType?)
|
case errors([Error], meta: MetaType?, links: LinksType?)
|
||||||
case data(Data)
|
case data(Data)
|
||||||
|
|
||||||
public struct Data: Equatable {
|
public typealias BodyData = Data
|
||||||
|
|
||||||
|
public struct Data: DocumentBodyData, Equatable {
|
||||||
/// The document's Primary Resource object(s)
|
/// The document's Primary Resource object(s)
|
||||||
public let primary: PrimaryResourceBody
|
public let primary: PrimaryResourceBody
|
||||||
/// The document's included objects
|
/// The document's included objects
|
||||||
@@ -117,97 +222,9 @@ public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(apiDescription: APIDescription, errors: [Error], meta: MetaType? = nil, links: LinksType? = nil) {
|
|
||||||
body = .errors(errors, meta: meta, links: links)
|
|
||||||
self.apiDescription = apiDescription
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(apiDescription: APIDescription,
|
extension Document.Body.Data where PrimaryResourceBody: ResourceBodyAppendable {
|
||||||
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) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, includes: .none, meta: meta, links: links)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where MetaType == NoMetadata {
|
|
||||||
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>, links: LinksType) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, includes: includes, meta: .none, links: links)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where LinksType == NoLinks {
|
|
||||||
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, includes: includes, meta: meta, links: .none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where APIDescription == NoAPIDescription {
|
|
||||||
public init(body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType, links: LinksType) {
|
|
||||||
self.init(apiDescription: .none, body: body, includes: includes, meta: meta, links: links)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where IncludeType == NoIncludes, LinksType == NoLinks {
|
|
||||||
public init(apiDescription: APIDescription, body: PrimaryResourceBody, meta: MetaType) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, meta: meta, links: .none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata {
|
|
||||||
public init(apiDescription: APIDescription, body: PrimaryResourceBody, links: LinksType) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, meta: .none, links: links)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where IncludeType == NoIncludes, APIDescription == NoAPIDescription {
|
|
||||||
public init(body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
|
|
||||||
self.init(apiDescription: .none, body: body, meta: meta, links: links)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where MetaType == NoMetadata, LinksType == NoLinks {
|
|
||||||
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, includes: includes, links: .none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where MetaType == NoMetadata, APIDescription == NoAPIDescription {
|
|
||||||
public init(body: PrimaryResourceBody, includes: Includes<Include>, links: LinksType) {
|
|
||||||
self.init(apiDescription: .none, body: body, includes: includes, links: links)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, LinksType == NoLinks {
|
|
||||||
public init(apiDescription: APIDescription, body: PrimaryResourceBody) {
|
|
||||||
self.init(apiDescription: apiDescription, body: body, includes: .none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where MetaType == NoMetadata, LinksType == NoLinks, APIDescription == NoAPIDescription {
|
|
||||||
public init(body: PrimaryResourceBody, includes: Includes<Include>) {
|
|
||||||
self.init(apiDescription: .none, body: body, includes: includes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, LinksType == NoLinks, APIDescription == NoAPIDescription {
|
|
||||||
public init(body: PrimaryResourceBody) {
|
|
||||||
self.init(apiDescription: .none, body: body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
extension Document.Body.Data where PrimaryResourceBody: Appendable {
|
|
||||||
public func merging(_ other: Document.Body.Data,
|
public func merging(_ other: Document.Body.Data,
|
||||||
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
|
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
|
||||||
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
|
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
|
||||||
@@ -218,7 +235,7 @@ extension Document.Body.Data where PrimaryResourceBody: Appendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Document.Body.Data where PrimaryResourceBody: Appendable, MetaType == NoMetadata, LinksType == NoLinks {
|
extension Document.Body.Data where PrimaryResourceBody: ResourceBodyAppendable, MetaType == NoMetadata, LinksType == NoLinks {
|
||||||
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
|
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
|
||||||
return merging(other,
|
return merging(other,
|
||||||
combiningMetaWith: { _, _ in .none },
|
combiningMetaWith: { _, _ in .none },
|
||||||
@@ -328,12 +345,12 @@ extension Document {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Document: Decodable, JSONAPIDocument where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
|
extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: RootCodingKeys.self)
|
let container = try decoder.container(keyedBy: RootCodingKeys.self)
|
||||||
|
|
||||||
if let noData = NoAPIDescription() as? APIDescription {
|
if let noAPIDescription = NoAPIDescription() as? APIDescription {
|
||||||
apiDescription = noData
|
apiDescription = noAPIDescription
|
||||||
} else {
|
} else {
|
||||||
apiDescription = try container.decode(APIDescription.self, forKey: .jsonapi)
|
apiDescription = try container.decode(APIDescription.self, forKey: .jsonapi)
|
||||||
}
|
}
|
||||||
@@ -372,18 +389,30 @@ extension Document: Decodable, JSONAPIDocument where PrimaryResourceBody: Resour
|
|||||||
if let noData = NoResourceBody() as? PrimaryResourceBody {
|
if let noData = NoResourceBody() as? PrimaryResourceBody {
|
||||||
data = noData
|
data = noData
|
||||||
} else {
|
} else {
|
||||||
|
do {
|
||||||
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
|
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
|
||||||
|
} catch let error as ResourceObjectDecodingError {
|
||||||
|
throw DocumentDecodingError(error)
|
||||||
|
} catch let error as ManyResourceBodyDecodingError {
|
||||||
|
throw DocumentDecodingError(error)
|
||||||
|
} catch let error as DecodingError {
|
||||||
|
throw DocumentDecodingError(error)
|
||||||
|
?? error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
|
let maybeIncludes: Includes<Include>?
|
||||||
|
do {
|
||||||
// TODO come back to this and make robust
|
maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
|
||||||
|
} catch let error as IncludesDecodingError {
|
||||||
|
throw DocumentDecodingError(error)
|
||||||
|
}
|
||||||
|
|
||||||
guard let metaVal = meta else {
|
guard let metaVal = meta else {
|
||||||
throw JSONAPIEncodingError.missingOrMalformedMetadata
|
throw JSONAPICodingError.missingOrMalformedMetadata(path: decoder.codingPath)
|
||||||
}
|
}
|
||||||
guard let linksVal = links else {
|
guard let linksVal = links else {
|
||||||
throw JSONAPIEncodingError.missingOrMalformedLinks
|
throw JSONAPICodingError.missingOrMalformedLinks(path: decoder.codingPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
body = .data(.init(primary: data, includes: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal))
|
body = .data(.init(primary: data, includes: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal))
|
||||||
@@ -420,8 +449,9 @@ extension Document.Body.Data: CustomStringConvertible {
|
|||||||
extension Document {
|
extension Document {
|
||||||
/// A Document that only supports error bodies. This is useful if you wish to pass around a
|
/// A Document that only supports error bodies. This is useful if you wish to pass around a
|
||||||
/// Document type but you wish to constrain it to error values.
|
/// Document type but you wish to constrain it to error values.
|
||||||
@dynamicMemberLookup
|
|
||||||
public struct ErrorDocument: EncodableJSONAPIDocument {
|
public struct ErrorDocument: EncodableJSONAPIDocument {
|
||||||
|
public typealias BodyData = Document.BodyData
|
||||||
|
|
||||||
public var body: Document.Body { return document.body }
|
public var body: Document.Body { return document.body }
|
||||||
|
|
||||||
private let document: Document
|
private let document: Document
|
||||||
@@ -436,19 +466,43 @@ extension Document {
|
|||||||
try container.encode(document)
|
try container.encode(document)
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript<T>(dynamicMember path: KeyPath<Document, T>) -> T {
|
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
||||||
return document[keyPath: path]
|
/// and metadata information about the API itself.
|
||||||
|
public var apiDescription: APIDescription {
|
||||||
|
return document.apiDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all errors in the document, if any.
|
||||||
|
public var errors: [Error] {
|
||||||
|
return document.body.errors ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The metadata for the error or data document or `nil` if
|
||||||
|
/// no metadata is found.
|
||||||
|
public var meta: MetaType? {
|
||||||
|
return document.body.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The links for the error or data document or `nil` if
|
||||||
|
/// no links are found.
|
||||||
|
public var links: LinksType? {
|
||||||
|
return document.body.links
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Document, rhs: ErrorDocument) -> Bool {
|
public static func ==(lhs: Document, rhs: ErrorDocument) -> Bool {
|
||||||
return lhs == rhs.document
|
return lhs == rhs.document
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ErrorDocument, rhs: Document) -> Bool {
|
||||||
|
return lhs.document == rhs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Document that only supports success bodies. This is useful if you wish to pass around a
|
/// A Document that only supports success bodies. This is useful if you wish to pass around a
|
||||||
/// Document type but you wish to constrain it to success values.
|
/// Document type but you wish to constrain it to success values.
|
||||||
@dynamicMemberLookup
|
|
||||||
public struct SuccessDocument: EncodableJSONAPIDocument {
|
public struct SuccessDocument: EncodableJSONAPIDocument {
|
||||||
|
public typealias BodyData = Document.BodyData
|
||||||
|
|
||||||
public var body: Document.Body { return document.body }
|
public var body: Document.Body { return document.body }
|
||||||
|
|
||||||
private let document: Document
|
private let document: Document
|
||||||
@@ -471,38 +525,84 @@ extension Document {
|
|||||||
try container.encode(document)
|
try container.encode(document)
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript<T>(dynamicMember path: KeyPath<Document, T>) -> T {
|
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
||||||
return document[keyPath: path]
|
/// and metadata information about the API itself.
|
||||||
|
public var apiDescription: APIDescription {
|
||||||
|
return document.apiDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the document data
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is an error response. Otherwise,
|
||||||
|
/// a structure containing the primary resource, any included
|
||||||
|
/// resources, metadata, and links.
|
||||||
|
public var data: BodyData? {
|
||||||
|
return document.body.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quick access to the `data`'s primary resource.
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is an error document. Otherwise,
|
||||||
|
/// the primary resource body, which will contain zero/one, one/many
|
||||||
|
/// resources dependening on the `PrimaryResourceBody` type.
|
||||||
|
///
|
||||||
|
/// See `SingleResourceBody` and `ManyResourceBody`.
|
||||||
|
public var primaryResource: PrimaryResourceBody? {
|
||||||
|
return document.body.primaryResource
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quick access to the `data`'s includes.
|
||||||
|
///
|
||||||
|
/// `nil` if the Document is an error document. Otherwise,
|
||||||
|
/// zero or more includes.
|
||||||
|
public var includes: Includes<IncludeType>? {
|
||||||
|
return document.body.includes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The metadata for the error or data document or `nil` if
|
||||||
|
/// no metadata is found.
|
||||||
|
public var meta: MetaType? {
|
||||||
|
return document.body.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The links for the error or data document or `nil` if
|
||||||
|
/// no links are found.
|
||||||
|
public var links: LinksType? {
|
||||||
|
return document.body.links
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Document, rhs: SuccessDocument) -> Bool {
|
public static func ==(lhs: Document, rhs: SuccessDocument) -> Bool {
|
||||||
return lhs == rhs.document
|
return lhs == rhs.document
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: SuccessDocument, rhs: Document) -> Bool {
|
||||||
|
return lhs.document == rhs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Document.ErrorDocument: Decodable, JSONAPIDocument
|
extension Document.ErrorDocument: Decodable, CodableJSONAPIDocument
|
||||||
where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
|
where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
document = try container.decode(Document.self)
|
document = try container.decode(Document.self)
|
||||||
|
|
||||||
guard document.body.isError else {
|
guard document.body.isError else {
|
||||||
throw JSONAPIDocumentDecodingError.foundSuccessDocumentWhenExpectingError
|
throw DocumentDecodingError.foundSuccessDocumentWhenExpectingError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Document.SuccessDocument: Decodable, JSONAPIDocument
|
extension Document.SuccessDocument: Decodable, CodableJSONAPIDocument
|
||||||
where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
|
where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
document = try container.decode(Document.self)
|
document = try container.decode(Document.self)
|
||||||
|
|
||||||
guard !document.body.isError else {
|
guard !document.body.isError else {
|
||||||
throw JSONAPIDocumentDecodingError.foundErrorDocumentWhenExpectingSuccess
|
throw DocumentDecodingError.foundErrorDocumentWhenExpectingSuccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,73 @@
|
|||||||
//
|
//
|
||||||
// DocumentDecodingErro.swift
|
// DocumentDecodingError.swift
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Created by Mathew Polzin on 10/20/19.
|
// Created by Mathew Polzin on 10/20/19.
|
||||||
//
|
//
|
||||||
|
|
||||||
public enum JSONAPIDocumentDecodingError: Swift.Error {
|
public enum DocumentDecodingError: Swift.Error, Equatable {
|
||||||
|
case primaryResource(error: ResourceObjectDecodingError, idx: Int?)
|
||||||
|
case primaryResourceMissing
|
||||||
|
case primaryResourcesMissing
|
||||||
|
|
||||||
|
case includes(error: IncludesDecodingError)
|
||||||
|
|
||||||
case foundErrorDocumentWhenExpectingSuccess
|
case foundErrorDocumentWhenExpectingSuccess
|
||||||
case foundSuccessDocumentWhenExpectingError
|
case foundSuccessDocumentWhenExpectingError
|
||||||
|
|
||||||
|
init(_ decodingError: ResourceObjectDecodingError) {
|
||||||
|
self = .primaryResource(error: decodingError, idx: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ decodingError: ManyResourceBodyDecodingError) {
|
||||||
|
self = .primaryResource(error: decodingError.error, idx: decodingError.idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ decodingError: IncludesDecodingError) {
|
||||||
|
self = .includes(error: decodingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(_ decodingError: DecodingError) {
|
||||||
|
switch decodingError {
|
||||||
|
case .valueNotFound(let type, let context) where Location(context) == .data && type is AbstractResourceObject.Type:
|
||||||
|
self = .primaryResourceMissing
|
||||||
|
case .valueNotFound(let type, let context) where Location(context) == .data && type == UnkeyedDecodingContainer.self:
|
||||||
|
self = .primaryResourcesMissing
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Location: Equatable {
|
||||||
|
case data
|
||||||
|
|
||||||
|
init?(_ context: DecodingError.Context) {
|
||||||
|
guard context.codingPath.contains(where: { $0.stringValue == "data" }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self = .data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DocumentDecodingError: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .primaryResource(error: let error, idx: let idx):
|
||||||
|
let idxString = idx.map { " \($0 + 1)" } ?? ""
|
||||||
|
return "Primary Resource\(idxString) failed to parse because \(error)"
|
||||||
|
case .primaryResourceMissing:
|
||||||
|
return "Primary Resource missing."
|
||||||
|
case .primaryResourcesMissing:
|
||||||
|
return "Primary Resources array missing."
|
||||||
|
|
||||||
|
case .includes(error: let error):
|
||||||
|
return "\(error)"
|
||||||
|
|
||||||
|
case .foundErrorDocumentWhenExpectingSuccess:
|
||||||
|
return "Expected a success document with a 'data' property but found an error document."
|
||||||
|
case .foundSuccessDocumentWhenExpectingError:
|
||||||
|
return "Expected an error document but found a success document with a 'data' property."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,15 @@ public typealias Include = EncodableJSONPoly
|
|||||||
///
|
///
|
||||||
/// If you have
|
/// If you have
|
||||||
///
|
///
|
||||||
/// `let includes: Includes<Include2<Thing1, Thing2>> = ...`
|
/// let includes: Includes<Include2<Thing1, Thing2>> = ...
|
||||||
///
|
///
|
||||||
/// then you can access all `Thing1` included resources with
|
/// then you can access all `Thing1` included resources with
|
||||||
///
|
///
|
||||||
/// `let includedThings = includes[Thing1.self]`
|
/// let includedThings = includes[Thing1.self]
|
||||||
public struct Includes<I: Include>: Encodable, Equatable {
|
public struct Includes<I: Include>: Encodable, Equatable {
|
||||||
public static var none: Includes { return .init(values: []) }
|
public static var none: Includes { return .init(values: []) }
|
||||||
|
|
||||||
let values: [I]
|
public let values: [I]
|
||||||
|
|
||||||
public init(values: [I]) {
|
public init(values: [I]) {
|
||||||
self.values = values
|
self.values = values
|
||||||
@@ -32,7 +32,7 @@ public struct Includes<I: Include>: Encodable, Equatable {
|
|||||||
var container = encoder.unkeyedContainer()
|
var container = encoder.unkeyedContainer()
|
||||||
|
|
||||||
guard I.self != NoIncludes.self else {
|
guard I.self != NoIncludes.self else {
|
||||||
throw JSONAPIEncodingError.illegalEncoding("Attempting to encode Include0, which should be represented by the absense of an 'included' entry altogether.")
|
throw JSONAPICodingError.illegalEncoding("Attempting to encode Include0, which should be represented by the absense of an 'included' entry altogether.", path: encoder.codingPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
for value in values {
|
for value in values {
|
||||||
@@ -56,8 +56,35 @@ extension Includes: Decodable where I: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var valueAggregator = [I]()
|
var valueAggregator = [I]()
|
||||||
|
var idx = 0
|
||||||
while !container.isAtEnd {
|
while !container.isAtEnd {
|
||||||
|
do {
|
||||||
valueAggregator.append(try container.decode(I.self))
|
valueAggregator.append(try container.decode(I.self))
|
||||||
|
idx = idx + 1
|
||||||
|
} catch let error as PolyDecodeNoTypesMatchedError {
|
||||||
|
let errors: [ResourceObjectDecodingError] = error
|
||||||
|
.individualTypeFailures
|
||||||
|
.compactMap { decodingError in
|
||||||
|
switch decodingError.error {
|
||||||
|
case .typeMismatch(_, let context),
|
||||||
|
.valueNotFound(_, let context),
|
||||||
|
.keyNotFound(_, let context),
|
||||||
|
.dataCorrupted(let context):
|
||||||
|
return context.underlyingError as? ResourceObjectDecodingError
|
||||||
|
@unknown default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard errors.count == error.individualTypeFailures.count else {
|
||||||
|
throw IncludesDecodingError(error: error, idx: idx)
|
||||||
|
}
|
||||||
|
throw IncludesDecodingError(
|
||||||
|
error: IncludeDecodingError(failures: errors),
|
||||||
|
idx: idx
|
||||||
|
)
|
||||||
|
} catch let error {
|
||||||
|
throw IncludesDecodingError(error: error, idx: idx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
values = valueAggregator
|
values = valueAggregator
|
||||||
@@ -177,3 +204,32 @@ extension Includes where I: _Poly11 {
|
|||||||
return values.compactMap { $0.k }
|
return values.compactMap { $0.k }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - DecodingError
|
||||||
|
public struct IncludesDecodingError: Swift.Error, Equatable {
|
||||||
|
public let error: Swift.Error
|
||||||
|
public let idx: Int
|
||||||
|
|
||||||
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
||||||
|
return lhs.idx == rhs.idx
|
||||||
|
&& String(describing: lhs) == String(describing: rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IncludesDecodingError: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
return "Include \(idx + 1) failed to parse: \(error)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct IncludeDecodingError: Swift.Error, Equatable, CustomStringConvertible {
|
||||||
|
public let failures: [ResourceObjectDecodingError]
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return failures
|
||||||
|
.enumerated()
|
||||||
|
.map {
|
||||||
|
"\nCould not have been Include Type \($0.offset + 1) because:\n\($0.element)"
|
||||||
|
}.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,60 +10,60 @@
|
|||||||
/// array should be used for no results).
|
/// array should be used for no results).
|
||||||
public protocol OptionalEncodablePrimaryResource: Equatable, Encodable {}
|
public protocol OptionalEncodablePrimaryResource: Equatable, Encodable {}
|
||||||
|
|
||||||
/// An `EncodablePrimaryResource` is a `PrimaryResource` that only supports encoding.
|
/// An `EncodablePrimaryResource` is a `CodablePrimaryResource` that only supports encoding.
|
||||||
/// This is actually more restrictave than `PrimaryResource`, which supports both encoding and
|
|
||||||
/// decoding.
|
|
||||||
public protocol EncodablePrimaryResource: OptionalEncodablePrimaryResource {}
|
public protocol EncodablePrimaryResource: OptionalEncodablePrimaryResource {}
|
||||||
|
|
||||||
/// This protocol allows for `SingleResourceBody` to contain a `null`
|
/// This protocol allows for `SingleResourceBody` to contain a `null`
|
||||||
/// data object where `ManyResourceBody` cannot (because an empty
|
/// data object where `ManyResourceBody` cannot (because an empty
|
||||||
/// array should be used for no results).
|
/// array should be used for no results).
|
||||||
public protocol OptionalPrimaryResource: OptionalEncodablePrimaryResource, Decodable {}
|
public protocol OptionalCodablePrimaryResource: OptionalEncodablePrimaryResource, Decodable {}
|
||||||
|
|
||||||
/// A `PrimaryResource` is a type that can be used in the body of a JSON API
|
/// A `CodablePrimaryResource` is a type that can be used in the body of a JSON API
|
||||||
/// document as the primary resource.
|
/// document as the primary resource.
|
||||||
public protocol PrimaryResource: EncodablePrimaryResource, OptionalPrimaryResource {}
|
public protocol CodablePrimaryResource: EncodablePrimaryResource, OptionalCodablePrimaryResource {}
|
||||||
|
|
||||||
extension Optional: OptionalEncodablePrimaryResource where Wrapped: EncodablePrimaryResource {}
|
extension Optional: OptionalEncodablePrimaryResource where Wrapped: EncodablePrimaryResource {}
|
||||||
|
|
||||||
extension Optional: OptionalPrimaryResource where Wrapped: PrimaryResource {}
|
extension Optional: OptionalCodablePrimaryResource where Wrapped: CodablePrimaryResource {}
|
||||||
|
|
||||||
/// An `EncodableResourceBody` is a `ResourceBody` that only supports being
|
/// An `EncodableResourceBody` is a `ResourceBody` that only supports being
|
||||||
/// encoded. It is actually weaker than `ResourceBody`, which supports both encoding
|
/// encoded. It is actually weaker than `ResourceBody`, which supports both encoding
|
||||||
/// and decoding.
|
/// and decoding.
|
||||||
public protocol EncodableResourceBody: Equatable, Encodable {}
|
public protocol EncodableResourceBody: Equatable, Encodable {
|
||||||
|
associatedtype PrimaryResource
|
||||||
|
}
|
||||||
|
|
||||||
/// A ResourceBody is a representation of the body of the JSON API Document.
|
/// A `CodableResourceBody` is a representation of the body of the JSON:API Document.
|
||||||
/// It can either be one resource (which can be specified as optional or not)
|
/// It can either be one resource (which can be specified as optional or not)
|
||||||
/// or it can contain many resources (and array with zero or more entries).
|
/// or it can contain many resources (and array with zero or more entries).
|
||||||
public protocol ResourceBody: Decodable, EncodableResourceBody {}
|
public protocol CodableResourceBody: Decodable, EncodableResourceBody {}
|
||||||
|
|
||||||
/// A `ResourceBody` that has the ability to take on more primary
|
/// A `ResourceBody` that has the ability to take on more primary
|
||||||
/// resources by appending another similarly typed `ResourceBody`.
|
/// resources by appending another similarly typed `ResourceBody`.
|
||||||
public protocol Appendable {
|
public protocol ResourceBodyAppendable {
|
||||||
func appending(_ other: Self) -> Self
|
func appending(_ other: Self) -> Self
|
||||||
}
|
}
|
||||||
|
|
||||||
public func +<R: Appendable>(_ left: R, right: R) -> R {
|
public func +<R: ResourceBodyAppendable>(_ left: R, right: R) -> R {
|
||||||
return left.appending(right)
|
return left.appending(right)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type allowing for a document body containing 1 primary resource.
|
/// A type allowing for a document body containing 1 primary resource.
|
||||||
/// If the `Entity` specialization is an `Optional` type, the body can contain
|
/// If the `Entity` specialization is an `Optional` type, the body can contain
|
||||||
/// 0 or 1 primary resources.
|
/// 0 or 1 primary resources.
|
||||||
public struct SingleResourceBody<Entity: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
public struct SingleResourceBody<PrimaryResource: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
||||||
public let value: Entity
|
public let value: PrimaryResource
|
||||||
|
|
||||||
public init(resourceObject: Entity) {
|
public init(resourceObject: PrimaryResource) {
|
||||||
self.value = resourceObject
|
self.value = resourceObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type allowing for a document body containing 0 or more primary resources.
|
/// A type allowing for a document body containing 0 or more primary resources.
|
||||||
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, Appendable {
|
public struct ManyResourceBody<PrimaryResource: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, ResourceBodyAppendable {
|
||||||
public let values: [Entity]
|
public let values: [PrimaryResource]
|
||||||
|
|
||||||
public init(resourceObjects: [Entity]) {
|
public init(resourceObjects: [PrimaryResource]) {
|
||||||
values = resourceObjects
|
values = resourceObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,9 @@ public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: Encoda
|
|||||||
|
|
||||||
/// Use NoResourceBody to indicate you expect a JSON API document to not
|
/// Use NoResourceBody to indicate you expect a JSON API document to not
|
||||||
/// contain a "data" top-level key.
|
/// contain a "data" top-level key.
|
||||||
public struct NoResourceBody: ResourceBody {
|
public struct NoResourceBody: CodableResourceBody {
|
||||||
|
public typealias PrimaryResource = Void
|
||||||
|
|
||||||
public static var none: NoResourceBody { return NoResourceBody() }
|
public static var none: NoResourceBody { return NoResourceBody() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ extension SingleResourceBody {
|
|||||||
var container = encoder.singleValueContainer()
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
let anyNil: Any? = nil
|
let anyNil: Any? = nil
|
||||||
let nilValue = anyNil as? Entity
|
let nilValue = anyNil as? PrimaryResource
|
||||||
guard value != nilValue else {
|
guard value != nilValue else {
|
||||||
try container.encodeNil()
|
try container.encodeNil()
|
||||||
return
|
return
|
||||||
@@ -94,18 +96,18 @@ extension SingleResourceBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SingleResourceBody: Decodable, ResourceBody where Entity: OptionalPrimaryResource {
|
extension SingleResourceBody: Decodable, CodableResourceBody where PrimaryResource: OptionalCodablePrimaryResource {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
let anyNil: Any? = nil
|
let anyNil: Any? = nil
|
||||||
if container.decodeNil(),
|
if container.decodeNil(),
|
||||||
let val = anyNil as? Entity {
|
let val = anyNil as? PrimaryResource {
|
||||||
value = val
|
value = val
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
value = try container.decode(Entity.self)
|
value = try container.decode(PrimaryResource.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,12 +121,21 @@ extension ManyResourceBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ManyResourceBody: Decodable, ResourceBody where Entity: PrimaryResource {
|
extension ManyResourceBody: Decodable, CodableResourceBody where PrimaryResource: CodablePrimaryResource {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
var container = try decoder.unkeyedContainer()
|
var container = try decoder.unkeyedContainer()
|
||||||
var valueAggregator = [Entity]()
|
var valueAggregator = [PrimaryResource]()
|
||||||
|
var idx = 0
|
||||||
while !container.isAtEnd {
|
while !container.isAtEnd {
|
||||||
valueAggregator.append(try container.decode(Entity.self))
|
do {
|
||||||
|
valueAggregator.append(try container.decode(PrimaryResource.self))
|
||||||
|
} catch let error as ResourceObjectDecodingError {
|
||||||
|
throw ManyResourceBodyDecodingError(
|
||||||
|
error: error,
|
||||||
|
idx: idx
|
||||||
|
)
|
||||||
|
}
|
||||||
|
idx = idx + 1
|
||||||
}
|
}
|
||||||
values = valueAggregator
|
values = valueAggregator
|
||||||
}
|
}
|
||||||
@@ -143,3 +154,9 @@ extension ManyResourceBody: CustomStringConvertible {
|
|||||||
return "PrimaryResourceBody(\(String(describing: values)))"
|
return "PrimaryResourceBody(\(String(describing: values)))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - DecodingError
|
||||||
|
public struct ManyResourceBodyDecodingError: Swift.Error, Equatable {
|
||||||
|
public let error: ResourceObjectDecodingError
|
||||||
|
public let idx: Int
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
//
|
|
||||||
// EncodingError.swift
|
|
||||||
// JSONAPI
|
|
||||||
//
|
|
||||||
// Created by Mathew Polzin on 12/7/18.
|
|
||||||
//
|
|
||||||
|
|
||||||
public enum JSONAPIEncodingError: Swift.Error {
|
|
||||||
case typeMismatch(expected: String, found: String)
|
|
||||||
case illegalEncoding(String)
|
|
||||||
case illegalDecoding(String)
|
|
||||||
case missingOrMalformedMetadata
|
|
||||||
case missingOrMalformedLinks
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// Most of the JSON:API Spec defined Error fields.
|
/// Most of the JSON:API Spec defined Error fields.
|
||||||
public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Equatable, ErrorDictType {
|
public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Equatable, ErrorDictType, CustomStringConvertible {
|
||||||
/// a unique identifier for this particular occurrence of the problem
|
/// a unique identifier for this particular occurrence of the problem
|
||||||
public let id: IdType?
|
public let id: IdType?
|
||||||
|
|
||||||
// public let links: Links? // we skip this for now to avoid adding complexity to using this basic type.
|
// public let links: Links? // we skip this for now to avoid adding complexity to using this basic type.
|
||||||
|
|
||||||
/// the HTTP status code applicable to this problem
|
/// the HTTP status code applicable to this problem
|
||||||
public let status: String?
|
public let status: String?
|
||||||
/// an application-specific error code
|
/// an application-specific error code
|
||||||
@@ -20,6 +22,7 @@ public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Eq
|
|||||||
public let detail: String?
|
public let detail: String?
|
||||||
/// an object containing references to the source of the error
|
/// an object containing references to the source of the error
|
||||||
public let source: Source?
|
public let source: Source?
|
||||||
|
|
||||||
// public let meta: Meta? // we skip this for now to avoid adding complexity to using this basic type
|
// public let meta: Meta? // we skip this for now to avoid adding complexity to using this basic type
|
||||||
|
|
||||||
public init(id: IdType? = nil,
|
public init(id: IdType? = nil,
|
||||||
@@ -61,6 +64,10 @@ public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Eq
|
|||||||
].compactMap { $0 }
|
].compactMap { $0 }
|
||||||
return Dictionary(uniqueKeysWithValues: keysAndValues)
|
return Dictionary(uniqueKeysWithValues: keysAndValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return definedFields.map { "\($0.key): \($0.value)" }.sorted().joined(separator: ", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `BasicJSONAPIError` optionally decodes many possible fields
|
/// `BasicJSONAPIError` optionally decodes many possible fields
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
/// `GenericJSONAPIError` can be used to specify whatever error
|
/// `GenericJSONAPIError` can be used to specify whatever error
|
||||||
/// payload you expect to need to parse in responses and handle any
|
/// payload you expect to need to parse in responses and handle any
|
||||||
/// other payload structure as `.unknownError`.
|
/// other payload structure as `.unknownError`.
|
||||||
public enum GenericJSONAPIError<ErrorPayload: Codable & Equatable>: JSONAPIError {
|
public enum GenericJSONAPIError<ErrorPayload: Codable & Equatable>: JSONAPIError, CustomStringConvertible {
|
||||||
case unknownError
|
case unknownError
|
||||||
case error(ErrorPayload)
|
case error(ErrorPayload)
|
||||||
|
|
||||||
@@ -35,6 +35,15 @@ public enum GenericJSONAPIError<ErrorPayload: Codable & Equatable>: JSONAPIError
|
|||||||
public static var unknown: Self {
|
public static var unknown: Self {
|
||||||
return .unknownError
|
return .unknownError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .unknownError:
|
||||||
|
return "unknown error"
|
||||||
|
case .error(let payload):
|
||||||
|
return String(describing: payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension GenericJSONAPIError {
|
public extension GenericJSONAPIError {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// JSONAPICodingError.swift
|
||||||
|
// JSONAPI
|
||||||
|
//
|
||||||
|
// Created by Mathew Polzin on 12/7/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
public enum JSONAPICodingError: Swift.Error {
|
||||||
|
case typeMismatch(expected: String, found: String, path: [CodingKey])
|
||||||
|
case quantityMismatch(expected: Quantity, path: [CodingKey])
|
||||||
|
case illegalEncoding(String, path: [CodingKey])
|
||||||
|
case illegalDecoding(String, path: [CodingKey])
|
||||||
|
case missingOrMalformedMetadata(path: [CodingKey])
|
||||||
|
case missingOrMalformedLinks(path: [CodingKey])
|
||||||
|
|
||||||
|
public enum Quantity: String, Equatable {
|
||||||
|
case one
|
||||||
|
case many
|
||||||
|
|
||||||
|
public var other: Quantity {
|
||||||
|
switch self {
|
||||||
|
case .one: return .many
|
||||||
|
case .many: return .one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// Created by Mathew Polzin on 11/24/18.
|
// Created by Mathew Polzin on 11/24/18.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// A Links structure should contain nothing but JSONAPI.Link properties.
|
/// A Links structure should contain nothing but `JSONAPI.Link` properties.
|
||||||
public protocol Links: Codable, Equatable {}
|
public protocol Links: Codable, Equatable {}
|
||||||
|
|
||||||
/// Use NoLinks where no links should belong to a JSON API component
|
/// Use NoLinks where no links should belong to a JSON API component
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// Conform a type to this protocol to indicate it can be encoded to or decoded from
|
/// Conform a type to this protocol to indicate it can be encoded to or decoded from
|
||||||
/// the meta data attached to a component of a JSON API document. Different meta data
|
/// the meta data attached to a component of a JSON:API document. Different meta data
|
||||||
/// can be stored all over the place: On the root document, on a resource object, on
|
/// can be stored all over the place: On the root document, on a resource object, on
|
||||||
/// link objects, etc.
|
/// link objects, etc.
|
||||||
///
|
///
|
||||||
/// JSON API Metadata is totally open ended. It can take whatever JSON-compliant structure
|
/// JSON:API Metadata is totally open ended. It can take whatever JSON-compliant structure
|
||||||
/// the server and client agree upon.
|
/// the server and client agree upon.
|
||||||
public protocol Meta: Codable, Equatable {
|
public protocol Meta: Codable, Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We make Optional a Meta if it wraps a Meta so that Metadata can be specified as
|
// We make Optional a Meta if it wraps a Meta so that
|
||||||
// nullable.
|
// Metadata can be specified as nullable.
|
||||||
extension Optional: Meta where Wrapped: Meta {}
|
extension Optional: Meta where Wrapped: Meta {}
|
||||||
|
|
||||||
/// Use this type when you want to specify not to encode or decode any metadata
|
/// Use this type when you want to specify not to encode or decode any metadata
|
||||||
|
|||||||
@@ -5,13 +5,21 @@
|
|||||||
// Created by Mathew Polzin on 11/13/18.
|
// Created by Mathew Polzin on 11/13/18.
|
||||||
//
|
//
|
||||||
|
|
||||||
public protocol AttributeType: Codable {
|
public protocol AbstractAttributeType {
|
||||||
|
var rawValueType: Any.Type { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol AttributeType: Codable, AbstractAttributeType {
|
||||||
associatedtype RawValue: Codable
|
associatedtype RawValue: Codable
|
||||||
associatedtype ValueType
|
associatedtype ValueType
|
||||||
|
|
||||||
var value: ValueType { get }
|
var value: ValueType { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AttributeType {
|
||||||
|
public var rawValueType: Any.Type { return RawValue.self }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: TransformedAttribute
|
// MARK: TransformedAttribute
|
||||||
|
|
||||||
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
|
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
|
||||||
@@ -141,7 +149,8 @@ extension Attribute {
|
|||||||
// MARK: Attribute decoding and encoding defaults
|
// MARK: Attribute decoding and encoding defaults
|
||||||
|
|
||||||
extension AttributeType {
|
extension AttributeType {
|
||||||
public static func defaultDecoding<Container: KeyedDecodingContainerProtocol>(from container: Container, forKey key: Container.Key) throws -> Self {
|
public static func defaultDecoding<Container: KeyedDecodingContainerProtocol>(from container: Container,
|
||||||
|
forKey key: Container.Key) throws -> Self {
|
||||||
return try container.decode(Self.self, forKey: key)
|
return try container.decode(Self.self, forKey: key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ public protocol OptionalId: Codable {
|
|||||||
init(rawValue: RawType)
|
init(rawValue: RawType)
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol IdType: OptionalId, CustomStringConvertible, Hashable where RawType: RawIdType {}
|
/// marker protocol
|
||||||
|
public protocol AbstractId {}
|
||||||
|
|
||||||
|
public protocol IdType: AbstractId, OptionalId, CustomStringConvertible, Hashable where RawType: RawIdType {}
|
||||||
|
|
||||||
extension Optional: MaybeRawId where Wrapped: Codable & Equatable {}
|
extension Optional: MaybeRawId where Wrapped: Codable & Equatable {}
|
||||||
extension Optional: OptionalId where Wrapped: IdType {
|
extension Optional: OptionalId where Wrapped: IdType {
|
||||||
@@ -94,7 +97,7 @@ public struct Id<RawType: MaybeRawId, IdentifiableType: JSONAPI.JSONTyped>: Equa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {
|
extension Id: Hashable, CustomStringConvertible, AbstractId, IdType where RawType: RawIdType {
|
||||||
public static func id(from rawValue: RawType) -> Id<RawType, IdentifiableType> {
|
public static func id(from rawValue: RawType) -> Id<RawType, IdentifiableType> {
|
||||||
return Id(rawValue: rawValue)
|
return Id(rawValue: rawValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,69 +18,147 @@ import Poly
|
|||||||
public typealias EncodableJSONPoly = Poly & EncodablePrimaryResource
|
public typealias EncodableJSONPoly = Poly & EncodablePrimaryResource
|
||||||
|
|
||||||
public typealias EncodablePolyWrapped = Encodable & Equatable
|
public typealias EncodablePolyWrapped = Encodable & Equatable
|
||||||
public typealias PolyWrapped = EncodablePolyWrapped & Decodable
|
public typealias CodablePolyWrapped = EncodablePolyWrapped & Decodable
|
||||||
|
|
||||||
extension Poly0: PrimaryResource {
|
extension Poly0: CodablePrimaryResource {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
throw JSONAPIEncodingError.illegalDecoding("Attempted to decode Poly0, which should represent a thing that is not expected to be found in a document.")
|
throw JSONAPICodingError.illegalDecoding("Attempted to decode Poly0, which should represent a thing that is not expected to be found in a document.", path: decoder.codingPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
throw JSONAPIEncodingError.illegalEncoding("Attempted to encode Poly0, which should represent a thing that is not expected to be found in a document.")
|
throw JSONAPICodingError.illegalEncoding("Attempted to encode Poly0, which should represent a thing that is not expected to be found in a document.", path: encoder.codingPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 1 type
|
// MARK: - 1 type
|
||||||
extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped {}
|
extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where A: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly1: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped {}
|
extension Poly1: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 2 types
|
// MARK: - 2 types
|
||||||
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
|
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly2: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped {}
|
extension Poly2: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 3 types
|
// MARK: - 3 types
|
||||||
extension Poly3: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped {}
|
extension Poly3: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly3: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
extension Poly3: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 4 types
|
// MARK: - 4 types
|
||||||
extension Poly4: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped {}
|
extension Poly4: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly4: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
extension Poly4: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 5 types
|
// MARK: - 5 types
|
||||||
extension Poly5: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped {}
|
extension Poly5: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly5: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
extension Poly5: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 6 types
|
// MARK: - 6 types
|
||||||
extension Poly6: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped {}
|
extension Poly6: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly6: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
extension Poly6: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 7 types
|
// MARK: - 7 types
|
||||||
extension Poly7: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped {}
|
extension Poly7: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where
|
||||||
|
A: EncodablePolyWrapped,
|
||||||
|
B: EncodablePolyWrapped,
|
||||||
|
C: EncodablePolyWrapped,
|
||||||
|
D: EncodablePolyWrapped,
|
||||||
|
E: EncodablePolyWrapped,
|
||||||
|
F: EncodablePolyWrapped,
|
||||||
|
G: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly7: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
extension Poly7: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 8 types
|
// MARK: - 8 types
|
||||||
extension Poly8: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped {}
|
extension Poly8: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where
|
||||||
|
A: EncodablePolyWrapped,
|
||||||
|
B: EncodablePolyWrapped,
|
||||||
|
C: EncodablePolyWrapped,
|
||||||
|
D: EncodablePolyWrapped,
|
||||||
|
E: EncodablePolyWrapped,
|
||||||
|
F: EncodablePolyWrapped,
|
||||||
|
G: EncodablePolyWrapped,
|
||||||
|
H: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly8: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
extension Poly8: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped, H: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 9 types
|
// MARK: - 9 types
|
||||||
extension Poly9: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped, I: EncodablePolyWrapped {}
|
extension Poly9: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where
|
||||||
|
A: EncodablePolyWrapped,
|
||||||
|
B: EncodablePolyWrapped,
|
||||||
|
C: EncodablePolyWrapped,
|
||||||
|
D: EncodablePolyWrapped,
|
||||||
|
E: EncodablePolyWrapped,
|
||||||
|
F: EncodablePolyWrapped,
|
||||||
|
G: EncodablePolyWrapped,
|
||||||
|
H: EncodablePolyWrapped,
|
||||||
|
I: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly9: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
|
extension Poly9: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped, H: CodablePolyWrapped, I: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 10 types
|
// MARK: - 10 types
|
||||||
extension Poly10: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped, I: EncodablePolyWrapped, J: EncodablePolyWrapped {}
|
extension Poly10: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where
|
||||||
|
A: EncodablePolyWrapped,
|
||||||
|
B: EncodablePolyWrapped,
|
||||||
|
C: EncodablePolyWrapped,
|
||||||
|
D: EncodablePolyWrapped,
|
||||||
|
E: EncodablePolyWrapped,
|
||||||
|
F: EncodablePolyWrapped,
|
||||||
|
G: EncodablePolyWrapped,
|
||||||
|
H: EncodablePolyWrapped,
|
||||||
|
I: EncodablePolyWrapped,
|
||||||
|
J: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly10: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped, J: PolyWrapped {}
|
extension Poly10: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped, H: CodablePolyWrapped, I: CodablePolyWrapped, J: CodablePolyWrapped {}
|
||||||
|
|
||||||
// MARK: - 11 types
|
// MARK: - 11 types
|
||||||
extension Poly11: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped, I: EncodablePolyWrapped, J: EncodablePolyWrapped, K: EncodablePolyWrapped {}
|
extension Poly11: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||||
|
where
|
||||||
|
A: EncodablePolyWrapped,
|
||||||
|
B: EncodablePolyWrapped,
|
||||||
|
C: EncodablePolyWrapped,
|
||||||
|
D: EncodablePolyWrapped,
|
||||||
|
E: EncodablePolyWrapped,
|
||||||
|
F: EncodablePolyWrapped,
|
||||||
|
G: EncodablePolyWrapped,
|
||||||
|
H: EncodablePolyWrapped,
|
||||||
|
I: EncodablePolyWrapped,
|
||||||
|
J: EncodablePolyWrapped,
|
||||||
|
K: EncodablePolyWrapped {}
|
||||||
|
|
||||||
extension Poly11: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped, J: PolyWrapped, K: PolyWrapped {}
|
extension Poly11: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||||
|
where
|
||||||
|
A: CodablePolyWrapped,
|
||||||
|
B: CodablePolyWrapped,
|
||||||
|
C: CodablePolyWrapped,
|
||||||
|
D: CodablePolyWrapped,
|
||||||
|
E: CodablePolyWrapped,
|
||||||
|
F: CodablePolyWrapped,
|
||||||
|
G: CodablePolyWrapped,
|
||||||
|
H: CodablePolyWrapped,
|
||||||
|
I: CodablePolyWrapped,
|
||||||
|
J: CodablePolyWrapped,
|
||||||
|
K: CodablePolyWrapped {}
|
||||||
|
|||||||
@@ -170,18 +170,36 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
|
|||||||
// succeeds and then attempt to coerce nil to a Identifier
|
// succeeds and then attempt to coerce nil to a Identifier
|
||||||
// type at which point we can store nil in `id`.
|
// type at which point we can store nil in `id`.
|
||||||
let anyNil: Any? = nil
|
let anyNil: Any? = nil
|
||||||
if try container.decodeNil(forKey: .data),
|
if try container.decodeNil(forKey: .data) {
|
||||||
let val = anyNil as? Identifiable.Identifier {
|
guard let val = anyNil as? Identifiable.Identifier else {
|
||||||
|
throw DecodingError.valueNotFound(
|
||||||
|
Self.self,
|
||||||
|
DecodingError.Context(
|
||||||
|
codingPath: decoder.codingPath,
|
||||||
|
debugDescription: "Expected non-null relationship data."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
id = val
|
id = val
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
let identifier: KeyedDecodingContainer<ResourceIdentifierCodingKeys>
|
||||||
|
do {
|
||||||
|
identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||||
|
} catch let error as DecodingError {
|
||||||
|
guard case let .typeMismatch(type, context) = error,
|
||||||
|
type is _DictionaryType.Type else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
throw JSONAPICodingError.quantityMismatch(expected: .one,
|
||||||
|
path: context.codingPath)
|
||||||
|
}
|
||||||
|
|
||||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||||
|
|
||||||
guard type == Identifiable.jsonType else {
|
guard type == Identifiable.jsonType else {
|
||||||
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.jsonType, found: type)
|
throw JSONAPICodingError.typeMismatch(expected: Identifiable.jsonType, found: type, path: decoder.codingPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
|
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
|
||||||
@@ -230,7 +248,17 @@ extension ToManyRelationship: Codable {
|
|||||||
links = try container.decode(LinksType.self, forKey: .links)
|
links = try container.decode(LinksType.self, forKey: .links)
|
||||||
}
|
}
|
||||||
|
|
||||||
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
|
var identifiers: UnkeyedDecodingContainer
|
||||||
|
do {
|
||||||
|
identifiers = try container.nestedUnkeyedContainer(forKey: .data)
|
||||||
|
} catch let error as DecodingError {
|
||||||
|
guard case let .typeMismatch(type, context) = error,
|
||||||
|
type is _ArrayType.Type else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
throw JSONAPICodingError.quantityMismatch(expected: .many,
|
||||||
|
path: context.codingPath)
|
||||||
|
}
|
||||||
|
|
||||||
var newIds = [Relatable.Identifier]()
|
var newIds = [Relatable.Identifier]()
|
||||||
while !identifiers.isAtEnd {
|
while !identifiers.isAtEnd {
|
||||||
@@ -239,7 +267,7 @@ extension ToManyRelationship: Codable {
|
|||||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||||
|
|
||||||
guard type == Relatable.jsonType else {
|
guard type == Relatable.jsonType else {
|
||||||
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.jsonType, found: type)
|
throw JSONAPICodingError.typeMismatch(expected: Relatable.jsonType, found: type, path: decoder.codingPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
|
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
|
||||||
@@ -277,3 +305,9 @@ extension ToOneRelationship: CustomStringConvertible {
|
|||||||
extension ToManyRelationship: CustomStringConvertible {
|
extension ToManyRelationship: CustomStringConvertible {
|
||||||
public var description: String { return "Relationship([\(ids.map(String.init(describing:)).joined(separator: ", "))])" }
|
public var description: String { return "Relationship([\(ids.map(String.init(describing:)).joined(separator: ", "))])" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private protocol _DictionaryType {}
|
||||||
|
extension Dictionary: _DictionaryType {}
|
||||||
|
|
||||||
|
private protocol _ArrayType {}
|
||||||
|
extension Array: _ArrayType {}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user