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,4 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
@@ -0,0 +1,28 @@
|
||||
// swift-tools-version:4.2
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "JSONAPI",
|
||||
products: [
|
||||
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||
.library(
|
||||
name: "JSONAPI",
|
||||
targets: ["JSONAPI"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
|
||||
.target(
|
||||
name: "JSONAPI",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "JSONAPITests",
|
||||
dependencies: ["JSONAPI"]),
|
||||
]
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// DocumentTests.swift
|
||||
// JSONAPITests
|
||||
//
|
||||
// Created by Mathew Polzin on 11/12/18.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
|
||||
class DocumentTests: XCTestCase {
|
||||
|
||||
func test_singleDocumentNoIncludes() {
|
||||
let document = try? JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<ArticleType>, Include0, TmpError>.self, from: single_document_no_includes)
|
||||
|
||||
XCTAssertNotNil(document)
|
||||
|
||||
guard let d = document else { return }
|
||||
|
||||
XCTAssertFalse(d.body.isError)
|
||||
XCTAssertNotNil(d.body.data)
|
||||
XCTAssertEqual(d.body.data?.0.value?.id.rawValue, "1")
|
||||
XCTAssertEqual(d.body.data?.included.count, 0)
|
||||
}
|
||||
|
||||
func test_singleDocumentSomeIncludes() {
|
||||
let document = try? JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<ArticleType>, Include1<AuthorType>, TmpError>.self, from: single_document_some_includes)
|
||||
|
||||
XCTAssertNotNil(document)
|
||||
|
||||
guard let d = document else { return }
|
||||
|
||||
XCTAssertFalse(d.body.isError)
|
||||
XCTAssertNotNil(d.body.data)
|
||||
XCTAssertEqual(d.body.data?.0.value?.id.rawValue, "1")
|
||||
XCTAssertEqual(d.body.data?.included.count, 1)
|
||||
XCTAssertEqual(d.body.data?.included[Author.self].count, 1)
|
||||
XCTAssertEqual(d.body.data?.included[Author.self][0].id.rawValue, "33")
|
||||
}
|
||||
|
||||
func test_manyDocumentNoIncludes() {
|
||||
let document = try? JSONDecoder().decode(JSONAPIDocument<ManyResourceBody<ArticleType>, Include0, TmpError>.self, from: many_document_no_includes)
|
||||
|
||||
XCTAssertNotNil(document)
|
||||
|
||||
guard let d = document else { return }
|
||||
|
||||
XCTAssertFalse(d.body.isError)
|
||||
XCTAssertNotNil(d.body.data)
|
||||
XCTAssertEqual(d.body.data?.0.values.count, 3)
|
||||
XCTAssertEqual(d.body.data?.0.values[0].id.rawValue, "1")
|
||||
XCTAssertEqual(d.body.data?.0.values[1].id.rawValue, "2")
|
||||
XCTAssertEqual(d.body.data?.0.values[2].id.rawValue, "3")
|
||||
XCTAssertEqual(d.body.data?.included.count, 0)
|
||||
}
|
||||
|
||||
func test_manyDocumentSomeIncludes() {
|
||||
let document = try? JSONDecoder().decode(JSONAPIDocument<ManyResourceBody<ArticleType>, Include1<AuthorType>, TmpError>.self, from: many_document_some_includes)
|
||||
|
||||
XCTAssertNotNil(document)
|
||||
|
||||
guard let d = document else { return }
|
||||
|
||||
XCTAssertFalse(d.body.isError)
|
||||
XCTAssertNotNil(d.body.data)
|
||||
XCTAssertEqual(d.body.data?.0.values.count, 3)
|
||||
XCTAssertEqual(d.body.data?.0.values[0].id.rawValue, "1")
|
||||
XCTAssertEqual(d.body.data?.0.values[1].id.rawValue, "2")
|
||||
XCTAssertEqual(d.body.data?.0.values[2].id.rawValue, "3")
|
||||
XCTAssertEqual(d.body.data?.included.count, 3)
|
||||
XCTAssertEqual(d.body.data?.included[Author.self].count, 3)
|
||||
XCTAssertEqual(d.body.data?.included[Author.self][0].id.rawValue, "33")
|
||||
XCTAssertEqual(d.body.data?.included[Author.self][1].id.rawValue, "22")
|
||||
XCTAssertEqual(d.body.data?.included[Author.self][2].id.rawValue, "11")
|
||||
}
|
||||
|
||||
enum AuthorType: EntityType {
|
||||
static var type: String { return "authors" }
|
||||
|
||||
typealias Identifier = Id<String, AuthorType>
|
||||
typealias AttributeType = NoAttributes
|
||||
typealias RelatedType = NoRelatives
|
||||
}
|
||||
|
||||
typealias Author = Entity<AuthorType>
|
||||
|
||||
enum ArticleType: EntityType {
|
||||
static var type: String { return "articles" }
|
||||
|
||||
typealias Identifier = Id<String, ArticleType>
|
||||
typealias AttributeType = NoAttributes
|
||||
typealias RelatedType = Relationships
|
||||
|
||||
struct Relationships: Relatives {
|
||||
let author: ToOneRelationship<AuthorType>
|
||||
}
|
||||
}
|
||||
|
||||
typealias Article = Entity<ArticleType>
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// DocumentStubs.swift
|
||||
// JSONAPITests
|
||||
//
|
||||
// Created by Mathew Polzin on 11/12/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let single_document_no_includes = """
|
||||
{
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "33"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let single_document_some_includes = """
|
||||
{
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "33"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"included": [
|
||||
{
|
||||
"id": "33",
|
||||
"type": "authors"
|
||||
}
|
||||
]
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let many_document_no_includes = """
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "1",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "33"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "22"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "11"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let many_document_some_includes = """
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "1",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "33"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "22"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"type": "articles",
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": {
|
||||
"type": "authors",
|
||||
"id": "11"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"included": [
|
||||
{
|
||||
"id": "33",
|
||||
"type": "authors"
|
||||
},
|
||||
{
|
||||
"id": "22",
|
||||
"type": "authors"
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"type": "authors"
|
||||
}
|
||||
]
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// EntityTests.swift
|
||||
// ElevatedCoreTests
|
||||
//
|
||||
// Created by Mathew Polzin on 7/25/18.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
|
||||
class EntityTests: XCTestCase {
|
||||
|
||||
func test_relationship_access() {
|
||||
let entity1 = TestEntity1()
|
||||
let entity2 = TestEntity2(other: entity1.pointer)
|
||||
|
||||
XCTAssertEqual(entity2.relationships.other, entity1.pointer)
|
||||
}
|
||||
|
||||
func test_relationship_operator_access() {
|
||||
let entity1 = TestEntity1()
|
||||
let entity2 = TestEntity2(other: entity1.pointer)
|
||||
|
||||
XCTAssertEqual(entity2 ~> \.other, entity1.id)
|
||||
}
|
||||
|
||||
func test_toMany_relationship_operator_access() {
|
||||
let entity1 = TestEntity1()
|
||||
let entity2 = TestEntity1()
|
||||
let entity4 = TestEntity1()
|
||||
let entity3 = TestEntity3(others: .init(relationships: [entity1.pointer, entity2.pointer, entity4.pointer]))
|
||||
|
||||
XCTAssertEqual(entity3 ~> \.others, [entity1.id, entity2.id, entity4.id])
|
||||
}
|
||||
|
||||
func test_relationshipIds() {
|
||||
let entity1 = TestEntity1()
|
||||
let entity2 = TestEntity2(other: entity1.pointer)
|
||||
|
||||
XCTAssertEqual(entity2.relationships.other.ids, [entity1.id])
|
||||
}
|
||||
|
||||
func test_EntityNoRelationshipsNoAttributes() {
|
||||
let entity = try? JSONDecoder().decode(TestEntity1.self, from: entity_no_relationships_no_attributes)
|
||||
|
||||
XCTAssertNotNil(entity)
|
||||
XCTAssert(type(of: entity?.relationships) == NoRelatives?.self)
|
||||
XCTAssert(type(of: entity?.attributes) == NoAttributes?.self)
|
||||
}
|
||||
|
||||
func test_EntityNoRelationshipsSomeAttributes() {
|
||||
let entity = try? JSONDecoder().decode(TestEntity5.self, from: entity_no_relationships_some_attributes)
|
||||
|
||||
XCTAssertNotNil(entity)
|
||||
XCTAssert(type(of: entity?.relationships) == NoRelatives?.self)
|
||||
|
||||
guard let e = entity else { return }
|
||||
|
||||
XCTAssertEqual(e[\.floater], 123.321)
|
||||
}
|
||||
|
||||
func test_EntitySomeRelationshipsNoAttributes() {
|
||||
let entity = try? JSONDecoder().decode(TestEntity3.self, from: entity_some_relationships_no_attributes)
|
||||
|
||||
XCTAssertNotNil(entity)
|
||||
XCTAssert(type(of: entity?.attributes) == NoAttributes?.self)
|
||||
|
||||
guard let e = entity else { return }
|
||||
|
||||
XCTAssertEqual((e ~> \.others).map { $0.rawValue.uuidString }, ["364B3B69-4DF1-467F-B52E-B0C9E44F666E"])
|
||||
}
|
||||
|
||||
func test_EntitySomeRelationshipsSomeAttributes() {
|
||||
let entity = try? JSONDecoder().decode(TestEntity4.self, from: entity_some_relationships_some_attributes)
|
||||
|
||||
XCTAssertNotNil(entity)
|
||||
|
||||
guard let e = entity else { return }
|
||||
|
||||
XCTAssertEqual(e[\.word], "coolio")
|
||||
XCTAssertEqual(e[\.number], 992299)
|
||||
XCTAssertEqual((e ~> \.other).rawValue.uuidString, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
|
||||
}
|
||||
|
||||
enum TestEntityType1: EntityType {
|
||||
static var type: String { return "test_entities"}
|
||||
|
||||
typealias Identifier = Id<UUID, TestEntityType1>
|
||||
typealias AttributeType = NoAttributes
|
||||
typealias RelatedType = NoRelatives
|
||||
}
|
||||
|
||||
typealias TestEntity1 = Entity<TestEntityType1>
|
||||
|
||||
enum TestEntityType2: EntityType {
|
||||
static var type: String { return "second_test_entities"}
|
||||
|
||||
typealias Identifier = Id<UUID, TestEntityType2>
|
||||
typealias RelatedType = Relationships
|
||||
typealias AttributeType = NoAttributes
|
||||
|
||||
struct Relationships: Relatives {
|
||||
let other: ToOneRelationship<TestEntityType1>
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity2 = Entity<TestEntityType2>
|
||||
|
||||
enum TestEntityType3: EntityType {
|
||||
static var type: String { return "third_test_entities"}
|
||||
|
||||
typealias Identifier = Id<UUID, TestEntityType3>
|
||||
typealias RelatedType = Relationships
|
||||
typealias AttributeType = NoAttributes
|
||||
|
||||
struct Relationships: Relatives {
|
||||
let others: ToManyRelationship<TestEntityType1>
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity3 = Entity<TestEntityType3>
|
||||
|
||||
enum TestEntityType4: EntityType {
|
||||
static var type: String { return "fourth_test_entities"}
|
||||
|
||||
typealias Identifier = Id<UUID, TestEntityType4>
|
||||
typealias RelatedType = Relationships
|
||||
typealias AttributeType = Atts
|
||||
|
||||
struct Relationships: Relatives {
|
||||
let other: ToOneRelationship<TestEntityType2>
|
||||
}
|
||||
|
||||
struct Atts: Attributes {
|
||||
let word: String
|
||||
let number: Int
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity4 = Entity<TestEntityType4>
|
||||
|
||||
enum TestEntityType5: EntityType {
|
||||
static var type: String { return "fifth_test_entities"}
|
||||
|
||||
typealias Identifier = Id<UUID, TestEntityType5>
|
||||
typealias RelatedType = NoRelatives
|
||||
typealias AttributeType = Atts
|
||||
|
||||
struct Atts: Attributes {
|
||||
let floater: Double
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity5 = Entity<TestEntityType5>
|
||||
}
|
||||
|
||||
extension Entity where EntityType == EntityTests.TestEntityType2 {
|
||||
init(other: ToOneRelationship<EntityTests.TestEntityType1>) {
|
||||
self.init(relationships: .init(other: other))
|
||||
}
|
||||
}
|
||||
|
||||
extension Entity where EntityType == EntityTests.TestEntityType3 {
|
||||
init(others: ToManyRelationship<EntityTests.TestEntityType1>) {
|
||||
self.init(relationships: .init(others: others))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// EntityStubs.swift
|
||||
// JSONAPITests
|
||||
//
|
||||
// Created by Mathew Polzin on 11/12/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let entity_no_relationships_no_attributes = """
|
||||
{
|
||||
"id": "A24B3B69-4DF1-467F-B52E-B0C9E44F436A",
|
||||
"type": "test_entities"
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let entity_no_relationships_some_attributes = """
|
||||
{
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"type": "fifth_test_entities",
|
||||
"attributes": {
|
||||
"floater": 123.321
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let entity_some_relationships_no_attributes = """
|
||||
{
|
||||
"id": "11113B69-4DF1-467F-B52E-B0C9E44FC444",
|
||||
"type": "third_test_entities",
|
||||
"relationships": {
|
||||
"others": {
|
||||
"data": [{
|
||||
"type": "test_entities",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let entity_some_relationships_some_attributes = """
|
||||
{
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"type": "fourth_test_entities",
|
||||
"attributes": {
|
||||
"word": "coolio",
|
||||
"number": 992299
|
||||
},
|
||||
"relationships": {
|
||||
"other": {
|
||||
"data": {
|
||||
"type": "second_test_entities",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
@@ -0,0 +1,204 @@
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
|
||||
class IncludedTests: XCTestCase {
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
func test_OneInclude() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include1<TestEntityType>>.self, from: one_include)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
}
|
||||
|
||||
func test_TwoSameIncludes() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include1<TestEntityType>>.self, from: two_same_type_includes)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 2)
|
||||
}
|
||||
|
||||
func test_TwoDifferentIncludes() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include2<TestEntityType, TestEntityType2>>.self, from: two_different_type_includes)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
}
|
||||
|
||||
func test_ThreeDifferentIncludes() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include3<TestEntityType, TestEntityType2, TestEntityType4>>.self, from: three_different_type_includes)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
}
|
||||
|
||||
func test_FourDifferentIncludes() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include4<TestEntityType, TestEntityType2, TestEntityType4, TestEntityType6>>.self, from: four_different_type_includes)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity6.self].count, 1)
|
||||
}
|
||||
|
||||
func test_FiveDifferentIncludes() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include5<TestEntityType, TestEntityType2, TestEntityType3, TestEntityType4, TestEntityType6>>.self, from: five_different_type_includes)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity3.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity6.self].count, 1)
|
||||
}
|
||||
|
||||
func test_SixDifferentIncludes() {
|
||||
let maybeIncludes = try? decoder.decode(Includes<Include6<TestEntityType, TestEntityType2, TestEntityType3, TestEntityType4, TestEntityType5, TestEntityType6>>.self, from: six_different_type_includes)
|
||||
|
||||
XCTAssertNotNil(maybeIncludes)
|
||||
|
||||
guard let includes = maybeIncludes else {
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity3.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity5.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity6.self].count, 1)
|
||||
}
|
||||
}
|
||||
|
||||
extension IncludedTests {
|
||||
enum TestEntityType: EntityType {
|
||||
typealias Identifier = Id<UUID, TestEntityType>
|
||||
|
||||
typealias AttributeType = Atts
|
||||
|
||||
typealias RelatedType = NoRelatives
|
||||
|
||||
public static var type: String { return "test_entity1" }
|
||||
|
||||
public struct Atts: Attributes {
|
||||
let foo: String
|
||||
let bar: Int
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity = Entity<TestEntityType>
|
||||
|
||||
enum TestEntityType2: EntityType {
|
||||
typealias Identifier = Id<UUID, TestEntityType2>
|
||||
|
||||
typealias AttributeType = Atts
|
||||
|
||||
typealias RelatedType = Relationships
|
||||
|
||||
public static var type: String { return "test_entity2" }
|
||||
|
||||
public struct Relationships: Relatives {
|
||||
let entity1: ToOneRelationship<TestEntityType>
|
||||
}
|
||||
|
||||
public struct Atts: Attributes {
|
||||
let foo: String
|
||||
let bar: Int
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity2 = Entity<TestEntityType2>
|
||||
|
||||
enum TestEntityType3: EntityType {
|
||||
typealias Identifier = Id<UUID, TestEntityType3>
|
||||
|
||||
typealias AttributeType = NoAttributes
|
||||
|
||||
typealias RelatedType = Relationships
|
||||
|
||||
public static var type: String { return "test_entity3" }
|
||||
|
||||
public struct Relationships: Relatives {
|
||||
let entity1: ToOneRelationship<TestEntityType>
|
||||
let entity2: ToManyRelationship<TestEntityType2>
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity3 = Entity<TestEntityType3>
|
||||
|
||||
enum TestEntityType4: EntityType {
|
||||
typealias Identifier = Id<UUID, TestEntityType4>
|
||||
|
||||
typealias AttributeType = NoAttributes
|
||||
|
||||
typealias RelatedType = NoRelatives
|
||||
|
||||
public static var type: String { return "test_entity4" }
|
||||
}
|
||||
|
||||
typealias TestEntity4 = Entity<TestEntityType4>
|
||||
|
||||
enum TestEntityType5: EntityType {
|
||||
typealias Identifier = Id<UUID, TestEntityType5>
|
||||
|
||||
typealias AttributeType = NoAttributes
|
||||
|
||||
typealias RelatedType = NoRelatives
|
||||
|
||||
public static var type: String { return "test_entity5" }
|
||||
}
|
||||
|
||||
typealias TestEntity5 = Entity<TestEntityType5>
|
||||
|
||||
enum TestEntityType6: EntityType {
|
||||
typealias Identifier = Id<UUID, TestEntityType6>
|
||||
|
||||
typealias AttributeType = NoAttributes
|
||||
|
||||
typealias RelatedType = Relationships
|
||||
|
||||
public static var type: String { return "test_entity6" }
|
||||
|
||||
struct Relationships: Relatives {
|
||||
let entity4: ToOneRelationship<TestEntityType4>
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestEntity6 = Entity<TestEntityType6>
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
//
|
||||
// IncludeStubs.swift
|
||||
// ElevatedCore
|
||||
//
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let one_include = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let two_same_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let two_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let three_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let four_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity6",
|
||||
"id": "11113B69-4DF1-467F-B52E-B0C9E44FC444",
|
||||
"relationships": {
|
||||
"entity4": {
|
||||
"data": {
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let five_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity3",
|
||||
"id": "11223B69-4DF1-467F-B52E-B0C9E44FC443",
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
},
|
||||
"entity2": {
|
||||
"data": [
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity6",
|
||||
"id": "11113B69-4DF1-467F-B52E-B0C9E44FC444",
|
||||
"relationships": {
|
||||
"entity4": {
|
||||
"data": {
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let six_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity3",
|
||||
"id": "11223B69-4DF1-467F-B52E-B0C9E44FC443",
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
},
|
||||
"entity2": {
|
||||
"data": [
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity6",
|
||||
"id": "11113B69-4DF1-467F-B52E-B0C9E44FC444",
|
||||
"relationships": {
|
||||
"entity4": {
|
||||
"data": {
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity5",
|
||||
"id": "A24B3B69-4DF1-467F-B52E-B0C9E44F436A"
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user