mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Merge pull request #51 from mattpolzin/feature/primary-resource-body-improvements
Feature/primary resource body improvements
This commit is contained in:
@@ -29,7 +29,9 @@ extension Optional: OptionalCodablePrimaryResource where Wrapped: CodablePrimary
|
||||
/// An `EncodableResourceBody` is a `ResourceBody` that only supports being
|
||||
/// encoded. It is actually weaker than `ResourceBody`, which supports both encoding
|
||||
/// and decoding.
|
||||
public protocol EncodableResourceBody: Equatable, Encodable {}
|
||||
public protocol EncodableResourceBody: Equatable, Encodable {
|
||||
associatedtype PrimaryResource
|
||||
}
|
||||
|
||||
/// A `CodableResourceBody` is a representation of the body of the JSON:API Document.
|
||||
/// It can either be one resource (which can be specified as optional or not)
|
||||
@@ -49,19 +51,19 @@ public func +<R: ResourceBodyAppendable>(_ left: R, right: R) -> R {
|
||||
/// A type allowing for a document body containing 1 primary resource.
|
||||
/// If the `Entity` specialization is an `Optional` type, the body can contain
|
||||
/// 0 or 1 primary resources.
|
||||
public struct SingleResourceBody<Entity: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
||||
public let value: Entity
|
||||
public struct SingleResourceBody<PrimaryResource: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
||||
public let value: PrimaryResource
|
||||
|
||||
public init(resourceObject: Entity) {
|
||||
public init(resourceObject: PrimaryResource) {
|
||||
self.value = resourceObject
|
||||
}
|
||||
}
|
||||
|
||||
/// A type allowing for a document body containing 0 or more primary resources.
|
||||
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, ResourceBodyAppendable {
|
||||
public let values: [Entity]
|
||||
public struct ManyResourceBody<PrimaryResource: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, ResourceBodyAppendable {
|
||||
public let values: [PrimaryResource]
|
||||
|
||||
public init(resourceObjects: [Entity]) {
|
||||
public init(resourceObjects: [PrimaryResource]) {
|
||||
values = resourceObjects
|
||||
}
|
||||
|
||||
@@ -73,6 +75,8 @@ public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: Encoda
|
||||
/// Use NoResourceBody to indicate you expect a JSON API document to not
|
||||
/// contain a "data" top-level key.
|
||||
public struct NoResourceBody: CodableResourceBody {
|
||||
public typealias PrimaryResource = Void
|
||||
|
||||
public static var none: NoResourceBody { return NoResourceBody() }
|
||||
}
|
||||
|
||||
@@ -82,7 +86,7 @@ extension SingleResourceBody {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
let anyNil: Any? = nil
|
||||
let nilValue = anyNil as? Entity
|
||||
let nilValue = anyNil as? PrimaryResource
|
||||
guard value != nilValue else {
|
||||
try container.encodeNil()
|
||||
return
|
||||
@@ -92,18 +96,18 @@ extension SingleResourceBody {
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody: Decodable, CodableResourceBody where Entity: OptionalCodablePrimaryResource {
|
||||
extension SingleResourceBody: Decodable, CodableResourceBody where PrimaryResource: OptionalCodablePrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? Entity {
|
||||
let val = anyNil as? PrimaryResource {
|
||||
value = val
|
||||
return
|
||||
}
|
||||
|
||||
value = try container.decode(Entity.self)
|
||||
value = try container.decode(PrimaryResource.self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,12 +121,12 @@ extension ManyResourceBody {
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: Decodable, CodableResourceBody where Entity: CodablePrimaryResource {
|
||||
extension ManyResourceBody: Decodable, CodableResourceBody where PrimaryResource: CodablePrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
var valueAggregator = [Entity]()
|
||||
var valueAggregator = [PrimaryResource]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(Entity.self))
|
||||
valueAggregator.append(try container.decode(PrimaryResource.self))
|
||||
}
|
||||
values = valueAggregator
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import Poly
|
||||
public typealias EncodableJSONPoly = Poly & EncodablePrimaryResource
|
||||
|
||||
public typealias EncodablePolyWrapped = Encodable & Equatable
|
||||
public typealias PolyWrapped = EncodablePolyWrapped & Decodable
|
||||
public typealias CodablePolyWrapped = EncodablePolyWrapped & Decodable
|
||||
|
||||
extension Poly0: CodablePrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
@@ -35,42 +35,42 @@ extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly1: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped {}
|
||||
where A: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 2 types
|
||||
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly2: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 3 types
|
||||
extension Poly3: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly3: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 4 types
|
||||
extension Poly4: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly4: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 5 types
|
||||
extension Poly5: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly5: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 6 types
|
||||
extension Poly6: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly6: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 7 types
|
||||
extension Poly7: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
@@ -84,7 +84,7 @@ extension Poly7: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
G: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly7: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 8 types
|
||||
extension Poly8: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
@@ -99,7 +99,7 @@ extension Poly8: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
H: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly8: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped, H: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 9 types
|
||||
extension Poly9: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
@@ -115,7 +115,7 @@ extension Poly9: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
I: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly9: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped, H: CodablePolyWrapped, I: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 10 types
|
||||
extension Poly10: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
@@ -132,7 +132,7 @@ extension Poly10: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
J: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly10: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped, J: PolyWrapped {}
|
||||
where A: CodablePolyWrapped, B: CodablePolyWrapped, C: CodablePolyWrapped, D: CodablePolyWrapped, E: CodablePolyWrapped, F: CodablePolyWrapped, G: CodablePolyWrapped, H: CodablePolyWrapped, I: CodablePolyWrapped, J: CodablePolyWrapped {}
|
||||
|
||||
// MARK: - 11 types
|
||||
extension Poly11: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
@@ -151,14 +151,14 @@ extension Poly11: EncodablePrimaryResource, OptionalEncodablePrimaryResource
|
||||
|
||||
extension Poly11: CodablePrimaryResource, OptionalCodablePrimaryResource
|
||||
where
|
||||
A: PolyWrapped,
|
||||
B: PolyWrapped,
|
||||
C: PolyWrapped,
|
||||
D: PolyWrapped,
|
||||
E: PolyWrapped,
|
||||
F: PolyWrapped,
|
||||
G: PolyWrapped,
|
||||
H: PolyWrapped,
|
||||
I: PolyWrapped,
|
||||
J: PolyWrapped,
|
||||
K: PolyWrapped {}
|
||||
A: CodablePolyWrapped,
|
||||
B: CodablePolyWrapped,
|
||||
C: CodablePolyWrapped,
|
||||
D: CodablePolyWrapped,
|
||||
E: CodablePolyWrapped,
|
||||
F: CodablePolyWrapped,
|
||||
G: CodablePolyWrapped,
|
||||
H: CodablePolyWrapped,
|
||||
I: CodablePolyWrapped,
|
||||
J: CodablePolyWrapped,
|
||||
K: CodablePolyWrapped {}
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
// Created by Mathew Polzin on 11/3/19.
|
||||
//
|
||||
|
||||
public enum Comparison: Equatable, CustomStringConvertible {
|
||||
public protocol Comparable: CustomStringConvertible {
|
||||
var rawValue: String { get }
|
||||
|
||||
var isSame: Bool { get }
|
||||
}
|
||||
|
||||
public enum Comparison: Comparable, Equatable {
|
||||
case same
|
||||
case different(String, String)
|
||||
case prebuilt(String)
|
||||
@@ -50,7 +56,7 @@ public enum Comparison: Equatable, CustomStringConvertible {
|
||||
|
||||
public typealias NamedDifferences = [String: String]
|
||||
|
||||
public protocol PropertyComparable: CustomStringConvertible {
|
||||
public protocol PropertyComparable: Comparable {
|
||||
var differences: NamedDifferences { get }
|
||||
}
|
||||
|
||||
|
||||
@@ -79,28 +79,8 @@ public enum BodyComparison: Equatable, CustomStringConvertible {
|
||||
public var rawValue: String { description }
|
||||
}
|
||||
|
||||
extension EncodableJSONAPIDocument where Body: Equatable {
|
||||
public func compare<T>(to other: Self) -> DocumentComparison where PrimaryResourceBody == SingleResourceBody<T>, T: ResourceObjectType {
|
||||
return DocumentComparison(
|
||||
apiDescription: Comparison(
|
||||
String(describing: apiDescription),
|
||||
String(describing: other.apiDescription)
|
||||
),
|
||||
body: body.compare(to: other.body)
|
||||
)
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> DocumentComparison where PrimaryResourceBody == SingleResourceBody<T?>, T: ResourceObjectType {
|
||||
return DocumentComparison(
|
||||
apiDescription: Comparison(
|
||||
String(describing: apiDescription),
|
||||
String(describing: other.apiDescription)
|
||||
),
|
||||
body: body.compare(to: other.body)
|
||||
)
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> DocumentComparison where PrimaryResourceBody == ManyResourceBody<T>, T: ResourceObjectType {
|
||||
extension EncodableJSONAPIDocument where Body: Equatable, PrimaryResourceBody: _OptionalResourceBody {
|
||||
public func compare(to other: Self) -> DocumentComparison {
|
||||
return DocumentComparison(
|
||||
apiDescription: Comparison(
|
||||
String(describing: apiDescription),
|
||||
@@ -111,64 +91,8 @@ extension EncodableJSONAPIDocument where Body: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension DocumentBody where Self: Equatable {
|
||||
public func compare<T>(to other: Self) -> BodyComparison where T: ResourceObjectType, PrimaryResourceBody == SingleResourceBody<T> {
|
||||
|
||||
// rule out case where they are the same
|
||||
guard self != other else {
|
||||
return .same
|
||||
}
|
||||
|
||||
// rule out case where they are both error bodies
|
||||
if let errors1 = errors, let errors2 = other.errors {
|
||||
return .differentErrors(
|
||||
BodyComparison.compare(
|
||||
errors: errors1, meta, links,
|
||||
with: errors2, meta, links
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// rule out the case where they are both data
|
||||
if let data1 = data, let data2 = other.data {
|
||||
return .differentData(data1.compare(to: data2))
|
||||
}
|
||||
|
||||
// we are left with the case where one is data and the
|
||||
// other is an error if self.isError, then "the error
|
||||
// is on the left"
|
||||
return .dataErrorMismatch(errorOnLeft: isError)
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> BodyComparison where T: ResourceObjectType, PrimaryResourceBody == SingleResourceBody<T?> {
|
||||
|
||||
// rule out case where they are the same
|
||||
guard self != other else {
|
||||
return .same
|
||||
}
|
||||
|
||||
// rule out case where they are both error bodies
|
||||
if let errors1 = errors, let errors2 = other.errors {
|
||||
return .differentErrors(
|
||||
BodyComparison.compare(
|
||||
errors: errors1, meta, links,
|
||||
with: errors2, meta, links
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// rule out the case where they are both data
|
||||
if let data1 = data, let data2 = other.data {
|
||||
return .differentData(data1.compare(to: data2))
|
||||
}
|
||||
|
||||
// we are left with the case where one is data and the
|
||||
// other is an error if self.isError, then "the error
|
||||
// is on the left"
|
||||
return .dataErrorMismatch(errorOnLeft: isError)
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> BodyComparison where T: ResourceObjectType, PrimaryResourceBody == ManyResourceBody<T> {
|
||||
extension DocumentBody where Self: Equatable, PrimaryResourceBody: _OptionalResourceBody {
|
||||
public func compare(to other: Self) -> BodyComparison {
|
||||
|
||||
// rule out case where they are the same
|
||||
guard self != other else {
|
||||
|
||||
@@ -33,26 +33,8 @@ public struct DocumentDataComparison: Equatable, PropertyComparable {
|
||||
}
|
||||
}
|
||||
|
||||
extension DocumentBodyData {
|
||||
public func compare<T>(to other: Self) -> DocumentDataComparison where T: ResourceObjectType, PrimaryResourceBody == SingleResourceBody<T> {
|
||||
return .init(
|
||||
primary: primary.compare(to: other.primary),
|
||||
includes: includes.compare(to: other.includes),
|
||||
meta: Comparison(meta, other.meta),
|
||||
links: Comparison(links, other.links)
|
||||
)
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> DocumentDataComparison where T: ResourceObjectType, PrimaryResourceBody == SingleResourceBody<T?> {
|
||||
return .init(
|
||||
primary: primary.compare(to: other.primary),
|
||||
includes: includes.compare(to: other.includes),
|
||||
meta: Comparison(meta, other.meta),
|
||||
links: Comparison(links, other.links)
|
||||
)
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> DocumentDataComparison where T: ResourceObjectType, PrimaryResourceBody == ManyResourceBody<T> {
|
||||
extension DocumentBodyData where PrimaryResourceBody: _OptionalResourceBody {
|
||||
public func compare(to other: Self) -> DocumentDataComparison {
|
||||
return .init(
|
||||
primary: primary.compare(to: other.primary),
|
||||
includes: includes.compare(to: other.includes),
|
||||
@@ -63,28 +45,23 @@ extension DocumentBodyData {
|
||||
}
|
||||
|
||||
public enum PrimaryResourceBodyComparison: Equatable, CustomStringConvertible {
|
||||
case single(ResourceObjectComparison)
|
||||
case many(ManyResourceObjectComparison)
|
||||
case other(Comparison)
|
||||
case oneOrMore(ManyResourceObjectComparison)
|
||||
case optionalSingle(Comparison)
|
||||
|
||||
public var isSame: Bool {
|
||||
switch self {
|
||||
case .other(let comparison):
|
||||
case .optionalSingle(let comparison):
|
||||
return comparison == .same
|
||||
case .single(let comparison):
|
||||
return comparison.isSame
|
||||
case .many(let comparison):
|
||||
case .oneOrMore(let comparison):
|
||||
return comparison.isSame
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .other(let comparison):
|
||||
case .optionalSingle(let comparison):
|
||||
return comparison.rawValue
|
||||
case .single(let comparison):
|
||||
return comparison.rawValue
|
||||
case .many(let comparison):
|
||||
case .oneOrMore(let comparison):
|
||||
return comparison.rawValue
|
||||
}
|
||||
}
|
||||
@@ -109,42 +86,19 @@ public struct ManyResourceObjectComparison: Equatable, PropertyComparable {
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody where Entity: ResourceObjectType {
|
||||
extension _OptionalResourceBody where WrappedPrimaryResourceType: ResourceObjectType {
|
||||
public func compare(to other: Self) -> PrimaryResourceBodyComparison {
|
||||
return .single(.init(value, other.value))
|
||||
}
|
||||
}
|
||||
guard let one = optionalResourceObject,
|
||||
let two = other.optionalResourceObject else {
|
||||
|
||||
public protocol _OptionalResourceObjectType {
|
||||
associatedtype Wrapped: ResourceObjectType
|
||||
func nilOrName<T>(_ resObj: [T]?) -> String {
|
||||
resObj.map { _ in String(describing: T.self) } ?? "nil"
|
||||
}
|
||||
|
||||
var maybeValue: Wrapped? { get }
|
||||
}
|
||||
|
||||
extension Optional: _OptionalResourceObjectType where Wrapped: ResourceObjectType {
|
||||
public var maybeValue: Wrapped? {
|
||||
switch self {
|
||||
case .none:
|
||||
return nil
|
||||
case .some(let value):
|
||||
return value
|
||||
return .optionalSingle(Comparison(nilOrName(optionalResourceObject), nilOrName(other.optionalResourceObject)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody where Entity: _OptionalResourceObjectType {
|
||||
public func compare(to other: Self) -> PrimaryResourceBodyComparison {
|
||||
guard let one = value.maybeValue,
|
||||
let two = other.value.maybeValue else {
|
||||
return .other(Comparison(value, other.value))
|
||||
}
|
||||
return .single(.init(one, two))
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody where Entity: ResourceObjectType {
|
||||
public func compare(to other: Self) -> PrimaryResourceBodyComparison {
|
||||
return .many(.init(values.compare(to: other.values, using: { r1, r2 in
|
||||
return .oneOrMore(.init(one.compare(to: two, using: { r1, r2 in
|
||||
let r1AsResource = r1 as? AbstractResourceObjectType
|
||||
|
||||
let maybeComparison = r1AsResource
|
||||
@@ -165,3 +119,39 @@ extension ManyResourceBody where Entity: ResourceObjectType {
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
public protocol _OptionalResourceBody {
|
||||
associatedtype WrappedPrimaryResourceType: ResourceObjectType
|
||||
var optionalResourceObject: [WrappedPrimaryResourceType]? { get }
|
||||
}
|
||||
|
||||
public protocol _OptionalResourceObjectType {
|
||||
associatedtype Wrapped: ResourceObjectType
|
||||
|
||||
var maybeValue: Wrapped? { get }
|
||||
}
|
||||
|
||||
extension Optional: _OptionalResourceObjectType where Wrapped: ResourceObjectType {
|
||||
public var maybeValue: Wrapped? {
|
||||
switch self {
|
||||
case .none:
|
||||
return nil
|
||||
case .some(let value):
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ResourceObject: _OptionalResourceObjectType {
|
||||
public var maybeValue: Self? { self }
|
||||
}
|
||||
|
||||
extension ManyResourceBody: _OptionalResourceBody where PrimaryResource: ResourceObjectType {
|
||||
public var optionalResourceObject: [PrimaryResource]? { values }
|
||||
}
|
||||
|
||||
extension SingleResourceBody: _OptionalResourceBody where PrimaryResource: _OptionalResourceObjectType {
|
||||
public typealias WrappedPrimaryResourceType = PrimaryResource.Wrapped
|
||||
|
||||
public var optionalResourceObject: [WrappedPrimaryResourceType]? { value.maybeValue.map { [$0] } }
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ final class DocumentCompareTests: XCTestCase {
|
||||
XCTAssertTrue(d2.compare(to: d2).differences.isEmpty)
|
||||
XCTAssertTrue(d3.compare(to: d3).differences.isEmpty)
|
||||
XCTAssertTrue(d4.compare(to: d4).differences.isEmpty)
|
||||
XCTAssertTrue(d5.compare(to: d5).differences.isEmpty)
|
||||
XCTAssertTrue(d6.compare(to: d6).differences.isEmpty)
|
||||
XCTAssertTrue(d7.compare(to: d7).differences.isEmpty)
|
||||
XCTAssertTrue(d8.compare(to: d8).differences.isEmpty)
|
||||
XCTAssertTrue(d9.compare(to: d9).differences.isEmpty)
|
||||
XCTAssertTrue(d10.compare(to: d10).differences.isEmpty)
|
||||
}
|
||||
|
||||
func test_errorAndData() {
|
||||
@@ -41,6 +47,18 @@ final class DocumentCompareTests: XCTestCase {
|
||||
XCTAssertEqual(d3.compare(to: d6).differences, [
|
||||
"Body": ##"(Includes: (include 2: missing)), (Primary Resource: (resource 2: 'age' attribute: 10 ≠ 12, 'bestFriend' relationship: Optional(Id(2)) ≠ nil, 'favoriteColor' attribute: nil ≠ Optional("blue"), 'name' attribute: name ≠ Fig, id: 1 ≠ 5), (resource 3: missing))"##
|
||||
])
|
||||
|
||||
XCTAssertEqual(d7.compare(to: d8).differences, [
|
||||
"Body": ##"(Primary Resource: nil ≠ ResourceObject<TestDescription, NoMetadata, NoLinks, String>)"##
|
||||
])
|
||||
|
||||
XCTAssertEqual(d8.compare(to: d9).differences, [
|
||||
"Body": ##"(Primary Resource: (resource 1: 'age' attribute: 10 ≠ 12, 'bestFriend' relationship: Optional(Id(2)) ≠ nil, 'favoriteColor' attribute: nil ≠ Optional("blue"), 'name' attribute: name ≠ Fig, id: 1 ≠ 5))"##
|
||||
])
|
||||
|
||||
XCTAssertEqual(d1.compare(to: d10).differences, [
|
||||
"Body": ##"(Primary Resource: (resource 1: 'age' attribute: 10 ≠ 12, 'bestFriend' relationship: Optional(Id(2)) ≠ nil, 'favoriteColor' attribute: nil ≠ Optional("blue"), 'name' attribute: name ≠ Fig, id: 1 ≠ 5))"##
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +98,8 @@ fileprivate typealias TestType2 = ResourceObject<TestDescription2, NoMetadata, N
|
||||
|
||||
fileprivate typealias SingleDocument = JSONAPI.Document<SingleResourceBody<TestType>, NoMetadata, NoLinks, Include2<TestType, TestType2>, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
|
||||
fileprivate typealias OptionalSingleDocument = JSONAPI.Document<SingleResourceBody<TestType?>, NoMetadata, NoLinks, Include2<TestType, TestType2>, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
|
||||
fileprivate typealias ManyDocument = JSONAPI.Document<ManyResourceBody<TestType>, NoMetadata, NoLinks, Include2<TestType, TestType2>, NoAPIDescription, BasicJSONAPIError<String>>
|
||||
|
||||
fileprivate let r1 = TestType(
|
||||
@@ -168,3 +188,35 @@ fileprivate let d6 = ManyDocument(
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d7 = OptionalSingleDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObject: nil),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d8 = OptionalSingleDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObject: r1),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d9 = OptionalSingleDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObject: r2),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d10 = SingleDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObject: r2),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user