mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Initial commit of files taken from a package started as part of another project.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// AnyEntity.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 11/5/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
//private class AnyRelationshipBase<Entity: ElevatedCore.Entity>: Relationship {
|
||||
//
|
||||
// init() {
|
||||
// guard type(of: self) != AnyRelationshipBase.self else {
|
||||
// fatalError("Must use subclasses")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var ids: [Id<Entity>] {
|
||||
// fatalError("Implemented by subclasses")
|
||||
// }
|
||||
//
|
||||
// static func == (lhs: AnyRelationshipBase<Entity>, rhs: AnyRelationshipBase<Entity>) -> Bool {
|
||||
// fatalError("Implemented by subclasses")
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private final class AnyRelationshipBox<Concrete: Relationship>: AnyRelationshipBase<Concrete.Entity> {
|
||||
// let concrete: Concrete
|
||||
//
|
||||
// init(_ concrete: Concrete) {
|
||||
// self.concrete = concrete
|
||||
// super.init()
|
||||
// }
|
||||
//
|
||||
// override func encode(to encoder: Encoder) throws {
|
||||
// try concrete.encode(to: encoder)
|
||||
// }
|
||||
//
|
||||
// override var ids: [Id<Concrete.Entity>] {
|
||||
// return concrete.ids
|
||||
// }
|
||||
//
|
||||
// static func == (lhs: AnyRelationshipBox<Concrete>, rhs: AnyRelationshipBox<Concrete>) -> Bool {
|
||||
// return lhs.concrete == rhs.concrete
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public final class AnyRelationship<Entity: ElevatedCore.Entity>: Relationship {
|
||||
// private let box: AnyRelationshipBase<Entity>
|
||||
//
|
||||
// public init<Concrete: Relationship>(_ concrete: Concrete) where Concrete.Entity == Entity {
|
||||
// box = AnyRelationshipBox(concrete)
|
||||
// }
|
||||
//
|
||||
// public func encode(to encoder: Encoder) throws {
|
||||
// try box.encode(to: encoder)
|
||||
// }
|
||||
//
|
||||
// public var ids: [Id<Entity>] {
|
||||
// return box.ids
|
||||
// }
|
||||
//
|
||||
// public static func == (lhs: AnyRelationship<Entity>, rhs: AnyRelationship<Entity>) -> Bool {
|
||||
// return lhs.box == rhs.box
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private class AnyEntityBase<Attributes: Equatable & Encodable, Relationships: Equatable & Encodable, ID: IdType>: Entity {
|
||||
//
|
||||
// init() {
|
||||
// guard Swift.type(of: self) != AnyEntityBase.self else {
|
||||
// fatalError("Must use subclasses")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class var type: String { fatalError("Implemented by subclasses") }
|
||||
// var id: Id<AnyEntity> { fatalError("Implemented by subclasses") }
|
||||
//
|
||||
// var attributes: Attributes { fatalError("Implemented by subclasses") }
|
||||
// var relationships: Relationships { fatalError("Implemented by subclasses") }
|
||||
//
|
||||
// static func == (lhs: AnyEntityBase<Attributes, Relationships, ID>, rhs: AnyEntityBase<Attributes, Relationships, ID>) -> Bool {
|
||||
// fatalError("Implemented by subclasses")
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private final class AnyEntityBox<Concrete: Entity>: AnyEntityBase<Concrete.Attributes, Concrete.Relationships, Concrete.ID> {
|
||||
//
|
||||
// let concrete: Concrete
|
||||
//
|
||||
// init(_ concrete: Concrete) {
|
||||
// self.concrete = concrete
|
||||
// super.init()
|
||||
// }
|
||||
//
|
||||
// override class var type: String {
|
||||
// return Concrete.type
|
||||
// }
|
||||
//
|
||||
// override var id: Id<AnyEntity> {
|
||||
// return concrete.id
|
||||
// }
|
||||
//
|
||||
// override var attributes: Concrete.Attributes {
|
||||
// return concrete.attributes
|
||||
// }
|
||||
//
|
||||
// override var relationships: Concrete.Relationships {
|
||||
// return concrete.relationships
|
||||
// }
|
||||
//
|
||||
// static func == (lhs: AnyEntityBox<Concrete>, rhs: AnyEntityBox<Concrete>) -> Bool {
|
||||
// return lhs.concrete == rhs.concrete
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public final class AnyEntity<Attributes: Equatable & Encodable, Relationships: Equatable & Encodable, ID: IdType>: Entity {
|
||||
// private let box: AnyEntityBase<Attributes, Relationships, ID>
|
||||
//
|
||||
// init<Concrete: Entity>(_ concrete: Concrete) where Concrete.Attributes == Attributes, Concrete.Relationships == Relationships, Concrete.ID == ID {
|
||||
// box = AnyEntityBox(concrete)
|
||||
// }
|
||||
//
|
||||
// public class var type: String { return Swift.type(of: box).type }
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// JSONAPIDocument.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 11/5/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A JSON API Document represents the entire body
|
||||
/// of a JSON API request or the entire body of
|
||||
/// a JSON API response.
|
||||
/// Note that this type uses Camel case. If your
|
||||
/// API uses snake case, you will want to use
|
||||
/// a conversion such as the one offerred by the
|
||||
/// Foundation JSONEncoder/Decoder: `KeyDecodingStrategy`
|
||||
public struct JSONAPIDocument<Body: ResourceBody, Include: IncludeDecoder, Error: JSONAPIError & Decodable> {
|
||||
public let body: Data
|
||||
// public let meta: Meta?
|
||||
// public let jsonApi: APIDescription?
|
||||
// public let links: Links?
|
||||
|
||||
public enum Data {
|
||||
case errors([Error])
|
||||
case data(Body, included: Includes<Include>)
|
||||
|
||||
public var isError: Bool {
|
||||
guard case .errors = self else { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
public var data: (Body, included: Includes<Include>)? {
|
||||
guard case let .data(body, included: includes) = self else { return nil }
|
||||
return (body, included: includes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONAPIDocument: Decodable {
|
||||
private enum RootCodingKeys: String, CodingKey {
|
||||
case data
|
||||
case errors
|
||||
case included
|
||||
case meta
|
||||
case links
|
||||
case jsonapi
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: RootCodingKeys.self)
|
||||
|
||||
let maybeData = try container.decodeIfPresent(Body.self, forKey: .data)
|
||||
let maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
|
||||
let errors = try container.decodeIfPresent([Error].self, forKey: .errors)
|
||||
|
||||
assert(!(maybeData != nil && errors != nil), "JSON API Spec dictates data and errors will not both be present.")
|
||||
assert((maybeIncludes == nil || maybeData != nil), "JSON API Spec dictates that includes will not be present if data is not present.")
|
||||
|
||||
// TODO come back to this and make robust
|
||||
|
||||
if let errors = errors {
|
||||
body = .errors(errors)
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = maybeData else {
|
||||
body = .errors([.unknown]) // TODO: this should be more descriptive
|
||||
return
|
||||
}
|
||||
|
||||
body = .data(data, included: maybeIncludes ?? Includes<Include>.none)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// JSONAPI_Error.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol JSONAPIError: Swift.Error {
|
||||
static var unknown: Self { get }
|
||||
}
|
||||
|
||||
// TODO: remove temp error stuff below
|
||||
public enum TmpError: JSONAPIError & Decodable {
|
||||
case unknownError
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
self = .unknown
|
||||
}
|
||||
|
||||
public static var unknown: TmpError {
|
||||
return .unknownError
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
//
|
||||
// JSONAPI_Includes.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Result
|
||||
|
||||
public protocol IncludeDecoder: Decodable {}
|
||||
|
||||
public struct Includes<I: IncludeDecoder>: Decodable {
|
||||
public static var none: Includes { return .init(values: []) }
|
||||
|
||||
let values: [I]
|
||||
|
||||
private init(values: [I]) {
|
||||
self.values = values
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
|
||||
var valueAggregator = [I]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(I.self))
|
||||
}
|
||||
|
||||
values = valueAggregator
|
||||
}
|
||||
|
||||
public var count: Int {
|
||||
return values.count
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decoding
|
||||
|
||||
func decode<EntityType: JSONAPI.EntityType>(_ type: EntityType.Type, from container: SingleValueDecodingContainer) throws -> Result<Entity<EntityType>, EncodingError> {
|
||||
let ret: Result<Entity<EntityType>, EncodingError>
|
||||
do {
|
||||
ret = try .success(container.decode(Entity<EntityType>.self))
|
||||
} catch (let err as EncodingError) {
|
||||
ret = .failure(err)
|
||||
} catch (let err) {
|
||||
ret = .failure(EncodingError.invalidValue(EntityType.self,
|
||||
.init(codingPath: container.codingPath,
|
||||
debugDescription: err.localizedDescription,
|
||||
underlyingError: err)))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// MARK: - 0 includes
|
||||
|
||||
public protocol _Include0: IncludeDecoder { }
|
||||
public struct Include0: _Include0 {
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
}
|
||||
}
|
||||
public typealias NoIncludes = Include0
|
||||
|
||||
// MARK: - 1 include
|
||||
public protocol _Include1: _Include0 {
|
||||
associatedtype A: EntityType
|
||||
var a: Entity<A>? { get }
|
||||
}
|
||||
public enum Include1<A: EntityType>: _Include1 {
|
||||
case a(Entity<A>)
|
||||
|
||||
public var a: Entity<A>? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
self = .a(try container.decode(Entity<A>.self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I: _Include1 {
|
||||
public subscript(_ lookup: I.A.Type) -> [Entity<I.A>] {
|
||||
return values.compactMap { $0.a }
|
||||
}
|
||||
|
||||
public subscript(_ lookup: Entity<I.A>.Type) -> [Entity<I.A>] {
|
||||
return values.compactMap { $0.a}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 2 includes
|
||||
public protocol _Include2: _Include1 {
|
||||
associatedtype B: EntityType
|
||||
var b: Entity<B>? { get }
|
||||
}
|
||||
public enum Include2<A: EntityType, B: EntityType>: _Include2 {
|
||||
case a(Entity<A>)
|
||||
case b(Entity<B>)
|
||||
|
||||
public var a: Entity<A>? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var b: Entity<B>? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Include2.a($0) },
|
||||
try decode(B.self, from: container).map { Include2.b($0) } ]
|
||||
|
||||
let maybeVal: Include2<A, B>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Include2<A, B>.self, .init(codingPath: decoder.codingPath, debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I: _Include2 {
|
||||
public subscript(_ lookup: I.B.Type) -> [Entity<I.B>] {
|
||||
return values.compactMap { $0.b }
|
||||
}
|
||||
|
||||
public subscript(_ lookup: Entity<I.B>.Type) -> [Entity<I.B>] {
|
||||
return values.compactMap { $0.b}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 3 includes
|
||||
public protocol _Include3: _Include2 {
|
||||
associatedtype C: EntityType
|
||||
var c: Entity<C>? { get }
|
||||
}
|
||||
public enum Include3<A: EntityType, B: EntityType, C: EntityType>: _Include3 {
|
||||
case a(Entity<A>)
|
||||
case b(Entity<B>)
|
||||
case c(Entity<C>)
|
||||
|
||||
public var a: Entity<A>? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var b: Entity<B>? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var c: Entity<C>? {
|
||||
guard case let .c(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Include3.a($0) },
|
||||
try decode(B.self, from: container).map { Include3.b($0) },
|
||||
try decode(C.self, from: container).map { Include3.c($0) }]
|
||||
|
||||
let maybeVal: Include3<A, B, C>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Include3<A, B, C>.self, .init(codingPath: decoder.codingPath, debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I: _Include3 {
|
||||
public subscript(_ lookup: I.C.Type) -> [Entity<I.C>] {
|
||||
return values.compactMap { $0.c }
|
||||
}
|
||||
|
||||
public subscript(_ lookup: Entity<I.C>.Type) -> [Entity<I.C>] {
|
||||
return values.compactMap { $0.c}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 4 includes
|
||||
public protocol _Include4: _Include3 {
|
||||
associatedtype D: EntityType
|
||||
var d: Entity<D>? { get }
|
||||
}
|
||||
public enum Include4<A: EntityType, B: EntityType, C: EntityType, D: EntityType>: _Include4 {
|
||||
case a(Entity<A>)
|
||||
case b(Entity<B>)
|
||||
case c(Entity<C>)
|
||||
case d(Entity<D>)
|
||||
|
||||
public var a: Entity<A>? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var b: Entity<B>? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var c: Entity<C>? {
|
||||
guard case let .c(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var d: Entity<D>? {
|
||||
guard case let .d(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Include4.a($0) },
|
||||
try decode(B.self, from: container).map { Include4.b($0) },
|
||||
try decode(C.self, from: container).map { Include4.c($0) },
|
||||
try decode(D.self, from: container).map { Include4.d($0) }]
|
||||
|
||||
let maybeVal: Include4<A, B, C, D>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Include4<A, B, C, D>.self, .init(codingPath: decoder.codingPath, debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I: _Include4 {
|
||||
public subscript(_ lookup: I.D.Type) -> [Entity<I.D>] {
|
||||
return values.compactMap { $0.d }
|
||||
}
|
||||
|
||||
public subscript(_ lookup: Entity<I.D>.Type) -> [Entity<I.D>] {
|
||||
return values.compactMap { $0.d}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 5 includes
|
||||
public protocol _Include5: _Include4 {
|
||||
associatedtype E: EntityType
|
||||
var e: Entity<E>? { get }
|
||||
}
|
||||
public enum Include5<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType>: _Include5 {
|
||||
case a(Entity<A>)
|
||||
case b(Entity<B>)
|
||||
case c(Entity<C>)
|
||||
case d(Entity<D>)
|
||||
case e(Entity<E>)
|
||||
|
||||
public var a: Entity<A>? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var b: Entity<B>? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var c: Entity<C>? {
|
||||
guard case let .c(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var d: Entity<D>? {
|
||||
guard case let .d(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var e: Entity<E>? {
|
||||
guard case let .e(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Include5.a($0) },
|
||||
try decode(B.self, from: container).map { Include5.b($0) },
|
||||
try decode(C.self, from: container).map { Include5.c($0) },
|
||||
try decode(D.self, from: container).map { Include5.d($0) },
|
||||
try decode(E.self, from: container).map { Include5.e($0) }]
|
||||
|
||||
let maybeVal: Include5<A, B, C, D, E>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Include5<A, B, C, D, E>.self, .init(codingPath: decoder.codingPath, debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I: _Include5 {
|
||||
public subscript(_ lookup: I.E.Type) -> [Entity<I.E>] {
|
||||
return values.compactMap { $0.e }
|
||||
}
|
||||
|
||||
public subscript(_ lookup: Entity<I.E>.Type) -> [Entity<I.E>] {
|
||||
return values.compactMap { $0.e}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 6 includes
|
||||
public protocol _Include6: _Include5 {
|
||||
associatedtype F: EntityType
|
||||
var f: Entity<F>? { get }
|
||||
}
|
||||
public enum Include6<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType>: _Include6 {
|
||||
case a(Entity<A>)
|
||||
case b(Entity<B>)
|
||||
case c(Entity<C>)
|
||||
case d(Entity<D>)
|
||||
case e(Entity<E>)
|
||||
case f(Entity<F>)
|
||||
|
||||
public var a: Entity<A>? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var b: Entity<B>? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var c: Entity<C>? {
|
||||
guard case let .c(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var d: Entity<D>? {
|
||||
guard case let .d(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var e: Entity<E>? {
|
||||
guard case let .e(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public var f: Entity<F>? {
|
||||
guard case let .f(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Include6.a($0) },
|
||||
try decode(B.self, from: container).map { Include6.b($0) },
|
||||
try decode(C.self, from: container).map { Include6.c($0) },
|
||||
try decode(D.self, from: container).map { Include6.d($0) },
|
||||
try decode(E.self, from: container).map { Include6.e($0) },
|
||||
try decode(F.self, from: container).map { Include6.f($0) }]
|
||||
|
||||
let maybeVal: Include6<A, B, C, D, E, F>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Include6<A, B, C, D, E, F>.self, .init(codingPath: decoder.codingPath, debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes where I: _Include6 {
|
||||
public subscript(_ lookup: I.F.Type) -> [Entity<I.F>] {
|
||||
return values.compactMap { $0.f }
|
||||
}
|
||||
|
||||
public subscript(_ lookup: Entity<I.F>.Type) -> [Entity<I.F>] {
|
||||
return values.compactMap { $0.f}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// JSONAPI_ResourceBody.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ResourceBody: Decodable {
|
||||
typealias Single<EntityType: JSONAPI.EntityType> = SingleResourceBody<EntityType>
|
||||
typealias Many<EntityType: JSONAPI.EntityType> = ManyResourceBody<EntityType>
|
||||
}
|
||||
|
||||
public struct SingleResourceBody<EntityType: JSONAPI.EntityType>: ResourceBody {
|
||||
public let value: Entity<EntityType>?
|
||||
}
|
||||
|
||||
public struct ManyResourceBody<EntityType: JSONAPI.EntityType>: ResourceBody {
|
||||
public let values: [Entity<EntityType>]
|
||||
}
|
||||
|
||||
// MARK: Decodable
|
||||
extension SingleResourceBody {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
value = try container.decode(Entity<EntityType>.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody {
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
var valueAggregator = [Entity<EntityType>]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(Entity<EntityType>.self))
|
||||
}
|
||||
values = valueAggregator
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
struct JSONAPI {
|
||||
var text = "Hello, World!"
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Entity.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 7/24/18.
|
||||
//
|
||||
|
||||
public typealias Relatives = Codable & Equatable
|
||||
|
||||
public typealias Attributes = Codable & Equatable
|
||||
|
||||
/// Can be used as Relationships Type for Entities that do not
|
||||
/// have any Relationships.
|
||||
public struct NoRelatives: Relatives {}
|
||||
|
||||
/// Can be used as Attributes Type for Entities that do not
|
||||
/// have any Attributes.
|
||||
public struct NoAttributes: Attributes {}
|
||||
|
||||
public protocol EntityType {
|
||||
associatedtype Identifier: JSONAPI.Identifier
|
||||
associatedtype AttributeType: Attributes
|
||||
associatedtype RelatedType: Relatives
|
||||
|
||||
static var type: String { get }
|
||||
}
|
||||
|
||||
public protocol IdentifiedEntityType: EntityType where Identifier: IdType {}
|
||||
|
||||
/// An Entity is a single model type that can be
|
||||
/// encoded to or decoded from a JSON API
|
||||
/// "Resource Object."
|
||||
/// See https://jsonapi.org/format/#document-resource-objects
|
||||
/// Easiest to use with `protocol MyEntity: Entity, Identified, Related, Attributed where ID = UUID`.
|
||||
public struct Entity<EntityType: JSONAPI.EntityType>: Codable, Equatable {
|
||||
public static var type: String { return EntityType.type }
|
||||
|
||||
public let id: EntityType.Identifier
|
||||
public let attributes: EntityType.AttributeType
|
||||
|
||||
public let relationships: EntityType.RelatedType
|
||||
|
||||
public init(id: EntityType.Identifier, attributes: EntityType.AttributeType, relationships: EntityType.RelatedType) {
|
||||
self.id = id
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
}
|
||||
|
||||
public init(attributes: EntityType.AttributeType, relationships: EntityType.RelatedType) {
|
||||
self.id = .init()
|
||||
self.attributes = attributes
|
||||
self.relationships = relationships
|
||||
}
|
||||
}
|
||||
|
||||
extension Entity where EntityType.AttributeType == NoAttributes {
|
||||
public init(id: EntityType.Identifier, relationships: EntityType.RelatedType) {
|
||||
self.init(id: id, attributes: NoAttributes(), relationships: relationships)
|
||||
}
|
||||
|
||||
public init(relationships: EntityType.RelatedType) {
|
||||
self.init(attributes: NoAttributes(), relationships: relationships)
|
||||
}
|
||||
}
|
||||
|
||||
extension Entity where EntityType.RelatedType == NoRelatives {
|
||||
public init(id: EntityType.Identifier, attributes: EntityType.AttributeType) {
|
||||
self.init(id: id, attributes: attributes, relationships: NoRelatives())
|
||||
}
|
||||
|
||||
public init(attributes: EntityType.AttributeType) {
|
||||
self.init(attributes: attributes, relationships: NoRelatives())
|
||||
}
|
||||
}
|
||||
|
||||
extension Entity where EntityType.AttributeType == NoAttributes, EntityType.RelatedType == NoRelatives {
|
||||
public init(id: EntityType.Identifier) {
|
||||
self.init(id: id, attributes: NoAttributes(), relationships: NoRelatives())
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.init(attributes: NoAttributes(), relationships: NoRelatives())
|
||||
}
|
||||
}
|
||||
|
||||
//public protocol IdentifiedEntityType: JSONAPI.EntityType where IdentifiedEntityType.Identifier: IdType, Identifier.Entity == Self {}
|
||||
|
||||
public extension Entity where EntityType.Identifier: IdType {
|
||||
/// Get a pointer to this entity that can be used as a
|
||||
/// relationship to another entity.
|
||||
public var pointer: ToOneRelationship<EntityType> {
|
||||
return ToOneRelationship(entity: self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Attribute Access
|
||||
public extension Entity {
|
||||
subscript<T>(_ path: KeyPath<EntityType.AttributeType, T>) -> T {
|
||||
return attributes[keyPath: path]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Relationship Access
|
||||
public extension Entity {
|
||||
public static func ~><OtherEntityType: JSONAPI.EntityType>(entity: Entity<EntityType>, path: KeyPath<EntityType.RelatedType, ToOneRelationship<OtherEntityType>>) -> OtherEntityType.Identifier {
|
||||
return entity.relationships[keyPath: path].id
|
||||
}
|
||||
|
||||
public static func ~><OtherEntityType: JSONAPI.EntityType>(entity: Entity<EntityType>, path: KeyPath<EntityType.RelatedType, ToManyRelationship<OtherEntityType>>) -> [OtherEntityType.Identifier] {
|
||||
return entity.relationships[keyPath: path].ids
|
||||
}
|
||||
}
|
||||
|
||||
infix operator ~>
|
||||
|
||||
// MARK: - Codable
|
||||
private enum ResourceObjectCodingKeys: String, CodingKey {
|
||||
case type = "type"
|
||||
case id = "id"
|
||||
case attributes = "attributes"
|
||||
case relationships = "relationships"
|
||||
}
|
||||
|
||||
public extension Entity {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceObjectCodingKeys.self)
|
||||
|
||||
try container.encode(Entity.type, forKey: .type)
|
||||
|
||||
if EntityType.Identifier.self != Unidentified.self {
|
||||
try container.encode(id, forKey: .id)
|
||||
}
|
||||
|
||||
if EntityType.AttributeType.self != NoAttributes.self {
|
||||
try container.encode(attributes, forKey: .attributes)
|
||||
}
|
||||
|
||||
if EntityType.RelatedType.self != NoRelatives.self {
|
||||
try container.encode(relationships, forKey: .relationships)
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
||||
let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self)
|
||||
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
|
||||
guard Entity.type == type else {
|
||||
throw JSONAPIEncodingError.typeMismatch(expected: EntityType.type, found: type)
|
||||
}
|
||||
|
||||
id = try (Unidentified() as? EntityType.Identifier) ?? container.decode(EntityType.Identifier.self, forKey: .id)
|
||||
|
||||
attributes = try (NoAttributes() as? EntityType.AttributeType) ?? container.decode(EntityType.AttributeType.self, forKey: .attributes)
|
||||
|
||||
relationships = try (NoRelatives() as? EntityType.RelatedType) ?? container.decode(EntityType.RelatedType.self, forKey: .relationships)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Id.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 7/24/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Any type that you would like to be encoded to and
|
||||
/// decoded from JSON API ids should conform to this
|
||||
/// protocol. Conformance for `String` and `UUID`
|
||||
/// is given by this library.
|
||||
public protocol RawIdType: Codable, Equatable {
|
||||
static func unique() -> Self
|
||||
}
|
||||
|
||||
public protocol Identifier: Codable, Equatable {
|
||||
init()
|
||||
}
|
||||
|
||||
public struct Unidentified: Identifier {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public protocol IdType: Identifier {
|
||||
associatedtype EntityType: JSONAPI.EntityType
|
||||
associatedtype RawType: RawIdType
|
||||
|
||||
var rawValue: RawType { get }
|
||||
}
|
||||
|
||||
extension UUID: RawIdType {
|
||||
public static func unique() -> UUID {
|
||||
return UUID()
|
||||
}
|
||||
}
|
||||
|
||||
extension String: RawIdType {
|
||||
public static func unique() -> String {
|
||||
return UUID().uuidString
|
||||
}
|
||||
}
|
||||
|
||||
/// An Entity ID. These IDs can be encoded to or decoded from
|
||||
/// JSON API IDs.
|
||||
public struct Id<RawType: RawIdType, EntityType: JSONAPI.EntityType>: IdType {
|
||||
public let rawValue: RawType
|
||||
|
||||
public init(rawValue: RawType) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
rawValue = try container.decode(RawType.self)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
|
||||
public init() {
|
||||
rawValue = .unique()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// Relationship.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 8/31/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An Entity relationship that can be encoded to or decoded from
|
||||
/// a JSON API "Resource Linkage."
|
||||
/// You should use the `ToOneRelationship` and `ToManyRelationship`
|
||||
/// concrete types.
|
||||
/// See https://jsonapi.org/format/#document-resource-object-linkage
|
||||
public protocol Relationship: Equatable, Encodable {
|
||||
associatedtype EntityType: JSONAPI.EntityType where EntityType.Identifier: IdType
|
||||
var ids: [EntityType.Identifier] { get }
|
||||
}
|
||||
|
||||
/// An Entity relationship that can be encoded to or decoded from
|
||||
/// a JSON API "Resource Linkage."
|
||||
/// See https://jsonapi.org/format/#document-resource-object-linkage
|
||||
/// A convenient typealias might make your code much more legible: `One<Entity>`
|
||||
public struct ToOneRelationship<EntityType: JSONAPI.EntityType>: Equatable, Relationship, Decodable where EntityType.Identifier: IdType {
|
||||
public let id: EntityType.Identifier
|
||||
|
||||
public init(entity: Entity<EntityType>) {
|
||||
id = entity.id
|
||||
}
|
||||
|
||||
public var ids: [EntityType.Identifier] {
|
||||
return [id]
|
||||
}
|
||||
}
|
||||
|
||||
/// An Entity relationship that can be encoded to or decoded from
|
||||
/// a JSON API "Resource Linkage."
|
||||
/// See https://jsonapi.org/format/#document-resource-object-linkage
|
||||
/// A convenient typealias might make your code much more legible: `Many<Entity>`
|
||||
public struct ToManyRelationship<EntityType: JSONAPI.EntityType>: Equatable, Relationship, Decodable where EntityType.Identifier: IdType {
|
||||
public let ids: [EntityType.Identifier]
|
||||
|
||||
public init(entities: [Entity<EntityType>]) {
|
||||
ids = entities.map { $0.id }
|
||||
}
|
||||
|
||||
public init<T: Relationship>(relationships: [T]) where T.EntityType == EntityType {
|
||||
ids = relationships.flatMap { $0.ids }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Codable
|
||||
private enum ResourceLinkageCodingKeys: String, CodingKey {
|
||||
case data = "data"
|
||||
}
|
||||
private enum ResourceIdentifierCodingKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
case entityType = "type"
|
||||
}
|
||||
|
||||
public enum JSONAPIEncodingError: Swift.Error {
|
||||
case typeMismatch(expected: String, found: String)
|
||||
}
|
||||
|
||||
extension ToOneRelationship {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
let identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||
|
||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||
|
||||
guard type == EntityType.type else {
|
||||
throw JSONAPIEncodingError.typeMismatch(expected: EntityType.type, found: type)
|
||||
}
|
||||
|
||||
id = try identifier.decode(EntityType.Identifier.self, forKey: .id)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
|
||||
|
||||
try identifier.encode(id, forKey: .id)
|
||||
try identifier.encode(EntityType.type, forKey: .entityType)
|
||||
}
|
||||
}
|
||||
|
||||
extension ToManyRelationship {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
|
||||
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
|
||||
|
||||
var newIds = [EntityType.Identifier]()
|
||||
while !identifiers.isAtEnd {
|
||||
let identifier = try identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
|
||||
|
||||
let type = try identifier.decode(String.self, forKey: .entityType)
|
||||
|
||||
guard type == EntityType.type else {
|
||||
throw JSONAPIEncodingError.typeMismatch(expected: EntityType.type, found: type)
|
||||
}
|
||||
|
||||
newIds.append(try identifier.decode(EntityType.Identifier.self, forKey: .id))
|
||||
}
|
||||
ids = newIds
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
|
||||
var identifiers = container.nestedUnkeyedContainer(forKey: .data)
|
||||
|
||||
for id in ids {
|
||||
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
|
||||
|
||||
try identifier.encode(id, forKey: .id)
|
||||
try identifier.encode(EntityType.type, forKey: .entityType)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user