mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Merge pull request #48 from mattpolzin/feature/indentation
Feature/indentation
This commit is contained in:
@@ -7,58 +7,58 @@
|
||||
|
||||
/// This is what the JSON API Spec calls the "JSON:API Object"
|
||||
public protocol APIDescriptionType: Codable, Equatable {
|
||||
associatedtype Meta
|
||||
associatedtype Meta
|
||||
}
|
||||
|
||||
/// This is what the JSON API Spec calls the "JSON:API Object"
|
||||
public struct APIDescription<Meta: JSONAPI.Meta>: APIDescriptionType {
|
||||
public let version: String
|
||||
public let meta: Meta
|
||||
public let version: String
|
||||
public let meta: Meta
|
||||
|
||||
public init(version: String, meta: Meta) {
|
||||
self.version = version
|
||||
self.meta = meta
|
||||
}
|
||||
public init(version: String, meta: Meta) {
|
||||
self.version = version
|
||||
self.meta = meta
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be used as `APIDescriptionType` for Documents that do not
|
||||
/// have any API Description (a.k.a. "JSON:API Object").
|
||||
public struct NoAPIDescription: APIDescriptionType, CustomStringConvertible {
|
||||
public typealias Meta = NoMetadata
|
||||
public typealias Meta = NoMetadata
|
||||
|
||||
public init() {}
|
||||
public init() {}
|
||||
|
||||
public static var none: NoAPIDescription { return .init() }
|
||||
public static var none: NoAPIDescription { return .init() }
|
||||
|
||||
public var description: String { return "No JSON:API Object" }
|
||||
public var description: String { return "No JSON:API Object" }
|
||||
}
|
||||
|
||||
extension APIDescription {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case version
|
||||
case meta
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case version
|
||||
case meta
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
// The spec says that if a version is not specified, it should be assumed to be at least 1.0
|
||||
version = (try? container.decode(String.self, forKey: .version)) ?? "1.0"
|
||||
// The spec says that if a version is not specified, it should be assumed to be at least 1.0
|
||||
version = (try? container.decode(String.self, forKey: .version)) ?? "1.0"
|
||||
|
||||
if let metaVal = NoMetadata() as? Meta {
|
||||
meta = metaVal
|
||||
} else {
|
||||
meta = try container.decode(Meta.self, forKey: .meta)
|
||||
}
|
||||
}
|
||||
if let metaVal = NoMetadata() as? Meta {
|
||||
meta = metaVal
|
||||
} else {
|
||||
meta = try container.decode(Meta.self, forKey: .meta)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(version, forKey: .version)
|
||||
try container.encode(version, forKey: .version)
|
||||
|
||||
if Meta.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
}
|
||||
if Meta.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,29 +116,29 @@ public protocol CodableJSONAPIDocument: EncodableJSONAPIDocument, Decodable wher
|
||||
/// 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 Include = IncludeType
|
||||
public typealias BodyData = Body.Data
|
||||
|
||||
// See `EncodableJSONAPIDocument` for documentation.
|
||||
public let apiDescription: APIDescription
|
||||
public let apiDescription: APIDescription
|
||||
|
||||
// See `EncodableJSONAPIDocument` for documentation.
|
||||
public let body: Body
|
||||
public let body: Body
|
||||
|
||||
public init(apiDescription: APIDescription,
|
||||
public init(apiDescription: APIDescription,
|
||||
errors: [Error],
|
||||
meta: MetaType? = nil,
|
||||
links: LinksType? = nil) {
|
||||
body = .errors(errors, meta: meta, links: links)
|
||||
self.apiDescription = apiDescription
|
||||
}
|
||||
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(
|
||||
public init(apiDescription: APIDescription,
|
||||
body: PrimaryResourceBody,
|
||||
includes: Includes<Include>,
|
||||
meta: MetaType,
|
||||
links: LinksType) {
|
||||
self.body = .data(
|
||||
.init(
|
||||
primary: body,
|
||||
includes: includes,
|
||||
@@ -146,8 +146,8 @@ public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaT
|
||||
links: links
|
||||
)
|
||||
)
|
||||
self.apiDescription = apiDescription
|
||||
}
|
||||
self.apiDescription = apiDescription
|
||||
}
|
||||
}
|
||||
|
||||
extension Document {
|
||||
@@ -225,124 +225,124 @@ extension Document {
|
||||
}
|
||||
|
||||
extension Document.Body.Data where PrimaryResourceBody: ResourceBodyAppendable {
|
||||
public func merging(_ other: Document.Body.Data,
|
||||
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
|
||||
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
|
||||
return Document.Body.Data(primary: primary.appending(other.primary),
|
||||
includes: includes.appending(other.includes),
|
||||
meta: metaMerge(meta, other.meta),
|
||||
links: linksMerge(links, other.links))
|
||||
}
|
||||
public func merging(_ other: Document.Body.Data,
|
||||
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
|
||||
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
|
||||
return Document.Body.Data(primary: primary.appending(other.primary),
|
||||
includes: includes.appending(other.includes),
|
||||
meta: metaMerge(meta, other.meta),
|
||||
links: linksMerge(links, other.links))
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body.Data where PrimaryResourceBody: ResourceBodyAppendable, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
|
||||
return merging(other,
|
||||
combiningMetaWith: { _, _ in .none },
|
||||
combiningLinksWith: { _, _ in .none })
|
||||
}
|
||||
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
|
||||
return merging(other,
|
||||
combiningMetaWith: { _, _ in .none },
|
||||
combiningLinksWith: { _, _ in .none })
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
/// 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 {
|
||||
case data
|
||||
case errors
|
||||
case included
|
||||
case meta
|
||||
case links
|
||||
case jsonapi
|
||||
}
|
||||
private enum RootCodingKeys: String, CodingKey {
|
||||
case data
|
||||
case errors
|
||||
case included
|
||||
case meta
|
||||
case links
|
||||
case jsonapi
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: RootCodingKeys.self)
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: RootCodingKeys.self)
|
||||
|
||||
switch body {
|
||||
case .errors(let errors, meta: let meta, links: let links):
|
||||
var errContainer = container.nestedUnkeyedContainer(forKey: .errors)
|
||||
switch body {
|
||||
case .errors(let errors, meta: let meta, links: let links):
|
||||
var errContainer = container.nestedUnkeyedContainer(forKey: .errors)
|
||||
|
||||
for error in errors {
|
||||
try errContainer.encode(error)
|
||||
}
|
||||
for error in errors {
|
||||
try errContainer.encode(error)
|
||||
}
|
||||
|
||||
if MetaType.self != NoMetadata.self,
|
||||
let metaVal = meta {
|
||||
try container.encode(metaVal, forKey: .meta)
|
||||
}
|
||||
if MetaType.self != NoMetadata.self,
|
||||
let metaVal = meta {
|
||||
try container.encode(metaVal, forKey: .meta)
|
||||
}
|
||||
|
||||
if LinksType.self != NoLinks.self,
|
||||
let linksVal = links {
|
||||
try container.encode(linksVal, forKey: .links)
|
||||
}
|
||||
if LinksType.self != NoLinks.self,
|
||||
let linksVal = links {
|
||||
try container.encode(linksVal, forKey: .links)
|
||||
}
|
||||
|
||||
case .data(let data):
|
||||
try container.encode(data.primary, forKey: .data)
|
||||
case .data(let data):
|
||||
try container.encode(data.primary, forKey: .data)
|
||||
|
||||
if Include.self != NoIncludes.self {
|
||||
try container.encode(data.includes, forKey: .included)
|
||||
}
|
||||
if Include.self != NoIncludes.self {
|
||||
try container.encode(data.includes, forKey: .included)
|
||||
}
|
||||
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(data.meta, forKey: .meta)
|
||||
}
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(data.meta, forKey: .meta)
|
||||
}
|
||||
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(data.links, forKey: .links)
|
||||
}
|
||||
}
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(data.links, forKey: .links)
|
||||
}
|
||||
}
|
||||
|
||||
if APIDescription.self != NoAPIDescription.self {
|
||||
try container.encode(apiDescription, forKey: .jsonapi)
|
||||
}
|
||||
}
|
||||
if APIDescription.self != NoAPIDescription.self {
|
||||
try container.encode(apiDescription, forKey: .jsonapi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
|
||||
@@ -410,26 +410,26 @@ extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody:
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension Document: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Document(\(String(describing: body)))"
|
||||
}
|
||||
public var description: String {
|
||||
return "Document(\(String(describing: body)))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .errors(let errors, meta: let meta, links: let links):
|
||||
return "errors: \(String(describing: errors)), meta: \(String(describing: meta)), links: \(String(describing: links))"
|
||||
case .data(let data):
|
||||
return String(describing: data)
|
||||
}
|
||||
}
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .errors(let errors, meta: let meta, links: let links):
|
||||
return "errors: \(String(describing: errors)), meta: \(String(describing: meta)), links: \(String(describing: links))"
|
||||
case .data(let data):
|
||||
return String(describing: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body.Data: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "primary: \(String(describing: primary)), includes: \(String(describing: includes)), meta: \(String(describing: meta)), links: \(String(describing: links))"
|
||||
}
|
||||
public var description: String {
|
||||
return "primary: \(String(describing: primary)), includes: \(String(describing: includes)), meta: \(String(describing: meta)), links: \(String(describing: links))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error and Success Document Types
|
||||
|
||||
@@ -20,29 +20,29 @@ public typealias Include = EncodableJSONPoly
|
||||
///
|
||||
/// let includedThings = includes[Thing1.self]
|
||||
public struct Includes<I: Include>: Encodable, Equatable {
|
||||
public static var none: Includes { return .init(values: []) }
|
||||
|
||||
public let values: [I]
|
||||
|
||||
public init(values: [I]) {
|
||||
self.values = values
|
||||
}
|
||||
public static var none: Includes { return .init(values: []) }
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
public let values: [I]
|
||||
|
||||
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.")
|
||||
}
|
||||
public init(values: [I]) {
|
||||
self.values = values
|
||||
}
|
||||
|
||||
for value in values {
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
||||
|
||||
public var count: Int {
|
||||
return values.count
|
||||
}
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
for value in values {
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
||||
|
||||
public var count: Int {
|
||||
return values.count
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes: Decodable where I: Decodable {
|
||||
@@ -65,25 +65,25 @@ extension Includes: Decodable where I: Decodable {
|
||||
}
|
||||
|
||||
extension Includes {
|
||||
public func appending(_ other: Includes<I>) -> Includes {
|
||||
return Includes(values: values + other.values)
|
||||
}
|
||||
public func appending(_ other: Includes<I>) -> Includes {
|
||||
return Includes(values: values + other.values)
|
||||
}
|
||||
}
|
||||
|
||||
public func +<I: Include>(_ left: Includes<I>, _ right: Includes<I>) -> Includes<I> {
|
||||
return left.appending(right)
|
||||
return left.appending(right)
|
||||
}
|
||||
|
||||
extension Includes: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Includes(\(String(describing: values))"
|
||||
}
|
||||
public var description: String {
|
||||
return "Includes(\(String(describing: values))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I == NoIncludes {
|
||||
public init() {
|
||||
values = []
|
||||
}
|
||||
public init() {
|
||||
values = []
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 0 includes
|
||||
@@ -93,73 +93,73 @@ public typealias NoIncludes = Include0
|
||||
// MARK: - 1 include
|
||||
public typealias Include1 = Poly1
|
||||
extension Includes where I: _Poly1 {
|
||||
public subscript(_ lookup: I.A.Type) -> [I.A] {
|
||||
return values.compactMap { $0.a }
|
||||
}
|
||||
public subscript(_ lookup: I.A.Type) -> [I.A] {
|
||||
return values.compactMap { $0.a }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 2 includes
|
||||
public typealias Include2 = Poly2
|
||||
extension Includes where I: _Poly2 {
|
||||
public subscript(_ lookup: I.B.Type) -> [I.B] {
|
||||
return values.compactMap { $0.b }
|
||||
}
|
||||
public subscript(_ lookup: I.B.Type) -> [I.B] {
|
||||
return values.compactMap { $0.b }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 3 includes
|
||||
public typealias Include3 = Poly3
|
||||
extension Includes where I: _Poly3 {
|
||||
public subscript(_ lookup: I.C.Type) -> [I.C] {
|
||||
return values.compactMap { $0.c }
|
||||
}
|
||||
public subscript(_ lookup: I.C.Type) -> [I.C] {
|
||||
return values.compactMap { $0.c }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 4 includes
|
||||
public typealias Include4 = Poly4
|
||||
extension Includes where I: _Poly4 {
|
||||
public subscript(_ lookup: I.D.Type) -> [I.D] {
|
||||
return values.compactMap { $0.d }
|
||||
}
|
||||
public subscript(_ lookup: I.D.Type) -> [I.D] {
|
||||
return values.compactMap { $0.d }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 5 includes
|
||||
public typealias Include5 = Poly5
|
||||
extension Includes where I: _Poly5 {
|
||||
public subscript(_ lookup: I.E.Type) -> [I.E] {
|
||||
return values.compactMap { $0.e }
|
||||
}
|
||||
public subscript(_ lookup: I.E.Type) -> [I.E] {
|
||||
return values.compactMap { $0.e }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 6 includes
|
||||
public typealias Include6 = Poly6
|
||||
extension Includes where I: _Poly6 {
|
||||
public subscript(_ lookup: I.F.Type) -> [I.F] {
|
||||
return values.compactMap { $0.f }
|
||||
}
|
||||
public subscript(_ lookup: I.F.Type) -> [I.F] {
|
||||
return values.compactMap { $0.f }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 7 includes
|
||||
public typealias Include7 = Poly7
|
||||
extension Includes where I: _Poly7 {
|
||||
public subscript(_ lookup: I.G.Type) -> [I.G] {
|
||||
return values.compactMap { $0.g }
|
||||
}
|
||||
public subscript(_ lookup: I.G.Type) -> [I.G] {
|
||||
return values.compactMap { $0.g }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 8 includes
|
||||
public typealias Include8 = Poly8
|
||||
extension Includes where I: _Poly8 {
|
||||
public subscript(_ lookup: I.H.Type) -> [I.H] {
|
||||
return values.compactMap { $0.h }
|
||||
}
|
||||
public subscript(_ lookup: I.H.Type) -> [I.H] {
|
||||
return values.compactMap { $0.h }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 9 includes
|
||||
public typealias Include9 = Poly9
|
||||
extension Includes where I: _Poly9 {
|
||||
public subscript(_ lookup: I.I.Type) -> [I.I] {
|
||||
return values.compactMap { $0.i }
|
||||
}
|
||||
public subscript(_ lookup: I.I.Type) -> [I.I] {
|
||||
return values.compactMap { $0.i }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 10 includes
|
||||
|
||||
@@ -39,47 +39,47 @@ public protocol CodableResourceBody: Decodable, EncodableResourceBody {}
|
||||
/// A `ResourceBody` that has the ability to take on more primary
|
||||
/// resources by appending another similarly typed `ResourceBody`.
|
||||
public protocol ResourceBodyAppendable {
|
||||
func appending(_ other: Self) -> Self
|
||||
func appending(_ other: Self) -> Self
|
||||
}
|
||||
|
||||
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.
|
||||
/// If the `Entity` specialization is an `Optional` type, the body can contain
|
||||
/// 0 or 1 primary resources.
|
||||
public struct SingleResourceBody<Entity: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
||||
public let value: Entity
|
||||
public let value: Entity
|
||||
|
||||
public init(resourceObject: Entity) {
|
||||
self.value = resourceObject
|
||||
}
|
||||
public init(resourceObject: Entity) {
|
||||
self.value = resourceObject
|
||||
}
|
||||
}
|
||||
|
||||
/// A type allowing for a document body containing 0 or more primary resources.
|
||||
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, ResourceBodyAppendable {
|
||||
public let values: [Entity]
|
||||
public let values: [Entity]
|
||||
|
||||
public init(resourceObjects: [Entity]) {
|
||||
values = resourceObjects
|
||||
}
|
||||
public init(resourceObjects: [Entity]) {
|
||||
values = resourceObjects
|
||||
}
|
||||
|
||||
public func appending(_ other: ManyResourceBody) -> ManyResourceBody {
|
||||
return ManyResourceBody(resourceObjects: values + other.values)
|
||||
}
|
||||
public func appending(_ other: ManyResourceBody) -> ManyResourceBody {
|
||||
return ManyResourceBody(resourceObjects: values + other.values)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use NoResourceBody to indicate you expect a JSON API document to not
|
||||
/// contain a "data" top-level key.
|
||||
public struct NoResourceBody: CodableResourceBody {
|
||||
public static var none: NoResourceBody { return NoResourceBody() }
|
||||
public static var none: NoResourceBody { return NoResourceBody() }
|
||||
}
|
||||
|
||||
// MARK: Codable
|
||||
extension SingleResourceBody {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
let anyNil: Any? = nil
|
||||
let nilValue = anyNil as? Entity
|
||||
@@ -88,8 +88,8 @@ extension SingleResourceBody {
|
||||
return
|
||||
}
|
||||
|
||||
try container.encode(value)
|
||||
}
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody: Decodable, CodableResourceBody where Entity: OptionalCodablePrimaryResource {
|
||||
@@ -108,13 +108,13 @@ extension SingleResourceBody: Decodable, CodableResourceBody where Entity: Optio
|
||||
}
|
||||
|
||||
extension ManyResourceBody {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
|
||||
for value in values {
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
||||
for value in values {
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: Decodable, CodableResourceBody where Entity: CodablePrimaryResource {
|
||||
@@ -131,13 +131,13 @@ extension ManyResourceBody: Decodable, CodableResourceBody where Entity: Codable
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
extension SingleResourceBody: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "PrimaryResourceBody(\(String(describing: value)))"
|
||||
}
|
||||
public var description: String {
|
||||
return "PrimaryResourceBody(\(String(describing: value)))"
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "PrimaryResourceBody(\(String(describing: values)))"
|
||||
}
|
||||
public var description: String {
|
||||
return "PrimaryResourceBody(\(String(describing: values)))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
//
|
||||
|
||||
public enum JSONAPIEncodingError: Swift.Error {
|
||||
case typeMismatch(expected: String, found: String)
|
||||
case illegalEncoding(String)
|
||||
case illegalDecoding(String)
|
||||
case missingOrMalformedMetadata
|
||||
case missingOrMalformedLinks
|
||||
case typeMismatch(expected: String, found: String)
|
||||
case illegalEncoding(String)
|
||||
case illegalDecoding(String)
|
||||
case missingOrMalformedMetadata
|
||||
case missingOrMalformedLinks
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Equatable, ErrorDictType, CustomStringConvertible {
|
||||
/// a unique identifier for this particular occurrence of the problem
|
||||
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
|
||||
public let status: String?
|
||||
/// an application-specific error code
|
||||
@@ -20,7 +22,8 @@ public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Eq
|
||||
public let detail: String?
|
||||
/// an object containing references to the source of the error
|
||||
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,
|
||||
status: String? = nil,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
public protocol JSONAPIError: Swift.Error, Equatable, Codable {
|
||||
static var unknown: Self { get }
|
||||
static var unknown: Self { get }
|
||||
}
|
||||
|
||||
/// `UnknownJSONAPIError` can actually be used in any sitaution
|
||||
@@ -16,18 +16,18 @@ public protocol JSONAPIError: Swift.Error, Equatable, Codable {
|
||||
/// information the server might be providing in the error payload,
|
||||
/// use `BasicJSONAPIError` instead.
|
||||
public enum UnknownJSONAPIError: JSONAPIError {
|
||||
case unknownError
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
self = .unknown
|
||||
}
|
||||
case unknownError
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode("unknown")
|
||||
}
|
||||
|
||||
public static var unknown: Self {
|
||||
return .unknownError
|
||||
}
|
||||
public init(from decoder: Decoder) throws {
|
||||
self = .unknown
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode("unknown")
|
||||
}
|
||||
|
||||
public static var unknown: Self {
|
||||
return .unknownError
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,58 +10,58 @@ public protocol Links: Codable, Equatable {}
|
||||
|
||||
/// Use NoLinks where no links should belong to a JSON API component
|
||||
public struct NoLinks: Links, CustomStringConvertible {
|
||||
public static var none: NoLinks { return NoLinks() }
|
||||
public init() {}
|
||||
|
||||
public var description: String { return "No Links" }
|
||||
public static var none: NoLinks { return NoLinks() }
|
||||
public init() {}
|
||||
|
||||
public var description: String { return "No Links" }
|
||||
}
|
||||
|
||||
public protocol JSONAPIURL: Codable, Equatable {}
|
||||
|
||||
public struct Link<URL: JSONAPI.JSONAPIURL, Meta: JSONAPI.Meta>: Equatable, Codable {
|
||||
public let url: URL
|
||||
public let meta: Meta
|
||||
|
||||
public init(url: URL, meta: Meta) {
|
||||
self.url = url
|
||||
self.meta = meta
|
||||
}
|
||||
public let url: URL
|
||||
public let meta: Meta
|
||||
|
||||
public init(url: URL, meta: Meta) {
|
||||
self.url = url
|
||||
self.meta = meta
|
||||
}
|
||||
}
|
||||
|
||||
extension Link where Meta == NoMetadata {
|
||||
public init(url: URL) {
|
||||
self.init(url: url, meta: .none)
|
||||
}
|
||||
public init(url: URL) {
|
||||
self.init(url: url, meta: .none)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Link {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case href
|
||||
case meta
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
guard Meta.self == NoMetadata.self,
|
||||
let noMeta = NoMetadata() as? Meta else {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
meta = try container.decode(Meta.self, forKey: .meta)
|
||||
url = try container.decode(URL.self, forKey: .href)
|
||||
return
|
||||
}
|
||||
let container = try decoder.singleValueContainer()
|
||||
url = try container.decode(URL.self)
|
||||
meta = noMeta
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
guard Meta.self == NoMetadata.self else {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(url, forKey: .href)
|
||||
try container.encode(meta, forKey: .meta)
|
||||
return
|
||||
}
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(url)
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case href
|
||||
case meta
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
guard Meta.self == NoMetadata.self,
|
||||
let noMeta = NoMetadata() as? Meta else {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
meta = try container.decode(Meta.self, forKey: .meta)
|
||||
url = try container.decode(URL.self, forKey: .href)
|
||||
return
|
||||
}
|
||||
let container = try decoder.singleValueContainer()
|
||||
url = try container.decode(URL.self)
|
||||
meta = noMeta
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
guard Meta.self == NoMetadata.self else {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(url, forKey: .href)
|
||||
try container.encode(meta, forKey: .meta)
|
||||
return
|
||||
}
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ extension Optional: Meta where Wrapped: Meta {}
|
||||
/// Use this type when you want to specify not to encode or decode any metadata
|
||||
/// for a type.
|
||||
public struct NoMetadata: Meta, CustomStringConvertible {
|
||||
public static var none: NoMetadata { return NoMetadata() }
|
||||
public static var none: NoMetadata { return NoMetadata() }
|
||||
|
||||
public init() { }
|
||||
public init() { }
|
||||
|
||||
public var description: String { return "No Metadata" }
|
||||
public var description: String { return "No Metadata" }
|
||||
}
|
||||
|
||||
@@ -6,31 +6,31 @@
|
||||
//
|
||||
|
||||
public extension TransformedAttribute {
|
||||
/// Map an Attribute to a new wrapped type.
|
||||
/// Note that the resulting Attribute will have no transformer, even if the
|
||||
/// source Attribute has a transformer.
|
||||
/// You are mapping the output of the source transform into
|
||||
/// the RawValue of a new transformerless Attribute.
|
||||
///
|
||||
/// Generally, this is the most useful operation. The transformer gives you
|
||||
/// control over the decoding of the Attribute, but once the Attribute exists,
|
||||
/// mapping on it is most useful for creating computed Attribute properties.
|
||||
func map<T: Codable>(_ transform: (Transformer.To) throws -> T) rethrows -> Attribute<T> {
|
||||
return Attribute<T>(value: try transform(value))
|
||||
}
|
||||
/// Map an Attribute to a new wrapped type.
|
||||
/// Note that the resulting Attribute will have no transformer, even if the
|
||||
/// source Attribute has a transformer.
|
||||
/// You are mapping the output of the source transform into
|
||||
/// the RawValue of a new transformerless Attribute.
|
||||
///
|
||||
/// Generally, this is the most useful operation. The transformer gives you
|
||||
/// control over the decoding of the Attribute, but once the Attribute exists,
|
||||
/// mapping on it is most useful for creating computed Attribute properties.
|
||||
func map<T: Codable>(_ transform: (Transformer.To) throws -> T) rethrows -> Attribute<T> {
|
||||
return Attribute<T>(value: try transform(value))
|
||||
}
|
||||
}
|
||||
|
||||
public extension Attribute {
|
||||
/// Map an Attribute to a new wrapped type.
|
||||
/// Note that the resulting Attribute will have no transformer, even if the
|
||||
/// source Attribute has a transformer.
|
||||
/// You are mapping the output of the source transform into
|
||||
/// the RawValue of a new transformerless Attribute.
|
||||
///
|
||||
/// Generally, this is the most useful operation. The transformer gives you
|
||||
/// control over the decoding of the Attribute, but once the Attribute exists,
|
||||
/// mapping on it is most useful for creating computed Attribute properties.
|
||||
func map<T: Codable>(_ transform: (ValueType) throws -> T) rethrows -> Attribute<T> {
|
||||
return Attribute<T>(value: try transform(value))
|
||||
}
|
||||
/// Map an Attribute to a new wrapped type.
|
||||
/// Note that the resulting Attribute will have no transformer, even if the
|
||||
/// source Attribute has a transformer.
|
||||
/// You are mapping the output of the source transform into
|
||||
/// the RawValue of a new transformerless Attribute.
|
||||
///
|
||||
/// Generally, this is the most useful operation. The transformer gives you
|
||||
/// control over the decoding of the Attribute, but once the Attribute exists,
|
||||
/// mapping on it is most useful for creating computed Attribute properties.
|
||||
func map<T: Codable>(_ transform: (ValueType) throws -> T) rethrows -> Attribute<T> {
|
||||
return Attribute<T>(value: try transform(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,49 +6,49 @@
|
||||
//
|
||||
|
||||
public protocol AttributeType: Codable {
|
||||
associatedtype RawValue: Codable
|
||||
associatedtype ValueType
|
||||
associatedtype RawValue: Codable
|
||||
associatedtype ValueType
|
||||
|
||||
var value: ValueType { get }
|
||||
var value: ValueType { get }
|
||||
}
|
||||
|
||||
// MARK: TransformedAttribute
|
||||
|
||||
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
|
||||
public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: AttributeType where Transformer.From == RawValue {
|
||||
public let rawValue: RawValue
|
||||
public let rawValue: RawValue
|
||||
|
||||
public let value: Transformer.To
|
||||
public let value: Transformer.To
|
||||
|
||||
public init(rawValue: RawValue) throws {
|
||||
self.rawValue = rawValue
|
||||
value = try Transformer.transform(rawValue)
|
||||
}
|
||||
public init(rawValue: RawValue) throws {
|
||||
self.rawValue = rawValue
|
||||
value = try Transformer.transform(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute where Transformer == IdentityTransformer<RawValue> {
|
||||
// If we are using the identity transform, we can skip the transform and guarantee no
|
||||
// error is thrown.
|
||||
public init(value: RawValue) {
|
||||
rawValue = value
|
||||
self.value = value
|
||||
}
|
||||
// If we are using the identity transform, we can skip the transform and guarantee no
|
||||
// error is thrown.
|
||||
public init(value: RawValue) {
|
||||
rawValue = value
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute where Transformer: ReversibleTransformer {
|
||||
/// Initialize a TransformedAttribute from its transformed value. The
|
||||
/// RawValue, which is what gets encoded/decoded, is determined using
|
||||
/// The Transformer's reverse function.
|
||||
public init(transformedValue: Transformer.To) throws {
|
||||
self.value = transformedValue
|
||||
rawValue = try Transformer.reverse(value)
|
||||
}
|
||||
/// Initialize a TransformedAttribute from its transformed value. The
|
||||
/// RawValue, which is what gets encoded/decoded, is determined using
|
||||
/// The Transformer's reverse function.
|
||||
public init(transformedValue: Transformer.To) throws {
|
||||
self.value = transformedValue
|
||||
rawValue = try Transformer.reverse(value)
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Attribute<\(String(describing: Transformer.From.self)) -> \(String(describing: Transformer.To.self))>(\(String(describing: value)))"
|
||||
}
|
||||
public var description: String {
|
||||
return "Attribute<\(String(describing: Transformer.From.self)) -> \(String(describing: Transformer.To.self))>(\(String(describing: value)))"
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: Equatable where Transformer.From: Equatable, Transformer.To: Equatable {}
|
||||
@@ -63,85 +63,86 @@ public typealias ValidatedAttribute<RawValue: Codable, Validator: JSONAPI.Valida
|
||||
|
||||
/// An Attribute simply represents a type that can be encoded and decoded.
|
||||
public struct Attribute<RawValue: Codable>: AttributeType {
|
||||
let attribute: TransformedAttribute<RawValue, IdentityTransformer<RawValue>>
|
||||
let attribute: TransformedAttribute<RawValue, IdentityTransformer<RawValue>>
|
||||
|
||||
public var value: RawValue {
|
||||
return attribute.value
|
||||
}
|
||||
public var value: RawValue {
|
||||
return attribute.value
|
||||
}
|
||||
|
||||
public init(value: RawValue) {
|
||||
attribute = .init(value: value)
|
||||
}
|
||||
public init(value: RawValue) {
|
||||
attribute = .init(value: value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Attribute<\(String(describing: RawValue.self))>(\(String(describing: value)))"
|
||||
}
|
||||
public var description: String {
|
||||
return "Attribute<\(String(describing: RawValue.self))>(\(String(describing: value)))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: Equatable where RawValue: Equatable {}
|
||||
|
||||
// MARK: - Codable
|
||||
extension TransformedAttribute {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let rawVal: RawValue
|
||||
|
||||
// A little trickery follows. If the value is nil, the
|
||||
// container.decode(Value.self) will fail even if Value
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Value
|
||||
// type at which point we can store nil in `value`.
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? Transformer.From {
|
||||
rawVal = val
|
||||
} else {
|
||||
rawVal = try container.decode(Transformer.From.self)
|
||||
}
|
||||
|
||||
rawValue = rawVal
|
||||
value = try Transformer.transform(rawVal)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
let rawVal: RawValue
|
||||
|
||||
// A little trickery follows. If the value is nil, the
|
||||
// container.decode(Value.self) will fail even if Value
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Value
|
||||
// type at which point we can store nil in `value`.
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? Transformer.From {
|
||||
rawVal = val
|
||||
} else {
|
||||
rawVal = try container.decode(Transformer.From.self)
|
||||
}
|
||||
|
||||
rawValue = rawVal
|
||||
value = try Transformer.transform(rawVal)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
// A little trickery follows. If the value is nil, the
|
||||
// container.decode(Value.self) will fail even if Value
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Value
|
||||
// type at which point we can store nil in `value`.
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? RawValue {
|
||||
attribute = .init(value: val)
|
||||
} else {
|
||||
attribute = try container.decode(TransformedAttribute<RawValue, IdentityTransformer<RawValue>>.self)
|
||||
}
|
||||
}
|
||||
// A little trickery follows. If the value is nil, the
|
||||
// container.decode(Value.self) will fail even if Value
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Value
|
||||
// type at which point we can store nil in `value`.
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? RawValue {
|
||||
attribute = .init(value: val)
|
||||
} else {
|
||||
attribute = try container.decode(TransformedAttribute<RawValue, IdentityTransformer<RawValue>>.self)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(attribute)
|
||||
}
|
||||
try container.encode(attribute)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Attribute decoding and encoding defaults
|
||||
|
||||
extension AttributeType {
|
||||
public static func defaultDecoding<Container: KeyedDecodingContainerProtocol>(from container: Container, forKey key: Container.Key) throws -> Self {
|
||||
return try container.decode(Self.self, forKey: key)
|
||||
}
|
||||
public static func defaultDecoding<Container: KeyedDecodingContainerProtocol>(from container: Container,
|
||||
forKey key: Container.Key) throws -> Self {
|
||||
return try container.decode(Self.self, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public protocol RawIdType: MaybeRawId, Hashable {}
|
||||
/// Conformances for `String` and `UUID`
|
||||
/// are given in the README for this library.
|
||||
public protocol CreatableRawIdType: RawIdType {
|
||||
static func unique() -> Self
|
||||
static func unique() -> Self
|
||||
}
|
||||
|
||||
extension String: RawIdType {}
|
||||
@@ -32,80 +32,80 @@ extension String: RawIdType {}
|
||||
/// have an Id (most likely because it was created by a client and the server will be responsible
|
||||
/// for assigning it an Id).
|
||||
public struct Unidentified: MaybeRawId, CustomStringConvertible {
|
||||
public init() {}
|
||||
|
||||
public var description: String { return "Unidentified" }
|
||||
public init() {}
|
||||
|
||||
public var description: String { return "Unidentified" }
|
||||
}
|
||||
|
||||
public protocol OptionalId: Codable {
|
||||
associatedtype IdentifiableType: JSONAPI.JSONTyped
|
||||
associatedtype RawType: MaybeRawId
|
||||
associatedtype IdentifiableType: JSONAPI.JSONTyped
|
||||
associatedtype RawType: MaybeRawId
|
||||
|
||||
var rawValue: RawType { get }
|
||||
init(rawValue: RawType)
|
||||
var rawValue: RawType { get }
|
||||
init(rawValue: RawType)
|
||||
}
|
||||
|
||||
public protocol IdType: OptionalId, CustomStringConvertible, Hashable where RawType: RawIdType {}
|
||||
|
||||
extension Optional: MaybeRawId where Wrapped: Codable & Equatable {}
|
||||
extension Optional: OptionalId where Wrapped: IdType {
|
||||
public typealias IdentifiableType = Wrapped.IdentifiableType
|
||||
public typealias RawType = Wrapped.RawType?
|
||||
public typealias IdentifiableType = Wrapped.IdentifiableType
|
||||
public typealias RawType = Wrapped.RawType?
|
||||
|
||||
public var rawValue: Wrapped.RawType? {
|
||||
guard case .some(let value) = self else {
|
||||
return nil
|
||||
}
|
||||
return value.rawValue
|
||||
}
|
||||
public var rawValue: Wrapped.RawType? {
|
||||
guard case .some(let value) = self else {
|
||||
return nil
|
||||
}
|
||||
return value.rawValue
|
||||
}
|
||||
|
||||
public init(rawValue: Wrapped.RawType?) {
|
||||
self = rawValue.map { Wrapped(rawValue: $0) }
|
||||
}
|
||||
public init(rawValue: Wrapped.RawType?) {
|
||||
self = rawValue.map { Wrapped(rawValue: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
public extension IdType {
|
||||
var description: String { return "Id(\(String(describing: rawValue)))" }
|
||||
var description: String { return "Id(\(String(describing: rawValue)))" }
|
||||
}
|
||||
|
||||
public protocol CreatableIdType: IdType {
|
||||
init()
|
||||
init()
|
||||
}
|
||||
|
||||
/// An ResourceObject ID. These IDs can be encoded to or decoded from
|
||||
/// JSON API IDs.
|
||||
public struct Id<RawType: MaybeRawId, IdentifiableType: JSONAPI.JSONTyped>: Equatable, OptionalId {
|
||||
|
||||
public let rawValue: RawType
|
||||
|
||||
public init(rawValue: RawType) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
public let rawValue: RawType
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let rawValue = try container.decode(RawType.self)
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
||||
public init(rawValue: RawType) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let rawValue = try container.decode(RawType.self)
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {
|
||||
public static func id(from rawValue: RawType) -> Id<RawType, IdentifiableType> {
|
||||
return Id(rawValue: rawValue)
|
||||
}
|
||||
public static func id(from rawValue: RawType) -> Id<RawType, IdentifiableType> {
|
||||
return Id(rawValue: rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension Id: CreatableIdType where RawType: CreatableRawIdType {
|
||||
public init() {
|
||||
rawValue = .unique()
|
||||
}
|
||||
public init() {
|
||||
rawValue = .unique()
|
||||
}
|
||||
}
|
||||
|
||||
extension Id where RawType == Unidentified {
|
||||
public static var unidentified: Id { return .init(rawValue: Unidentified()) }
|
||||
public static var unidentified: Id { return .init(rawValue: Unidentified()) }
|
||||
}
|
||||
|
||||
@@ -21,66 +21,144 @@ public typealias EncodablePolyWrapped = Encodable & Equatable
|
||||
public typealias PolyWrapped = EncodablePolyWrapped & Decodable
|
||||
|
||||
extension Poly0: CodablePrimaryResource {
|
||||
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.")
|
||||
}
|
||||
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.")
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 1 type
|
||||
extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped {}
|
||||
extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly1: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped {}
|
||||
extension Poly1: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped {}
|
||||
|
||||
// MARK: - 2 types
|
||||
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
|
||||
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly2: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped {}
|
||||
extension Poly2: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
||||
extension Poly3: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
||||
extension Poly4: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
||||
extension Poly5: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
||||
extension Poly6: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
||||
extension Poly7: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
||||
extension Poly8: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource 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: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource 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: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped, J: PolyWrapped {}
|
||||
|
||||
// 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: CodablePrimaryResource, OptionalCodablePrimaryResource 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: PolyWrapped,
|
||||
B: PolyWrapped,
|
||||
C: PolyWrapped,
|
||||
D: PolyWrapped,
|
||||
E: PolyWrapped,
|
||||
F: PolyWrapped,
|
||||
G: PolyWrapped,
|
||||
H: PolyWrapped,
|
||||
I: PolyWrapped,
|
||||
J: PolyWrapped,
|
||||
K: PolyWrapped {}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
//
|
||||
|
||||
public protocol RelationshipType {
|
||||
associatedtype LinksType
|
||||
associatedtype MetaType
|
||||
associatedtype LinksType
|
||||
associatedtype MetaType
|
||||
|
||||
var links: LinksType { get }
|
||||
var meta: MetaType { get }
|
||||
var links: LinksType { get }
|
||||
var meta: MetaType { get }
|
||||
}
|
||||
|
||||
/// An ResourceObject relationship that can be encoded to or decoded from
|
||||
@@ -19,46 +19,46 @@ public protocol RelationshipType {
|
||||
/// A convenient typealias might make your code much more legible: `One<ResourceObjectDescription>`
|
||||
public struct ToOneRelationship<Identifiable: JSONAPI.Identifiable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
|
||||
|
||||
public let id: Identifiable.Identifier
|
||||
public let id: Identifiable.Identifier
|
||||
|
||||
public let meta: MetaType
|
||||
public let links: LinksType
|
||||
public let meta: MetaType
|
||||
public let links: LinksType
|
||||
|
||||
public init(id: Identifiable.Identifier, meta: MetaType, links: LinksType) {
|
||||
self.id = id
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
public init(id: Identifiable.Identifier, meta: MetaType, links: LinksType) {
|
||||
self.id = id
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public init(id: Identifiable.Identifier) {
|
||||
self.init(id: id, meta: .none, links: .none)
|
||||
}
|
||||
public init(id: Identifiable.Identifier) {
|
||||
self.init(id: id, meta: .none, links: .none)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship {
|
||||
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.Identifier {
|
||||
self.init(id: resourceObject.id, meta: meta, links: links)
|
||||
}
|
||||
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.Identifier {
|
||||
self.init(id: resourceObject.id, meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public init<T: ResourceObjectType>(resourceObject: T) where T.Id == Identifiable.Identifier {
|
||||
self.init(id: resourceObject.id, meta: .none, links: .none)
|
||||
}
|
||||
public init<T: ResourceObjectType>(resourceObject: T) where T.Id == Identifiable.Identifier {
|
||||
self.init(id: resourceObject.id, meta: .none, links: .none)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship where Identifiable: OptionalRelatable {
|
||||
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.Id == Identifiable.Wrapped.Identifier {
|
||||
self.init(id: resourceObject?.id, meta: meta, links: links)
|
||||
}
|
||||
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.Id == Identifiable.Wrapped.Identifier {
|
||||
self.init(id: resourceObject?.id, meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public init<T: ResourceObjectType>(resourceObject: T?) where T.Id == Identifiable.Wrapped.Identifier {
|
||||
self.init(id: resourceObject?.id, meta: .none, links: .none)
|
||||
}
|
||||
public init<T: ResourceObjectType>(resourceObject: T?) where T.Id == Identifiable.Wrapped.Identifier {
|
||||
self.init(id: resourceObject?.id, meta: .none, links: .none)
|
||||
}
|
||||
}
|
||||
|
||||
/// An ResourceObject relationship that can be encoded to or decoded from
|
||||
@@ -67,57 +67,57 @@ extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == N
|
||||
/// A convenient typealias might make your code much more legible: `Many<ResourceObjectDescription>`
|
||||
public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
|
||||
|
||||
public let ids: [Relatable.Identifier]
|
||||
public let ids: [Relatable.Identifier]
|
||||
|
||||
public let meta: MetaType
|
||||
public let links: LinksType
|
||||
public let meta: MetaType
|
||||
public let links: LinksType
|
||||
|
||||
public init(ids: [Relatable.Identifier], meta: MetaType, links: LinksType) {
|
||||
self.ids = ids
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
public init(ids: [Relatable.Identifier], meta: MetaType, links: LinksType) {
|
||||
self.ids = ids
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
|
||||
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.Identifier == Relatable.Identifier {
|
||||
ids = pointers.map { $0.id }
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.Identifier == Relatable.Identifier {
|
||||
ids = pointers.map { $0.id }
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
|
||||
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.Identifier {
|
||||
self.init(ids: resourceObjects.map { $0.id }, meta: meta, links: links)
|
||||
}
|
||||
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.Identifier {
|
||||
self.init(ids: resourceObjects.map { $0.id }, meta: meta, links: links)
|
||||
}
|
||||
|
||||
private init(meta: MetaType, links: LinksType) {
|
||||
self.init(ids: [], meta: meta, links: links)
|
||||
}
|
||||
private init(meta: MetaType, links: LinksType) {
|
||||
self.init(ids: [], meta: meta, links: links)
|
||||
}
|
||||
|
||||
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
|
||||
return ToManyRelationship(meta: meta, links: links)
|
||||
}
|
||||
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
|
||||
return ToManyRelationship(meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks {
|
||||
|
||||
public init(ids: [Relatable.Identifier]) {
|
||||
self.init(ids: ids, meta: .none, links: .none)
|
||||
}
|
||||
public init(ids: [Relatable.Identifier]) {
|
||||
self.init(ids: ids, meta: .none, links: .none)
|
||||
}
|
||||
|
||||
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.Identifier == Relatable.Identifier {
|
||||
self.init(pointers: pointers, meta: .none, links: .none)
|
||||
}
|
||||
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.Identifier == Relatable.Identifier {
|
||||
self.init(pointers: pointers, meta: .none, links: .none)
|
||||
}
|
||||
|
||||
public static var none: ToManyRelationship {
|
||||
return .none(withMeta: .none, links: .none)
|
||||
}
|
||||
public static var none: ToManyRelationship {
|
||||
return .none(withMeta: .none, links: .none)
|
||||
}
|
||||
|
||||
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.Id == Relatable.Identifier {
|
||||
self.init(resourceObjects: resourceObjects, meta: .none, links: .none)
|
||||
}
|
||||
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.Id == Relatable.Identifier {
|
||||
self.init(resourceObjects: resourceObjects, meta: .none, links: .none)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol Identifiable: JSONTyped {
|
||||
associatedtype Identifier: Equatable
|
||||
associatedtype Identifier: Equatable
|
||||
}
|
||||
|
||||
/// The Relatable protocol describes anything that
|
||||
@@ -128,152 +128,152 @@ public protocol Relatable: Identifiable where Identifier: JSONAPI.IdType {
|
||||
/// OptionalRelatable just describes an Optional
|
||||
/// with a Reltable Wrapped type.
|
||||
public protocol OptionalRelatable: Identifiable where Identifier == Wrapped.Identifier? {
|
||||
associatedtype Wrapped: JSONAPI.Relatable
|
||||
associatedtype Wrapped: JSONAPI.Relatable
|
||||
}
|
||||
|
||||
extension Optional: Identifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
|
||||
public typealias Identifier = Wrapped.Identifier?
|
||||
public typealias Identifier = Wrapped.Identifier?
|
||||
|
||||
public static var jsonType: String { return Wrapped.jsonType }
|
||||
public static var jsonType: String { return Wrapped.jsonType }
|
||||
}
|
||||
|
||||
// MARK: Codable
|
||||
private enum ResourceLinkageCodingKeys: String, CodingKey {
|
||||
case data = "data"
|
||||
case meta = "meta"
|
||||
case links = "links"
|
||||
case data = "data"
|
||||
case meta = "meta"
|
||||
case links = "links"
|
||||
}
|
||||
private enum ResourceIdentifierCodingKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
case entityType = "type"
|
||||
case id = "id"
|
||||
case entityType = "type"
|
||||
}
|
||||
|
||||
extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
|
||||
if let noMeta = NoMetadata() as? MetaType {
|
||||
meta = noMeta
|
||||
} else {
|
||||
meta = try container.decode(MetaType.self, forKey: .meta)
|
||||
}
|
||||
if let noMeta = NoMetadata() as? MetaType {
|
||||
meta = noMeta
|
||||
} else {
|
||||
meta = try container.decode(MetaType.self, forKey: .meta)
|
||||
}
|
||||
|
||||
if let noLinks = NoLinks() as? LinksType {
|
||||
links = noLinks
|
||||
} else {
|
||||
links = try container.decode(LinksType.self, forKey: .links)
|
||||
}
|
||||
if let noLinks = NoLinks() as? LinksType {
|
||||
links = noLinks
|
||||
} else {
|
||||
links = try container.decode(LinksType.self, forKey: .links)
|
||||
}
|
||||
|
||||
// A little trickery follows. If the id is nil, the
|
||||
// container.decode(Identifier.self) will fail even if Identifier
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Identifier
|
||||
// type at which point we can store nil in `id`.
|
||||
let anyNil: Any? = nil
|
||||
if try container.decodeNil(forKey: .data),
|
||||
let val = anyNil as? Identifiable.Identifier {
|
||||
id = val
|
||||
return
|
||||
}
|
||||
// A little trickery follows. If the id is nil, the
|
||||
// container.decode(Identifier.self) will fail even if Identifier
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Identifier
|
||||
// type at which point we can store nil in `id`.
|
||||
let anyNil: Any? = nil
|
||||
if try container.decodeNil(forKey: .data),
|
||||
let val = anyNil as? Identifiable.Identifier {
|
||||
id = val
|
||||
return
|
||||
}
|
||||
|
||||
let identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||
|
||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
let identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(links, forKey: .links)
|
||||
}
|
||||
guard type == Identifiable.jsonType else {
|
||||
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.jsonType, found: type)
|
||||
}
|
||||
|
||||
// If id is nil, instead of {id: , type: } we will just
|
||||
// encode `null`
|
||||
let anyNil: Any? = nil
|
||||
let nilId = anyNil as? Identifiable.Identifier
|
||||
guard id != nilId else {
|
||||
try container.encodeNil(forKey: .data)
|
||||
return
|
||||
}
|
||||
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
|
||||
}
|
||||
|
||||
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||
|
||||
try identifier.encode(id.rawValue, forKey: .id)
|
||||
try identifier.encode(Identifiable.jsonType, forKey: .entityType)
|
||||
}
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(links, forKey: .links)
|
||||
}
|
||||
|
||||
// If id is nil, instead of {id: , type: } we will just
|
||||
// encode `null`
|
||||
let anyNil: Any? = nil
|
||||
let nilId = anyNil as? Identifiable.Identifier
|
||||
guard id != nilId else {
|
||||
try container.encodeNil(forKey: .data)
|
||||
return
|
||||
}
|
||||
|
||||
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||
|
||||
try identifier.encode(id.rawValue, forKey: .id)
|
||||
try identifier.encode(Identifiable.jsonType, forKey: .entityType)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToManyRelationship: Codable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
|
||||
if let noMeta = NoMetadata() as? MetaType {
|
||||
meta = noMeta
|
||||
} else {
|
||||
meta = try container.decode(MetaType.self, forKey: .meta)
|
||||
}
|
||||
if let noMeta = NoMetadata() as? MetaType {
|
||||
meta = noMeta
|
||||
} else {
|
||||
meta = try container.decode(MetaType.self, forKey: .meta)
|
||||
}
|
||||
|
||||
if let noLinks = NoLinks() as? LinksType {
|
||||
links = noLinks
|
||||
} else {
|
||||
links = try container.decode(LinksType.self, forKey: .links)
|
||||
}
|
||||
if let noLinks = NoLinks() as? LinksType {
|
||||
links = noLinks
|
||||
} else {
|
||||
links = try container.decode(LinksType.self, forKey: .links)
|
||||
}
|
||||
|
||||
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
|
||||
|
||||
var newIds = [Relatable.Identifier]()
|
||||
while !identifiers.isAtEnd {
|
||||
let identifier = try identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
|
||||
|
||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||
|
||||
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)))
|
||||
}
|
||||
ids = newIds
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
|
||||
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
var newIds = [Relatable.Identifier]()
|
||||
while !identifiers.isAtEnd {
|
||||
let identifier = try identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
|
||||
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(links, forKey: .links)
|
||||
}
|
||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||
|
||||
var identifiers = container.nestedUnkeyedContainer(forKey: .data)
|
||||
|
||||
for id in ids {
|
||||
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
|
||||
|
||||
try identifier.encode(id.rawValue, forKey: .id)
|
||||
try identifier.encode(Relatable.jsonType, forKey: .entityType)
|
||||
}
|
||||
}
|
||||
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)))
|
||||
}
|
||||
ids = newIds
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(links, forKey: .links)
|
||||
}
|
||||
|
||||
var identifiers = container.nestedUnkeyedContainer(forKey: .data)
|
||||
|
||||
for id in ids {
|
||||
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
|
||||
|
||||
try identifier.encode(id.rawValue, forKey: .id)
|
||||
try identifier.encode(Relatable.jsonType, forKey: .entityType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CustomStringDescribable
|
||||
extension ToOneRelationship: CustomStringConvertible {
|
||||
public var description: String { return "Relationship(\(String(describing: id)))" }
|
||||
public var description: String { return "Relationship(\(String(describing: id)))" }
|
||||
}
|
||||
|
||||
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: ", "))])" }
|
||||
}
|
||||
|
||||
@@ -28,34 +28,34 @@ public protocol SparsableAttributes: Attributes {
|
||||
/// Can be used as `Relationships` Type for Entities that do not
|
||||
/// have any Relationships.
|
||||
public struct NoRelationships: Relationships {
|
||||
public static var none: NoRelationships { return .init() }
|
||||
public static var none: NoRelationships { return .init() }
|
||||
}
|
||||
|
||||
extension NoRelationships: CustomStringConvertible {
|
||||
public var description: String { return "No Relationships" }
|
||||
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() }
|
||||
public static var none: NoAttributes { return .init() }
|
||||
}
|
||||
|
||||
extension NoAttributes: CustomStringConvertible {
|
||||
public var description: String { return "No Attributes" }
|
||||
public var description: String { return "No Attributes" }
|
||||
}
|
||||
|
||||
/// Something that is JSONTyped provides a String representation
|
||||
/// of its type.
|
||||
public protocol JSONTyped {
|
||||
static var jsonType: String { get }
|
||||
static var jsonType: String { get }
|
||||
}
|
||||
|
||||
/// A `ResourceObjectProxyDescription` is an `ResourceObjectDescription`
|
||||
/// without Codable conformance.
|
||||
public protocol ResourceObjectProxyDescription: JSONTyped {
|
||||
associatedtype Attributes: Equatable
|
||||
associatedtype Relationships: Equatable
|
||||
associatedtype Attributes: Equatable
|
||||
associatedtype Relationships: Equatable
|
||||
}
|
||||
|
||||
/// A `ResourceObjectDescription` describes a JSON API
|
||||
@@ -70,38 +70,38 @@ public protocol ResourceObjectDescription: ResourceObjectProxyDescription where
|
||||
/// or decoded as ResourceObjects.
|
||||
@dynamicMemberLookup
|
||||
public protocol ResourceObjectProxy: Equatable, JSONTyped {
|
||||
associatedtype Description: ResourceObjectProxyDescription
|
||||
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
|
||||
associatedtype Description: ResourceObjectProxyDescription
|
||||
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
|
||||
|
||||
typealias Id = JSONAPI.Id<EntityRawIdType, Self>
|
||||
typealias Id = JSONAPI.Id<EntityRawIdType, Self>
|
||||
|
||||
typealias Attributes = Description.Attributes
|
||||
typealias Relationships = Description.Relationships
|
||||
typealias Attributes = Description.Attributes
|
||||
typealias Relationships = Description.Relationships
|
||||
|
||||
/// The `Entity`'s Id. This can be of type `Unidentified` if
|
||||
/// the entity is being created clientside and the
|
||||
/// server is being asked to create a unique Id. Otherwise,
|
||||
/// this should be of a type conforming to `IdType`.
|
||||
var id: Id { get }
|
||||
/// The `Entity`'s Id. This can be of type `Unidentified` if
|
||||
/// the entity is being created clientside and the
|
||||
/// server is being asked to create a unique Id. Otherwise,
|
||||
/// this should be of a type conforming to `IdType`.
|
||||
var id: Id { get }
|
||||
|
||||
/// The JSON API compliant attributes of this `Entity`.
|
||||
var attributes: Attributes { get }
|
||||
/// The JSON API compliant attributes of this `Entity`.
|
||||
var attributes: Attributes { get }
|
||||
|
||||
/// The JSON API compliant relationships of this `Entity`.
|
||||
var relationships: Relationships { get }
|
||||
/// The JSON API compliant relationships of this `Entity`.
|
||||
var relationships: Relationships { get }
|
||||
}
|
||||
|
||||
extension ResourceObjectProxy {
|
||||
/// The JSON API compliant "type" of this `ResourceObject`.
|
||||
public static var jsonType: String { return Description.jsonType }
|
||||
/// The JSON API compliant "type" of this `ResourceObject`.
|
||||
public static var jsonType: String { return Description.jsonType }
|
||||
}
|
||||
|
||||
/// ResourceObjectType is the protocol that ResourceObject conforms to. This
|
||||
/// protocol lets other types accept any ResourceObject as a generic
|
||||
/// specialization.
|
||||
public protocol ResourceObjectType: ResourceObjectProxy, CodablePrimaryResource where Description: ResourceObjectDescription {
|
||||
associatedtype Meta: JSONAPI.Meta
|
||||
associatedtype Links: JSONAPI.Links
|
||||
associatedtype Meta: JSONAPI.Meta
|
||||
associatedtype Links: JSONAPI.Links
|
||||
|
||||
/// Any additional metadata packaged with the entity.
|
||||
var meta: Meta { get }
|
||||
@@ -118,142 +118,142 @@ public protocol IdentifiableResourceObjectType: ResourceObjectType, Relatable wh
|
||||
/// See https://jsonapi.org/format/#document-resource-objects
|
||||
public struct ResourceObject<Description: JSONAPI.ResourceObjectDescription, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, EntityRawIdType: JSONAPI.MaybeRawId>: ResourceObjectType {
|
||||
|
||||
public typealias Meta = MetaType
|
||||
public typealias Links = LinksType
|
||||
public typealias Meta = MetaType
|
||||
public typealias Links = LinksType
|
||||
|
||||
/// The `ResourceObject`'s Id. This can be of type `Unidentified` if
|
||||
/// the entity is being created clientside and the
|
||||
/// server is being asked to create a unique Id. Otherwise,
|
||||
/// this should be of a type conforming to `IdType`.
|
||||
public let id: ResourceObject.Id
|
||||
|
||||
/// The JSON API compliant attributes of this `ResourceObject`.
|
||||
public let attributes: Description.Attributes
|
||||
|
||||
/// The JSON API compliant relationships of this `ResourceObject`.
|
||||
public let relationships: Description.Relationships
|
||||
/// The `ResourceObject`'s Id. This can be of type `Unidentified` if
|
||||
/// the entity is being created clientside and the
|
||||
/// server is being asked to create a unique Id. Otherwise,
|
||||
/// this should be of a type conforming to `IdType`.
|
||||
public let id: ResourceObject.Id
|
||||
|
||||
/// Any additional metadata packaged with the entity.
|
||||
public let meta: MetaType
|
||||
/// The JSON API compliant attributes of this `ResourceObject`.
|
||||
public let attributes: Description.Attributes
|
||||
|
||||
/// Links related to the entity.
|
||||
public let links: LinksType
|
||||
|
||||
public init(id: ResourceObject.Id, attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
|
||||
self.id = id
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
/// The JSON API compliant relationships of this `ResourceObject`.
|
||||
public let relationships: Description.Relationships
|
||||
|
||||
/// Any additional metadata packaged with the entity.
|
||||
public let meta: MetaType
|
||||
|
||||
/// Links related to the entity.
|
||||
public let links: LinksType
|
||||
|
||||
public init(id: ResourceObject.Id, attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
|
||||
self.id = id
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
}
|
||||
|
||||
extension ResourceObject: Identifiable, IdentifiableResourceObjectType, Relatable where EntityRawIdType: JSONAPI.RawIdType {
|
||||
public typealias Identifier = ResourceObject.Id
|
||||
public typealias Identifier = ResourceObject.Id
|
||||
}
|
||||
|
||||
extension ResourceObject: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "ResourceObject<\(ResourceObject.jsonType)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))"
|
||||
}
|
||||
public var description: String {
|
||||
return "ResourceObject<\(ResourceObject.jsonType)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience initializers
|
||||
extension ResourceObject where EntityRawIdType: CreatableRawIdType {
|
||||
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
|
||||
self.id = ResourceObject.Id()
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
|
||||
self.id = ResourceObject.Id()
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
}
|
||||
|
||||
extension ResourceObject where EntityRawIdType == Unidentified {
|
||||
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
|
||||
self.id = .unidentified
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
|
||||
self.id = .unidentified
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Pointer for Relationships use
|
||||
public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
|
||||
|
||||
/// A `ResourceObject.Pointer` is a `ToOneRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a `ResourceObject` so that
|
||||
/// other ResourceObjects' Relationships can be built up from it.
|
||||
typealias Pointer = ToOneRelationship<ResourceObject, NoMetadata, NoLinks>
|
||||
/// A `ResourceObject.Pointer` is a `ToOneRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a `ResourceObject` so that
|
||||
/// other ResourceObjects' Relationships can be built up from it.
|
||||
typealias Pointer = ToOneRelationship<ResourceObject, NoMetadata, NoLinks>
|
||||
|
||||
/// `ResourceObject.Pointers` is a `ToManyRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a bunch of ResourceObjects so
|
||||
/// that other ResourceObjects' Relationships can be built up from them.
|
||||
typealias Pointers = ToManyRelationship<ResourceObject, NoMetadata, NoLinks>
|
||||
/// `ResourceObject.Pointers` is a `ToManyRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a bunch of ResourceObjects so
|
||||
/// that other ResourceObjects' Relationships can be built up from them.
|
||||
typealias Pointers = ToManyRelationship<ResourceObject, NoMetadata, NoLinks>
|
||||
|
||||
/// Get a pointer to this resource object that can be used as a
|
||||
/// relationship to another resource object.
|
||||
var pointer: Pointer {
|
||||
return Pointer(resourceObject: self)
|
||||
}
|
||||
/// Get a pointer to this resource object that can be used as a
|
||||
/// relationship to another resource object.
|
||||
var pointer: Pointer {
|
||||
return Pointer(resourceObject: self)
|
||||
}
|
||||
|
||||
/// Get a pointer (i.e. `ToOneRelationship`) to this resource
|
||||
/// object with the given metadata and links attached.
|
||||
func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<ResourceObject, MType, LType> {
|
||||
return ToOneRelationship(resourceObject: self, meta: meta, links: links)
|
||||
}
|
||||
func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<ResourceObject, MType, LType> {
|
||||
return ToOneRelationship(resourceObject: self, meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Identifying Unidentified Entities
|
||||
public extension ResourceObject where EntityRawIdType == Unidentified {
|
||||
/// Create a new `ResourceObject` from this one with a newly created
|
||||
/// unique Id of the given type.
|
||||
func identified<RawIdType: CreatableRawIdType>(byType: RawIdType.Type) -> ResourceObject<Description, MetaType, LinksType, RawIdType> {
|
||||
return .init(attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
/// Create a new `ResourceObject` from this one with a newly created
|
||||
/// unique Id of the given type.
|
||||
func identified<RawIdType: CreatableRawIdType>(byType: RawIdType.Type) -> ResourceObject<Description, MetaType, LinksType, RawIdType> {
|
||||
return .init(attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
|
||||
/// Create a new `ResourceObject` from this one with the given Id.
|
||||
func identified<RawIdType: JSONAPI.RawIdType>(by id: RawIdType) -> ResourceObject<Description, MetaType, LinksType, RawIdType> {
|
||||
return .init(id: ResourceObject<Description, MetaType, LinksType, RawIdType>.Identifier(rawValue: id), attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
/// Create a new `ResourceObject` from this one with the given Id.
|
||||
func identified<RawIdType: JSONAPI.RawIdType>(by id: RawIdType) -> ResourceObject<Description, MetaType, LinksType, RawIdType> {
|
||||
return .init(id: ResourceObject<Description, MetaType, LinksType, RawIdType>.Identifier(rawValue: id), attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
public extension ResourceObject where EntityRawIdType: CreatableRawIdType {
|
||||
/// Create a copy of this `ResourceObject` with a new unique Id.
|
||||
func withNewIdentifier() -> ResourceObject {
|
||||
return ResourceObject(attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
/// Create a copy of this `ResourceObject` with a new unique Id.
|
||||
func withNewIdentifier() -> ResourceObject {
|
||||
return ResourceObject(attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Attribute Access
|
||||
public extension ResourceObjectProxy {
|
||||
// MARK: Keypath Subscript Lookup
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[\.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName.value`.
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[\.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName.value`.
|
||||
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
|
||||
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T>) -> T.ValueType {
|
||||
return attributes[keyPath: path].value
|
||||
}
|
||||
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T>) -> T.ValueType {
|
||||
return attributes[keyPath: path].value
|
||||
}
|
||||
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[\.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName.value`.
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[\.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName.value`.
|
||||
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
|
||||
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T?>) -> T.ValueType? {
|
||||
return attributes[keyPath: path]?.value
|
||||
}
|
||||
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T?>) -> T.ValueType? {
|
||||
return attributes[keyPath: path]?.value
|
||||
}
|
||||
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[\.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName.value`.
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[\.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName.value`.
|
||||
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
|
||||
subscript<T: AttributeType, U>(_ path: KeyPath<Description.Attributes, T?>) -> U? where T.ValueType == U? {
|
||||
// Implementation Note: Handles Transform that returns optional
|
||||
// type.
|
||||
return attributes[keyPath: path].flatMap { $0.value }
|
||||
}
|
||||
subscript<T: AttributeType, U>(_ path: KeyPath<Description.Attributes, T?>) -> U? where T.ValueType == U? {
|
||||
// Implementation Note: Handles Transform that returns optional
|
||||
// type.
|
||||
return attributes[keyPath: path].flatMap { $0.value }
|
||||
}
|
||||
|
||||
// MARK: Dynaminc Member Keypath Lookup
|
||||
/// Access the attribute at the given keypath. This just
|
||||
@@ -278,28 +278,28 @@ public extension ResourceObjectProxy {
|
||||
}
|
||||
|
||||
// MARK: Direct Keypath Subscript Lookup
|
||||
/// Access the storage of the attribute at the given keypath. This just
|
||||
/// Access the storage of the attribute at the given keypath. This just
|
||||
/// allows you to write `resourceObject[direct: \.propertyName]` instead
|
||||
/// of `resourceObject.attributes.propertyName`.
|
||||
/// of `resourceObject.attributes.propertyName`.
|
||||
/// Most of the subscripts dig into an `AttributeType`. This subscript
|
||||
/// returns the `AttributeType` (or another type, if you are accessing
|
||||
/// an attribute that is not stored in an `AttributeType`).
|
||||
subscript<T>(direct path: KeyPath<Description.Attributes, T>) -> T {
|
||||
// Implementation Note: Handles attributes that are not
|
||||
// AttributeType. These should only exist as computed properties.
|
||||
return attributes[keyPath: path]
|
||||
}
|
||||
subscript<T>(direct path: KeyPath<Description.Attributes, T>) -> T {
|
||||
// Implementation Note: Handles attributes that are not
|
||||
// AttributeType. These should only exist as computed properties.
|
||||
return attributes[keyPath: path]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Meta-Attribute Access
|
||||
public extension ResourceObjectProxy {
|
||||
// MARK: Keypath Subscript Lookup
|
||||
/// Access an attribute requiring a transformation on the RawValue _and_
|
||||
/// a secondary transformation on this entity (self).
|
||||
/// Access an attribute requiring a transformation on the RawValue _and_
|
||||
/// a secondary transformation on this entity (self).
|
||||
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
|
||||
subscript<T>(_ path: KeyPath<Description.Attributes, (Self) -> T>) -> T {
|
||||
return attributes[keyPath: path](self)
|
||||
}
|
||||
subscript<T>(_ path: KeyPath<Description.Attributes, (Self) -> T>) -> T {
|
||||
return attributes[keyPath: path](self)
|
||||
}
|
||||
|
||||
// MARK: Dynamic Member Keypath Lookup
|
||||
/// Access an attribute requiring a transformation on the RawValue _and_
|
||||
@@ -311,61 +311,61 @@ public extension ResourceObjectProxy {
|
||||
|
||||
// MARK: - Relationship Access
|
||||
public extension ResourceObjectProxy {
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other.id`.
|
||||
static func ~><OtherEntity: Identifiable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.Identifier {
|
||||
return entity.relationships[keyPath: path].id
|
||||
}
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other.id`.
|
||||
static func ~><OtherEntity: Identifiable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.Identifier {
|
||||
return entity.relationships[keyPath: path].id
|
||||
}
|
||||
|
||||
/// Access to an Id of an optional `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other?.id`.
|
||||
static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier {
|
||||
// Implementation Note: This signature applies to `ToOneRelationship<E?, _, _>?`
|
||||
// whereas the one below applies to `ToOneRelationship<E, _, _>?`
|
||||
return entity.relationships[keyPath: path]?.id
|
||||
}
|
||||
/// Access to an Id of an optional `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other?.id`.
|
||||
static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier {
|
||||
// Implementation Note: This signature applies to `ToOneRelationship<E?, _, _>?`
|
||||
// whereas the one below applies to `ToOneRelationship<E, _, _>?`
|
||||
return entity.relationships[keyPath: path]?.id
|
||||
}
|
||||
|
||||
/// Access to an Id of an optional `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other?.id`.
|
||||
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier? {
|
||||
// Implementation Note: This signature applies to `ToOneRelationship<E, _, _>?`
|
||||
// whereas the one above applies to `ToOneRelationship<E?, _, _>?`
|
||||
return entity.relationships[keyPath: path]?.id
|
||||
}
|
||||
/// Access to an Id of an optional `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other?.id`.
|
||||
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier? {
|
||||
// Implementation Note: This signature applies to `ToOneRelationship<E, _, _>?`
|
||||
// whereas the one above applies to `ToOneRelationship<E?, _, _>?`
|
||||
return entity.relationships[keyPath: path]?.id
|
||||
}
|
||||
|
||||
/// Access to all Ids of a `ToManyRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.others` instead
|
||||
/// of `resourceObject.relationships.others.ids`.
|
||||
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>>) -> [OtherEntity.Identifier] {
|
||||
return entity.relationships[keyPath: path].ids
|
||||
}
|
||||
/// Access to all Ids of a `ToManyRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.others` instead
|
||||
/// of `resourceObject.relationships.others.ids`.
|
||||
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>>) -> [OtherEntity.Identifier] {
|
||||
return entity.relationships[keyPath: path].ids
|
||||
}
|
||||
|
||||
/// Access to all Ids of an optional `ToManyRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.others` instead
|
||||
/// of `resourceObject.relationships.others?.ids`.
|
||||
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>?>) -> [OtherEntity.Identifier]? {
|
||||
return entity.relationships[keyPath: path]?.ids
|
||||
}
|
||||
/// Access to all Ids of an optional `ToManyRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.others` instead
|
||||
/// of `resourceObject.relationships.others?.ids`.
|
||||
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>?>) -> [OtherEntity.Identifier]? {
|
||||
return entity.relationships[keyPath: path]?.ids
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Meta-Relationship Access
|
||||
public extension ResourceObjectProxy {
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other.id`.
|
||||
static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> Identifier>) -> Identifier {
|
||||
return entity.relationships[keyPath: path](entity)
|
||||
}
|
||||
/// Access to an Id of a `ToOneRelationship`.
|
||||
/// This allows you to write `resourceObject ~> \.other` instead
|
||||
/// of `resourceObject.relationships.other.id`.
|
||||
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 `resourceObject ~> \.others` instead
|
||||
/// of `resourceObject.relationships.others.ids`.
|
||||
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 `resourceObject ~> \.others` instead
|
||||
/// of `resourceObject.relationships.others.ids`.
|
||||
static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> [Identifier]>) -> [Identifier] {
|
||||
return entity.relationships[keyPath: path](entity)
|
||||
}
|
||||
}
|
||||
|
||||
infix operator ~>
|
||||
|
||||
@@ -2,81 +2,81 @@
|
||||
import JSONAPI
|
||||
|
||||
extension Attribute: ExpressibleByUnicodeScalarLiteral where RawValue: ExpressibleByUnicodeScalarLiteral {
|
||||
public typealias UnicodeScalarLiteralType = RawValue.UnicodeScalarLiteralType
|
||||
public typealias UnicodeScalarLiteralType = RawValue.UnicodeScalarLiteralType
|
||||
|
||||
public init(unicodeScalarLiteral value: RawValue.UnicodeScalarLiteralType) {
|
||||
self.init(value: RawValue(unicodeScalarLiteral: value))
|
||||
}
|
||||
public init(unicodeScalarLiteral value: RawValue.UnicodeScalarLiteralType) {
|
||||
self.init(value: RawValue(unicodeScalarLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByExtendedGraphemeClusterLiteral where RawValue: ExpressibleByExtendedGraphemeClusterLiteral {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = RawValue.ExtendedGraphemeClusterLiteralType
|
||||
public typealias ExtendedGraphemeClusterLiteralType = RawValue.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: RawValue.ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(value: RawValue(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
public init(extendedGraphemeClusterLiteral value: RawValue.ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(value: RawValue(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByStringLiteral where RawValue: ExpressibleByStringLiteral {
|
||||
public typealias StringLiteralType = RawValue.StringLiteralType
|
||||
public typealias StringLiteralType = RawValue.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: RawValue.StringLiteralType) {
|
||||
self.init(value: RawValue(stringLiteral: value))
|
||||
}
|
||||
public init(stringLiteral value: RawValue.StringLiteralType) {
|
||||
self.init(value: RawValue(stringLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByNilLiteral where RawValue: ExpressibleByNilLiteral {
|
||||
public init(nilLiteral: ()) {
|
||||
self.init(value: RawValue(nilLiteral: ()))
|
||||
}
|
||||
public init(nilLiteral: ()) {
|
||||
self.init(value: RawValue(nilLiteral: ()))
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByFloatLiteral where RawValue: ExpressibleByFloatLiteral {
|
||||
public typealias FloatLiteralType = RawValue.FloatLiteralType
|
||||
public typealias FloatLiteralType = RawValue.FloatLiteralType
|
||||
|
||||
public init(floatLiteral value: RawValue.FloatLiteralType) {
|
||||
self.init(value: RawValue(floatLiteral: value))
|
||||
}
|
||||
public init(floatLiteral value: RawValue.FloatLiteralType) {
|
||||
self.init(value: RawValue(floatLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: ExpressibleByFloatLiteral where Wrapped: ExpressibleByFloatLiteral {
|
||||
public typealias FloatLiteralType = Wrapped.FloatLiteralType
|
||||
public typealias FloatLiteralType = Wrapped.FloatLiteralType
|
||||
|
||||
public init(floatLiteral value: FloatLiteralType) {
|
||||
self = .some(Wrapped(floatLiteral: value))
|
||||
}
|
||||
public init(floatLiteral value: FloatLiteralType) {
|
||||
self = .some(Wrapped(floatLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByBooleanLiteral where RawValue: ExpressibleByBooleanLiteral {
|
||||
public typealias BooleanLiteralType = RawValue.BooleanLiteralType
|
||||
public typealias BooleanLiteralType = RawValue.BooleanLiteralType
|
||||
|
||||
public init(booleanLiteral value: BooleanLiteralType) {
|
||||
self.init(value: RawValue(booleanLiteral: value))
|
||||
}
|
||||
public init(booleanLiteral value: BooleanLiteralType) {
|
||||
self.init(value: RawValue(booleanLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: ExpressibleByBooleanLiteral where Wrapped: ExpressibleByBooleanLiteral {
|
||||
public typealias BooleanLiteralType = Wrapped.BooleanLiteralType
|
||||
public typealias BooleanLiteralType = Wrapped.BooleanLiteralType
|
||||
|
||||
public init(booleanLiteral value: BooleanLiteralType) {
|
||||
self = .some(Wrapped(booleanLiteral: value))
|
||||
}
|
||||
public init(booleanLiteral value: BooleanLiteralType) {
|
||||
self = .some(Wrapped(booleanLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: ExpressibleByIntegerLiteral where RawValue: ExpressibleByIntegerLiteral {
|
||||
public typealias IntegerLiteralType = RawValue.IntegerLiteralType
|
||||
public typealias IntegerLiteralType = RawValue.IntegerLiteralType
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self.init(value: RawValue(integerLiteral: value))
|
||||
}
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self.init(value: RawValue(integerLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: ExpressibleByIntegerLiteral where Wrapped: ExpressibleByIntegerLiteral {
|
||||
public typealias IntegerLiteralType = Wrapped.IntegerLiteralType
|
||||
public typealias IntegerLiteralType = Wrapped.IntegerLiteralType
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .some(Wrapped(integerLiteral: value))
|
||||
}
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .some(Wrapped(integerLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
// regretably, array and dictionary literals are not so easy because Dictionaries and Arrays
|
||||
@@ -84,55 +84,55 @@ extension Optional: ExpressibleByIntegerLiteral where Wrapped: ExpressibleByInte
|
||||
|
||||
// we can still provide a case for the Array and Dictionary types, though.
|
||||
public protocol DictionaryType {
|
||||
associatedtype Key: Hashable
|
||||
associatedtype Value
|
||||
associatedtype Key: Hashable
|
||||
associatedtype Value
|
||||
|
||||
init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value)
|
||||
init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value)
|
||||
}
|
||||
extension Dictionary: DictionaryType {}
|
||||
|
||||
extension Attribute: ExpressibleByDictionaryLiteral where RawValue: DictionaryType {
|
||||
public typealias Key = RawValue.Key
|
||||
public typealias Key = RawValue.Key
|
||||
|
||||
public typealias Value = RawValue.Value
|
||||
public typealias Value = RawValue.Value
|
||||
|
||||
public init(dictionaryLiteral elements: (RawValue.Key, RawValue.Value)...) {
|
||||
public init(dictionaryLiteral elements: (RawValue.Key, RawValue.Value)...) {
|
||||
|
||||
// we arbitrarily keep the first value if two values are assigned to the same key
|
||||
self.init(value: RawValue(elements, uniquingKeysWith: { val, _ in val }))
|
||||
}
|
||||
// we arbitrarily keep the first value if two values are assigned to the same key
|
||||
self.init(value: RawValue(elements, uniquingKeysWith: { val, _ in val }))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: DictionaryType where Wrapped: DictionaryType {
|
||||
public typealias Key = Wrapped.Key
|
||||
public typealias Key = Wrapped.Key
|
||||
|
||||
public typealias Value = Wrapped.Value
|
||||
public typealias Value = Wrapped.Value
|
||||
|
||||
public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value) {
|
||||
self = try .some(Wrapped(keysAndValues, uniquingKeysWith: combine))
|
||||
}
|
||||
public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value) {
|
||||
self = try .some(Wrapped(keysAndValues, uniquingKeysWith: combine))
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ArrayType {
|
||||
associatedtype Element
|
||||
associatedtype Element
|
||||
|
||||
init<S>(_ s: S) where Element == S.Element, S : Sequence
|
||||
init<S>(_ s: S) where Element == S.Element, S : Sequence
|
||||
}
|
||||
extension Array: ArrayType {}
|
||||
extension ArraySlice: ArrayType {}
|
||||
|
||||
extension Attribute: ExpressibleByArrayLiteral where RawValue: ArrayType {
|
||||
public typealias ArrayLiteralElement = RawValue.Element
|
||||
public typealias ArrayLiteralElement = RawValue.Element
|
||||
|
||||
public init(arrayLiteral elements: ArrayLiteralElement...) {
|
||||
self.init(value: RawValue(elements))
|
||||
}
|
||||
public init(arrayLiteral elements: ArrayLiteralElement...) {
|
||||
self.init(value: RawValue(elements))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: ArrayType where Wrapped: ArrayType {
|
||||
public typealias Element = Wrapped.Element
|
||||
public typealias Element = Wrapped.Element
|
||||
|
||||
public init<S>(_ s: S) where Element == S.Element, S : Sequence {
|
||||
self = .some(Wrapped(s))
|
||||
}
|
||||
public init<S>(_ s: S) where Element == S.Element, S : Sequence {
|
||||
self = .some(Wrapped(s))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,33 +8,33 @@
|
||||
import JSONAPI
|
||||
|
||||
extension Id: ExpressibleByUnicodeScalarLiteral where RawType: ExpressibleByUnicodeScalarLiteral {
|
||||
public typealias UnicodeScalarLiteralType = RawType.UnicodeScalarLiteralType
|
||||
|
||||
public init(unicodeScalarLiteral value: RawType.UnicodeScalarLiteralType) {
|
||||
self.init(rawValue: RawType(unicodeScalarLiteral: value))
|
||||
}
|
||||
public typealias UnicodeScalarLiteralType = RawType.UnicodeScalarLiteralType
|
||||
|
||||
public init(unicodeScalarLiteral value: RawType.UnicodeScalarLiteralType) {
|
||||
self.init(rawValue: RawType(unicodeScalarLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Id: ExpressibleByExtendedGraphemeClusterLiteral where RawType: ExpressibleByExtendedGraphemeClusterLiteral {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = RawType.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: RawType.ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(rawValue: RawType(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
public typealias ExtendedGraphemeClusterLiteralType = RawType.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: RawType.ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(rawValue: RawType(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Id: ExpressibleByStringLiteral where RawType: ExpressibleByStringLiteral {
|
||||
public typealias StringLiteralType = RawType.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: RawType.StringLiteralType) {
|
||||
self.init(rawValue: RawType(stringLiteral: value))
|
||||
}
|
||||
public typealias StringLiteralType = RawType.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: RawType.StringLiteralType) {
|
||||
self.init(rawValue: RawType(stringLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Id: ExpressibleByIntegerLiteral where RawType: ExpressibleByIntegerLiteral {
|
||||
public typealias IntegerLiteralType = RawType.IntegerLiteralType
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self.init(rawValue: RawType(integerLiteral: value))
|
||||
}
|
||||
public typealias IntegerLiteralType = RawType.IntegerLiteralType
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self.init(rawValue: RawType(integerLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
//
|
||||
|
||||
extension Optional: ExpressibleByUnicodeScalarLiteral where Wrapped: ExpressibleByUnicodeScalarLiteral {
|
||||
public typealias UnicodeScalarLiteralType = Wrapped.UnicodeScalarLiteralType
|
||||
public typealias UnicodeScalarLiteralType = Wrapped.UnicodeScalarLiteralType
|
||||
|
||||
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
|
||||
self = .some(Wrapped(unicodeScalarLiteral: value))
|
||||
}
|
||||
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
|
||||
self = .some(Wrapped(unicodeScalarLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: ExpressibleByExtendedGraphemeClusterLiteral where Wrapped: ExpressibleByExtendedGraphemeClusterLiteral {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = Wrapped.ExtendedGraphemeClusterLiteralType
|
||||
public typealias ExtendedGraphemeClusterLiteralType = Wrapped.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
|
||||
self = .some(Wrapped(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
|
||||
self = .some(Wrapped(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: ExpressibleByStringLiteral where Wrapped: ExpressibleByStringLiteral {
|
||||
public typealias StringLiteralType = Wrapped.StringLiteralType
|
||||
public typealias StringLiteralType = Wrapped.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .some(Wrapped(stringLiteral: value))
|
||||
}
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .some(Wrapped(stringLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,40 +8,40 @@
|
||||
import JSONAPI
|
||||
|
||||
extension ToOneRelationship: ExpressibleByNilLiteral where Identifiable.Identifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public init(nilLiteral: ()) {
|
||||
public init(nilLiteral: ()) {
|
||||
|
||||
self.init(id: Identifiable.Identifier(nilLiteral: ()))
|
||||
}
|
||||
self.init(id: Identifiable.Identifier(nilLiteral: ()))
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Identifiable.Identifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public typealias UnicodeScalarLiteralType = Identifiable.Identifier.UnicodeScalarLiteralType
|
||||
public typealias UnicodeScalarLiteralType = Identifiable.Identifier.UnicodeScalarLiteralType
|
||||
|
||||
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
|
||||
self.init(id: Identifiable.Identifier(unicodeScalarLiteral: value))
|
||||
}
|
||||
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
|
||||
self.init(id: Identifiable.Identifier(unicodeScalarLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Identifiable.Identifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = Identifiable.Identifier.ExtendedGraphemeClusterLiteralType
|
||||
public typealias ExtendedGraphemeClusterLiteralType = Identifiable.Identifier.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(id: Identifiable.Identifier(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(id: Identifiable.Identifier(extendedGraphemeClusterLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension ToOneRelationship: ExpressibleByStringLiteral where Identifiable.Identifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public typealias StringLiteralType = Identifiable.Identifier.StringLiteralType
|
||||
public typealias StringLiteralType = Identifiable.Identifier.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self.init(id: Identifiable.Identifier(stringLiteral: value))
|
||||
}
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self.init(id: Identifiable.Identifier(stringLiteral: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension ToManyRelationship: ExpressibleByArrayLiteral where MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public typealias ArrayLiteralElement = Relatable.Identifier
|
||||
public typealias ArrayLiteralElement = Relatable.Identifier
|
||||
|
||||
public init(arrayLiteral elements: ArrayLiteralElement...) {
|
||||
self.init(ids: elements)
|
||||
}
|
||||
public init(arrayLiteral elements: ArrayLiteralElement...) {
|
||||
self.init(ids: elements)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,29 +8,29 @@
|
||||
import JSONAPI
|
||||
|
||||
public enum ResourceObjectCheckError: Swift.Error {
|
||||
/// The attributes should live in a struct, not
|
||||
/// another type class.
|
||||
case attributesNotStruct
|
||||
/// The attributes should live in a struct, not
|
||||
/// another type class.
|
||||
case attributesNotStruct
|
||||
|
||||
/// The relationships should live in a struct, not
|
||||
/// another type class.
|
||||
case relationshipsNotStruct
|
||||
/// The relationships should live in a struct, not
|
||||
/// another type class.
|
||||
case relationshipsNotStruct
|
||||
|
||||
/// All stored properties on an Attributes struct should
|
||||
/// be one of the supplied Attribute types.
|
||||
case nonAttribute(named: String)
|
||||
/// All stored properties on an Attributes struct should
|
||||
/// be one of the supplied Attribute types.
|
||||
case nonAttribute(named: String)
|
||||
|
||||
/// All stored properties on a Relationships struct should
|
||||
/// be one of the supplied Relationship types.
|
||||
case nonRelationship(named: String)
|
||||
/// All stored properties on a Relationships struct should
|
||||
/// be one of the supplied Relationship types.
|
||||
case nonRelationship(named: String)
|
||||
|
||||
/// It is explicitly stated by the JSON spec
|
||||
/// a "none" value for arrays is an empty array, not `nil`.
|
||||
case nullArray(named: String)
|
||||
/// It is explicitly stated by the JSON spec
|
||||
/// a "none" value for arrays is an empty array, not `nil`.
|
||||
case nullArray(named: String)
|
||||
}
|
||||
|
||||
public struct ResourceObjectCheckErrors: Swift.Error {
|
||||
let problems: [ResourceObjectCheckError]
|
||||
let problems: [ResourceObjectCheckError]
|
||||
}
|
||||
|
||||
private protocol OptionalAttributeType {}
|
||||
@@ -55,40 +55,40 @@ extension TransformedAttribute: _AttributeType {}
|
||||
extension Attribute: _AttributeType {}
|
||||
|
||||
public extension ResourceObject {
|
||||
static func check(_ entity: ResourceObject) throws {
|
||||
var problems = [ResourceObjectCheckError]()
|
||||
static func check(_ entity: ResourceObject) throws {
|
||||
var problems = [ResourceObjectCheckError]()
|
||||
|
||||
let attributesMirror = Mirror(reflecting: entity.attributes)
|
||||
let attributesMirror = Mirror(reflecting: entity.attributes)
|
||||
|
||||
if attributesMirror.displayStyle != .`struct` {
|
||||
problems.append(.attributesNotStruct)
|
||||
}
|
||||
if attributesMirror.displayStyle != .`struct` {
|
||||
problems.append(.attributesNotStruct)
|
||||
}
|
||||
|
||||
for attribute in attributesMirror.children {
|
||||
if attribute.value as? _AttributeType == nil,
|
||||
attribute.value as? OptionalAttributeType == nil {
|
||||
problems.append(.nonAttribute(named: attribute.label ?? "unnamed"))
|
||||
}
|
||||
if attribute.value as? AttributeTypeWithOptionalArray != nil {
|
||||
problems.append(.nullArray(named: attribute.label ?? "unnamed"))
|
||||
}
|
||||
}
|
||||
for attribute in attributesMirror.children {
|
||||
if attribute.value as? _AttributeType == nil,
|
||||
attribute.value as? OptionalAttributeType == nil {
|
||||
problems.append(.nonAttribute(named: attribute.label ?? "unnamed"))
|
||||
}
|
||||
if attribute.value as? AttributeTypeWithOptionalArray != nil {
|
||||
problems.append(.nullArray(named: attribute.label ?? "unnamed"))
|
||||
}
|
||||
}
|
||||
|
||||
let relationshipsMirror = Mirror(reflecting: entity.relationships)
|
||||
let relationshipsMirror = Mirror(reflecting: entity.relationships)
|
||||
|
||||
if relationshipsMirror.displayStyle != .`struct` {
|
||||
problems.append(.relationshipsNotStruct)
|
||||
}
|
||||
if relationshipsMirror.displayStyle != .`struct` {
|
||||
problems.append(.relationshipsNotStruct)
|
||||
}
|
||||
|
||||
for relationship in relationshipsMirror.children {
|
||||
if relationship.value as? _RelationshipType == nil,
|
||||
relationship.value as? OptionalRelationshipType == nil {
|
||||
problems.append(.nonRelationship(named: relationship.label ?? "unnamed"))
|
||||
}
|
||||
}
|
||||
for relationship in relationshipsMirror.children {
|
||||
if relationship.value as? _RelationshipType == nil,
|
||||
relationship.value as? OptionalRelationshipType == nil {
|
||||
problems.append(.nonRelationship(named: relationship.label ?? "unnamed"))
|
||||
}
|
||||
}
|
||||
|
||||
guard problems.count == 0 else {
|
||||
throw ResourceObjectCheckErrors(problems: problems)
|
||||
}
|
||||
}
|
||||
guard problems.count == 0 else {
|
||||
throw ResourceObjectCheckErrors(problems: problems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user