mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Merge pull request #46 from mattpolzin/feature/testing-comparisons
Feature/testing comparisons
This commit is contained in:
+2
-2
@@ -6,8 +6,8 @@
|
||||
"repositoryURL": "https://github.com/mattpolzin/Poly.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b24fd3b41bf3126d4c6dede3708135182172af60",
|
||||
"version": "2.2.0"
|
||||
"revision": "18cd995be5c28c4dfdc1464e54ee0efb03e215bf",
|
||||
"version": "2.3.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ let package = Package(
|
||||
targets: ["JSONAPITesting"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.2.0")),
|
||||
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.3.0")),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
||||
@@ -91,7 +91,16 @@ public protocol EncodableJSONAPIDocument: Equatable, Encodable, DocumentBodyCont
|
||||
Body.Error == Error,
|
||||
Body.BodyData == BodyData
|
||||
|
||||
/// The Body of the Document. This body is either one or more errors
|
||||
/// with links and metadata attempted to parse but not guaranteed or
|
||||
/// it is a successful data struct containing all the primary and
|
||||
/// included resources, the metadata, and the links that this
|
||||
/// document type specifies.
|
||||
var body: Body { get }
|
||||
|
||||
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
||||
/// and metadata information about the API itself.
|
||||
var apiDescription: APIDescription { get }
|
||||
}
|
||||
|
||||
/// A `CodableJSONAPIDocument` supports encoding and decoding of a JSON:API
|
||||
@@ -101,6 +110,7 @@ public protocol CodableJSONAPIDocument: EncodableJSONAPIDocument, Decodable wher
|
||||
/// 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
|
||||
@@ -109,15 +119,10 @@ public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaT
|
||||
public typealias Include = IncludeType
|
||||
public typealias BodyData = Body.Data
|
||||
|
||||
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
||||
/// and metadata information about the API itself.
|
||||
// See `EncodableJSONAPIDocument` for documentation.
|
||||
public let apiDescription: APIDescription
|
||||
|
||||
/// The Body of the Document. This body is either one or more errors
|
||||
/// with links and metadata attempted to parse but not guaranteed or
|
||||
/// it is a successful data struct containing all the primary and
|
||||
/// included resources, the metadata, and the links that this
|
||||
/// document type specifies.
|
||||
// See `EncodableJSONAPIDocument` for documentation.
|
||||
public let body: Body
|
||||
|
||||
public init(apiDescription: APIDescription,
|
||||
|
||||
@@ -14,15 +14,15 @@ public typealias Include = EncodableJSONPoly
|
||||
///
|
||||
/// If you have
|
||||
///
|
||||
/// `let includes: Includes<Include2<Thing1, Thing2>> = ...`
|
||||
/// let includes: Includes<Include2<Thing1, Thing2>> = ...
|
||||
///
|
||||
/// then you can access all `Thing1` included resources with
|
||||
///
|
||||
/// `let includedThings = includes[Thing1.self]`
|
||||
/// let includedThings = includes[Thing1.self]
|
||||
public struct Includes<I: Include>: Encodable, Equatable {
|
||||
public static var none: Includes { return .init(values: []) }
|
||||
|
||||
let values: [I]
|
||||
public let values: [I]
|
||||
|
||||
public init(values: [I]) {
|
||||
self.values = values
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
/// Most of the JSON:API Spec defined Error fields.
|
||||
public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Equatable, ErrorDictType {
|
||||
public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Equatable, ErrorDictType, CustomStringConvertible {
|
||||
/// a unique identifier for this particular occurrence of the problem
|
||||
public let id: IdType?
|
||||
// public let links: Links? // we skip this for now to avoid adding complexity to using this basic type.
|
||||
@@ -61,6 +61,10 @@ public struct BasicJSONAPIErrorPayload<IdType: Codable & Equatable>: Codable, Eq
|
||||
].compactMap { $0 }
|
||||
return Dictionary(uniqueKeysWithValues: keysAndValues)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return definedFields.map { "\($0.key): \($0.value)" }.sorted().joined(separator: ", ")
|
||||
}
|
||||
}
|
||||
|
||||
/// `BasicJSONAPIError` optionally decodes many possible fields
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/// `GenericJSONAPIError` can be used to specify whatever error
|
||||
/// payload you expect to need to parse in responses and handle any
|
||||
/// other payload structure as `.unknownError`.
|
||||
public enum GenericJSONAPIError<ErrorPayload: Codable & Equatable>: JSONAPIError {
|
||||
public enum GenericJSONAPIError<ErrorPayload: Codable & Equatable>: JSONAPIError, CustomStringConvertible {
|
||||
case unknownError
|
||||
case error(ErrorPayload)
|
||||
|
||||
@@ -35,6 +35,15 @@ public enum GenericJSONAPIError<ErrorPayload: Codable & Equatable>: JSONAPIError
|
||||
public static var unknown: Self {
|
||||
return .unknownError
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .unknownError:
|
||||
return "unknown error"
|
||||
case .error(let payload):
|
||||
return String(describing: payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension GenericJSONAPIError {
|
||||
|
||||
@@ -102,6 +102,12 @@ extension ResourceObjectProxy {
|
||||
public protocol ResourceObjectType: ResourceObjectProxy, CodablePrimaryResource where Description: ResourceObjectDescription {
|
||||
associatedtype Meta: JSONAPI.Meta
|
||||
associatedtype Links: JSONAPI.Links
|
||||
|
||||
/// Any additional metadata packaged with the entity.
|
||||
var meta: Meta { get }
|
||||
|
||||
/// Links related to the entity.
|
||||
var links: Links { get }
|
||||
}
|
||||
|
||||
public protocol IdentifiableResourceObjectType: ResourceObjectType, Relatable where EntityRawIdType: JSONAPI.RawIdType {}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/5/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
public enum ArrayElementComparison: Equatable, CustomStringConvertible {
|
||||
case same
|
||||
case missing
|
||||
case differentTypes(String, String)
|
||||
case differentValues(String, String)
|
||||
case prebuilt(String)
|
||||
|
||||
public init(sameTypeComparison: Comparison) {
|
||||
switch sameTypeComparison {
|
||||
case .same:
|
||||
self = .same
|
||||
case .different(let one, let two):
|
||||
self = .differentValues(one, two)
|
||||
case .prebuilt(let str):
|
||||
self = .prebuilt(str)
|
||||
}
|
||||
}
|
||||
|
||||
public init(resourceObjectComparison: ResourceObjectComparison) {
|
||||
guard !resourceObjectComparison.isSame else {
|
||||
self = .same
|
||||
return
|
||||
}
|
||||
|
||||
self = .prebuilt(
|
||||
resourceObjectComparison
|
||||
.differences
|
||||
.sorted { $0.key < $1.key }
|
||||
.map { "\($0.key): \($0.value)" }
|
||||
.joined(separator: ", ")
|
||||
)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .same:
|
||||
return "same"
|
||||
case .missing:
|
||||
return "missing"
|
||||
case .differentTypes(let one, let two),
|
||||
.differentValues(let one, let two):
|
||||
return "\(one) ≠ \(two)"
|
||||
case .prebuilt(let description):
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: String { description }
|
||||
}
|
||||
|
||||
extension Array {
|
||||
func compare(to other: Self, using compare: (Element, Element) -> ArrayElementComparison) -> [ArrayElementComparison] {
|
||||
let isSelfLonger = count >= other.count
|
||||
|
||||
let longer = isSelfLonger ? self : other
|
||||
let shorter = isSelfLonger ? other : self
|
||||
|
||||
return longer.indices.map { idx in
|
||||
guard shorter.indices.contains(idx) else {
|
||||
return .missing
|
||||
}
|
||||
|
||||
let this = longer[idx]
|
||||
let other = shorter[idx]
|
||||
|
||||
return compare(this, other)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/3/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
extension Attributes {
|
||||
public func compare(to other: Self) -> [String: Comparison] {
|
||||
let mirror1 = Mirror(reflecting: self)
|
||||
let mirror2 = Mirror(reflecting: other)
|
||||
|
||||
var comparisons = [String: Comparison]()
|
||||
|
||||
for child in mirror1.children {
|
||||
guard let childLabel = child.label else { continue }
|
||||
|
||||
let childDescription = attributeDescription(of: child.value)
|
||||
|
||||
guard let otherChild = mirror2.children.first(where: { $0.label == childLabel }) else {
|
||||
comparisons[childLabel] = .different(childDescription, "missing")
|
||||
continue
|
||||
}
|
||||
|
||||
if (attributesEqual(child.value, otherChild.value)) {
|
||||
comparisons[childLabel] = .same
|
||||
} else {
|
||||
let otherChildDescription = attributeDescription(of: otherChild.value)
|
||||
|
||||
comparisons[childLabel] = .different(childDescription, otherChildDescription)
|
||||
}
|
||||
}
|
||||
|
||||
return comparisons
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func attributesEqual(_ one: Any, _ two: Any) -> Bool {
|
||||
guard let attr = one as? AbstractAttribute else {
|
||||
return false
|
||||
}
|
||||
|
||||
return attr.equals(two)
|
||||
}
|
||||
|
||||
fileprivate func attributeDescription(of thing: Any) -> String {
|
||||
return (thing as? AbstractAttribute)?.abstractDescription ?? String(describing: thing)
|
||||
}
|
||||
|
||||
protocol AbstractAttribute {
|
||||
var abstractDescription: String { get }
|
||||
|
||||
func equals(_ other: Any) -> Bool
|
||||
}
|
||||
|
||||
extension Attribute: AbstractAttribute {
|
||||
var abstractDescription: String { String(describing: value) }
|
||||
|
||||
func equals(_ other: Any) -> Bool {
|
||||
guard let attributeB = other as? Self else {
|
||||
return false
|
||||
}
|
||||
return abstractDescription == attributeB.abstractDescription
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: AbstractAttribute {
|
||||
var abstractDescription: String { String(describing: value) }
|
||||
|
||||
func equals(_ other: Any) -> Bool {
|
||||
guard let attributeB = other as? Self else {
|
||||
return false
|
||||
}
|
||||
return abstractDescription == attributeB.abstractDescription
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Comparison.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/3/19.
|
||||
//
|
||||
|
||||
public enum Comparison: Equatable, CustomStringConvertible {
|
||||
case same
|
||||
case different(String, String)
|
||||
case prebuilt(String)
|
||||
|
||||
init<T: Equatable>(_ one: T, _ two: T) {
|
||||
guard one == two else {
|
||||
self = .different(String(describing: one), String(describing: two))
|
||||
return
|
||||
}
|
||||
self = .same
|
||||
}
|
||||
|
||||
init(reducing other: ArrayElementComparison) {
|
||||
switch other {
|
||||
case .same:
|
||||
self = .same
|
||||
case .differentTypes(let one, let two),
|
||||
.differentValues(let one, let two):
|
||||
self = .different(one, two)
|
||||
case .missing:
|
||||
self = .different("array length 1", "array length 2")
|
||||
case .prebuilt(let str):
|
||||
self = .prebuilt(str)
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .same:
|
||||
return "same"
|
||||
case .different(let one, let two):
|
||||
return "\(one) ≠ \(two)"
|
||||
case .prebuilt(let str):
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: String { description }
|
||||
|
||||
public var isSame: Bool { self == .same }
|
||||
}
|
||||
|
||||
public typealias NamedDifferences = [String: String]
|
||||
|
||||
public protocol PropertyComparable: CustomStringConvertible {
|
||||
var differences: NamedDifferences { get }
|
||||
}
|
||||
|
||||
extension PropertyComparable {
|
||||
public var description: String {
|
||||
return differences
|
||||
.map { "(\($0): \($1))" }
|
||||
.sorted()
|
||||
.joined(separator: ", ")
|
||||
}
|
||||
|
||||
public var rawValue: String { description }
|
||||
|
||||
public var isSame: Bool { differences.isEmpty }
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// DocumentCompare.swift
|
||||
// JSONAPITesting
|
||||
//
|
||||
// Created by Mathew Polzin on 11/4/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
public struct DocumentComparison: Equatable, PropertyComparable {
|
||||
public let apiDescription: Comparison
|
||||
public let body: BodyComparison
|
||||
|
||||
init(apiDescription: Comparison, body: BodyComparison) {
|
||||
self.apiDescription = apiDescription
|
||||
self.body = body
|
||||
}
|
||||
|
||||
public var differences: NamedDifferences {
|
||||
return Dictionary(
|
||||
[
|
||||
apiDescription != .same ? ("API Description", apiDescription.rawValue) : nil,
|
||||
body != .same ? ("Body", body.rawValue) : nil
|
||||
].compactMap { $0 },
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public enum BodyComparison: Equatable, CustomStringConvertible {
|
||||
case same
|
||||
case dataErrorMismatch(errorOnLeft: Bool)
|
||||
case differentErrors(ErrorComparison)
|
||||
case differentData(DocumentDataComparison)
|
||||
|
||||
public typealias ErrorComparison = [Comparison]
|
||||
|
||||
static func compare<E: JSONAPIError, M: JSONAPI.Meta, L: JSONAPI.Links>(errors errors1: [E], _ meta1: M?, _ links1: L?, with errors2: [E], _ meta2: M?, _ links2: L?) -> ErrorComparison {
|
||||
return errors1.compare(
|
||||
to: errors2,
|
||||
using: { error1, error2 in
|
||||
guard error1 != error2 else {
|
||||
return .same
|
||||
}
|
||||
|
||||
return .differentValues(
|
||||
String(describing: error1),
|
||||
String(describing: error2)
|
||||
)
|
||||
}
|
||||
).map(Comparison.init) + [
|
||||
Comparison(meta1, meta2),
|
||||
Comparison(links1, links2)
|
||||
]
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .same:
|
||||
return "same"
|
||||
case .dataErrorMismatch(errorOnLeft: let errorOnLeft):
|
||||
let errorString = "error response"
|
||||
let dataString = "data response"
|
||||
let left = errorOnLeft ? errorString : dataString
|
||||
let right = errorOnLeft ? dataString : errorString
|
||||
|
||||
return "\(left) ≠ \(right)"
|
||||
case .differentErrors(let comparisons):
|
||||
return comparisons
|
||||
.filter { !$0.isSame }
|
||||
.map { $0.rawValue }
|
||||
.sorted()
|
||||
.joined(separator: ", ")
|
||||
case .differentData(let comparison):
|
||||
return comparison.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: String { description }
|
||||
}
|
||||
|
||||
extension Document {
|
||||
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 {
|
||||
return DocumentComparison(
|
||||
apiDescription: Comparison(
|
||||
String(describing: apiDescription),
|
||||
String(describing: other.apiDescription)
|
||||
),
|
||||
body: body.compare(to: other.body)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body {
|
||||
public func compare<T>(to other: Self) -> BodyComparison where T: ResourceObjectType, PrimaryResourceBody == SingleResourceBody<T> {
|
||||
guard self != other else {
|
||||
return .same
|
||||
}
|
||||
|
||||
switch (self, other) {
|
||||
case (.errors(let errors1), .errors(let errors2)):
|
||||
return .differentErrors(BodyComparison.compare(errors: errors1.0,
|
||||
errors1.meta,
|
||||
errors1.links,
|
||||
with: errors2.0,
|
||||
errors2.meta,
|
||||
errors2.links))
|
||||
case (.errors, .data):
|
||||
return .dataErrorMismatch(errorOnLeft: true)
|
||||
case (.data, .errors):
|
||||
return .dataErrorMismatch(errorOnLeft: false)
|
||||
case (.data(let data1), .data(let data2)):
|
||||
return .differentData(data1.compare(to: data2))
|
||||
}
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> BodyComparison where T: ResourceObjectType, PrimaryResourceBody == SingleResourceBody<T?> {
|
||||
guard self != other else {
|
||||
return .same
|
||||
}
|
||||
|
||||
switch (self, other) {
|
||||
case (.errors(let errors1), .errors(let errors2)):
|
||||
return .differentErrors(BodyComparison.compare(errors: errors1.0,
|
||||
errors1.meta,
|
||||
errors1.links,
|
||||
with: errors2.0,
|
||||
errors2.meta,
|
||||
errors2.links))
|
||||
case (.errors, .data):
|
||||
return .dataErrorMismatch(errorOnLeft: true)
|
||||
case (.data, .errors):
|
||||
return .dataErrorMismatch(errorOnLeft: false)
|
||||
case (.data(let data1), .data(let data2)):
|
||||
return .differentData(data1.compare(to: data2))
|
||||
}
|
||||
}
|
||||
|
||||
public func compare<T>(to other: Self) -> BodyComparison where T: ResourceObjectType, PrimaryResourceBody == ManyResourceBody<T> {
|
||||
guard self != other else {
|
||||
return .same
|
||||
}
|
||||
|
||||
switch (self, other) {
|
||||
case (.errors(let errors1), .errors(let errors2)):
|
||||
return .differentErrors(BodyComparison.compare(errors: errors1.0,
|
||||
errors1.meta,
|
||||
errors1.links,
|
||||
with: errors2.0,
|
||||
errors2.meta,
|
||||
errors2.links))
|
||||
case (.errors, .data):
|
||||
return .dataErrorMismatch(errorOnLeft: true)
|
||||
case (.data, .errors):
|
||||
return .dataErrorMismatch(errorOnLeft: false)
|
||||
case (.data(let data1), .data(let data2)):
|
||||
return .differentData(data1.compare(to: data2))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// DocumentDataCompare.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/5/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
public struct DocumentDataComparison: Equatable, PropertyComparable {
|
||||
public let primary: PrimaryResourceBodyComparison
|
||||
public let includes: IncludesComparison
|
||||
public let meta: Comparison
|
||||
public let links: Comparison
|
||||
|
||||
init(primary: PrimaryResourceBodyComparison, includes: IncludesComparison, meta: Comparison, links: Comparison) {
|
||||
self.primary = primary
|
||||
self.includes = includes
|
||||
self.meta = meta
|
||||
self.links = links
|
||||
}
|
||||
|
||||
public var differences: NamedDifferences {
|
||||
return Dictionary(
|
||||
[
|
||||
!primary.isSame ? ("Primary Resource", primary.rawValue) : nil,
|
||||
!includes.isSame ? ("Includes", includes.rawValue) : nil,
|
||||
!meta.isSame ? ("Meta", meta.rawValue) : nil,
|
||||
!links.isSame ? ("Links", links.rawValue) : nil
|
||||
].compactMap { $0 },
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body.Data {
|
||||
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> {
|
||||
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 enum PrimaryResourceBodyComparison: Equatable, CustomStringConvertible {
|
||||
case single(ResourceObjectComparison)
|
||||
case many(ManyResourceObjectComparison)
|
||||
case other(Comparison)
|
||||
|
||||
public var isSame: Bool {
|
||||
switch self {
|
||||
case .other(let comparison):
|
||||
return comparison == .same
|
||||
case .single(let comparison):
|
||||
return comparison.isSame
|
||||
case .many(let comparison):
|
||||
return comparison.isSame
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .other(let comparison):
|
||||
return comparison.rawValue
|
||||
case .single(let comparison):
|
||||
return comparison.rawValue
|
||||
case .many(let comparison):
|
||||
return comparison.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: String { return description }
|
||||
}
|
||||
|
||||
public struct ManyResourceObjectComparison: Equatable, PropertyComparable {
|
||||
public let comparisons: [ArrayElementComparison]
|
||||
|
||||
public init(_ comparisons: [ArrayElementComparison]) {
|
||||
self.comparisons = comparisons
|
||||
}
|
||||
|
||||
public var differences: NamedDifferences {
|
||||
return comparisons
|
||||
.enumerated()
|
||||
.filter { $0.element != .same }
|
||||
.reduce(into: [String: String]()) { hash, next in
|
||||
hash["resource \(next.offset + 1)"] = next.element.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody where Entity: ResourceObjectType {
|
||||
public func compare(to other: Self) -> PrimaryResourceBodyComparison {
|
||||
return .single(.init(value, other.value))
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
let r1AsResource = r1 as? AbstractResourceObjectType
|
||||
|
||||
let maybeComparison = r1AsResource
|
||||
.flatMap { resource in
|
||||
try? ArrayElementComparison(
|
||||
resourceObjectComparison: resource.abstractCompare(to: r2)
|
||||
)
|
||||
}
|
||||
|
||||
guard let comparison = maybeComparison else {
|
||||
return .differentValues(
|
||||
String(describing: r1),
|
||||
String(describing: r2)
|
||||
)
|
||||
}
|
||||
|
||||
return comparison
|
||||
})))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// IncludesCompare.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/4/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
import Poly
|
||||
|
||||
public struct IncludesComparison: Equatable, PropertyComparable {
|
||||
public let comparisons: [ArrayElementComparison]
|
||||
|
||||
public init(_ comparisons: [ArrayElementComparison]) {
|
||||
self.comparisons = comparisons
|
||||
}
|
||||
|
||||
public var differences: NamedDifferences {
|
||||
return comparisons
|
||||
.enumerated()
|
||||
.filter { $0.element != .same }
|
||||
.reduce(into: [String: String]()) { hash, next in
|
||||
hash["include \(next.offset + 1)"] = next.element.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes {
|
||||
public func compare(to other: Self) -> IncludesComparison {
|
||||
|
||||
return IncludesComparison(
|
||||
values.compare(to: other.values) { thisInclude, otherInclude in
|
||||
guard thisInclude != otherInclude else {
|
||||
return .same
|
||||
}
|
||||
|
||||
let thisWrappedValue = thisInclude.value
|
||||
let otherWrappedValue = otherInclude.value
|
||||
guard type(of: thisWrappedValue) == type(of: otherWrappedValue) else {
|
||||
return .differentTypes(
|
||||
String(describing: type(of: thisWrappedValue)),
|
||||
String(describing: type(of: otherWrappedValue))
|
||||
)
|
||||
}
|
||||
|
||||
let thisAsAResource = thisWrappedValue as? AbstractResourceObjectType
|
||||
|
||||
let maybeComparison = thisAsAResource
|
||||
.flatMap { resource in
|
||||
try? ArrayElementComparison(
|
||||
resourceObjectComparison: resource.abstractCompare(to: otherWrappedValue)
|
||||
)
|
||||
}
|
||||
|
||||
guard let comparison = maybeComparison else {
|
||||
return .differentValues(
|
||||
String(describing: thisWrappedValue),
|
||||
String(describing: otherWrappedValue)
|
||||
)
|
||||
}
|
||||
|
||||
return comparison
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/3/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
extension Relationships {
|
||||
public func compare(to other: Self) -> [String: Comparison] {
|
||||
let mirror1 = Mirror(reflecting: self)
|
||||
let mirror2 = Mirror(reflecting: other)
|
||||
|
||||
var comparisons = [String: Comparison]()
|
||||
|
||||
for child in mirror1.children {
|
||||
guard let childLabel = child.label else { continue }
|
||||
|
||||
let childDescription = relationshipDescription(of: child.value)
|
||||
|
||||
guard let otherChild = mirror2.children.first(where: { $0.label == childLabel }) else {
|
||||
comparisons[childLabel] = .different(childDescription, "missing")
|
||||
continue
|
||||
}
|
||||
|
||||
if (relationshipsEqual(child.value, otherChild.value)) {
|
||||
comparisons[childLabel] = .same
|
||||
} else {
|
||||
let otherChildDescription = relationshipDescription(of: otherChild.value)
|
||||
|
||||
comparisons[childLabel] = .different(childDescription, otherChildDescription)
|
||||
}
|
||||
}
|
||||
|
||||
return comparisons
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func relationshipsEqual(_ one: Any, _ two: Any) -> Bool {
|
||||
guard let attr = one as? AbstractRelationship else {
|
||||
return false
|
||||
}
|
||||
|
||||
return attr.equals(two)
|
||||
}
|
||||
|
||||
fileprivate func relationshipDescription(of thing: Any) -> String {
|
||||
return (thing as? AbstractRelationship)?.abstractDescription ?? String(describing: thing)
|
||||
}
|
||||
|
||||
protocol AbstractRelationship {
|
||||
var abstractDescription: String { get }
|
||||
|
||||
func equals(_ other: Any) -> Bool
|
||||
}
|
||||
|
||||
extension ToOneRelationship: AbstractRelationship {
|
||||
var abstractDescription: String {
|
||||
if meta is NoMetadata && links is NoLinks {
|
||||
return String(describing: id)
|
||||
}
|
||||
|
||||
return String(describing:
|
||||
(
|
||||
String(describing: id),
|
||||
String(describing: meta),
|
||||
String(describing: links)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func equals(_ other: Any) -> Bool {
|
||||
guard let attributeB = other as? Self else {
|
||||
return false
|
||||
}
|
||||
return abstractDescription == attributeB.abstractDescription
|
||||
}
|
||||
}
|
||||
|
||||
extension ToManyRelationship: AbstractRelationship {
|
||||
var abstractDescription: String {
|
||||
|
||||
let idsString = ids.map { String.init(describing: $0.rawValue) }.joined(separator: ", ")
|
||||
|
||||
if meta is NoMetadata && links is NoLinks {
|
||||
return idsString
|
||||
}
|
||||
|
||||
return String(describing:
|
||||
(
|
||||
idsString,
|
||||
String(describing: meta),
|
||||
String(describing: links)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func equals(_ other: Any) -> Bool {
|
||||
guard let attributeB = other as? Self else {
|
||||
return false
|
||||
}
|
||||
return abstractDescription == attributeB.abstractDescription
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// ResourceObjectCompare.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/3/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
public struct ResourceObjectComparison: Equatable, PropertyComparable {
|
||||
public typealias ComparisonHash = [String: Comparison]
|
||||
|
||||
public let id: Comparison
|
||||
public let attributes: ComparisonHash
|
||||
public let relationships: ComparisonHash
|
||||
public let meta: Comparison
|
||||
public let links: Comparison
|
||||
|
||||
public init<T: ResourceObjectType>(_ one: T, _ two: T) {
|
||||
id = Comparison(one.id.rawValue, two.id.rawValue)
|
||||
attributes = one.attributes.compare(to: two.attributes)
|
||||
relationships = one.relationships.compare(to: two.relationships)
|
||||
meta = Comparison(one.meta, two.meta)
|
||||
links = Comparison(one.links, two.links)
|
||||
}
|
||||
|
||||
public var differences: NamedDifferences {
|
||||
return attributes.reduce(into: ComparisonHash()) { hash, next in
|
||||
hash["'\(next.key)' attribute"] = next.value
|
||||
}
|
||||
.merging(
|
||||
relationships.reduce(into: ComparisonHash()) { hash, next in
|
||||
hash["'\(next.key)' relationship"] = next.value
|
||||
},
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
.merging(
|
||||
[
|
||||
"id": id,
|
||||
"meta": meta,
|
||||
"links": links
|
||||
],
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
.filter { $1 != .same }
|
||||
.mapValues { $0.rawValue }
|
||||
}
|
||||
}
|
||||
|
||||
extension ResourceObjectType {
|
||||
public func compare(to other: Self) -> ResourceObjectComparison {
|
||||
return ResourceObjectComparison(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
protocol AbstractResourceObjectType {
|
||||
func abstractCompare(to other: Any) throws -> ResourceObjectComparison
|
||||
}
|
||||
|
||||
enum AbstractCompareError: Swift.Error {
|
||||
case typeMismatch
|
||||
}
|
||||
|
||||
extension ResourceObject: AbstractResourceObjectType {
|
||||
func abstractCompare(to other: Any) throws -> ResourceObjectComparison {
|
||||
guard let otherResource = other as? Self else {
|
||||
throw AbstractCompareError.typeMismatch
|
||||
}
|
||||
return self.compare(to: otherResource)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Optional+ZipWith.swift
|
||||
//
|
||||
// Created by Mathew Polzin on 1/19/19.
|
||||
//
|
||||
|
||||
/// Zip two optionals together with the given operation performed on
|
||||
/// the unwrapped contents. If either optional is nil, the zip
|
||||
/// yields nil.
|
||||
func zip<X, Y, Z>(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? {
|
||||
return left.flatMap { lft in right.map { rght in fn(lft, rght) }}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/3/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
import JSONAPITesting
|
||||
|
||||
final class AttributesCompareTests: XCTestCase {
|
||||
func test_sameAttributes() {
|
||||
let attr1 = TestAttributes(
|
||||
string: "hello world",
|
||||
int: 10,
|
||||
bool: true,
|
||||
double: 105.4,
|
||||
struct: .init(value: .init())
|
||||
)
|
||||
let attr2 = attr1
|
||||
|
||||
XCTAssertEqual(attr1.compare(to: attr2), [
|
||||
"string": .same,
|
||||
"int": .same,
|
||||
"bool": .same,
|
||||
"double": .same,
|
||||
"struct": .same
|
||||
])
|
||||
}
|
||||
|
||||
func test_differentAttributes() {
|
||||
let attr1 = TestAttributes(
|
||||
string: "hello world",
|
||||
int: 10,
|
||||
bool: true,
|
||||
double: 105.4,
|
||||
struct: .init(value: .init())
|
||||
)
|
||||
let attr2 = TestAttributes(
|
||||
string: "hello",
|
||||
int: 11,
|
||||
bool: false,
|
||||
double: 1.4,
|
||||
struct: .init(value: .init(val: "there"))
|
||||
)
|
||||
|
||||
XCTAssertEqual(attr1.compare(to: attr2), [
|
||||
"string": .different("hello world", "hello"),
|
||||
"int": .different("10", "11"),
|
||||
"bool": .different("true", "false"),
|
||||
"double": .different("105.4", "1.4"),
|
||||
"struct": .different("string: hello", "string: there")
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
private struct TestAttributes: JSONAPI.Attributes {
|
||||
let string: Attribute<String>
|
||||
let int: Attribute<Int>
|
||||
let bool: Attribute<Bool>
|
||||
let double: Attribute<Double>
|
||||
let `struct`: Attribute<Struct>
|
||||
|
||||
struct Struct: Equatable, Codable, CustomStringConvertible {
|
||||
let string: String
|
||||
|
||||
init(val: String = "hello") {
|
||||
self.string = val
|
||||
}
|
||||
|
||||
var description: String { return "string: \(string)" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// DocumentCompareTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/4/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
import JSONAPITesting
|
||||
|
||||
final class DocumentCompareTests: XCTestCase {
|
||||
func test_same() {
|
||||
XCTAssertTrue(d1.compare(to: d1).differences.isEmpty)
|
||||
XCTAssertTrue(d2.compare(to: d2).differences.isEmpty)
|
||||
XCTAssertTrue(d3.compare(to: d3).differences.isEmpty)
|
||||
XCTAssertTrue(d4.compare(to: d4).differences.isEmpty)
|
||||
}
|
||||
|
||||
func test_errorAndData() {
|
||||
XCTAssertEqual(d1.compare(to: d2).differences, [
|
||||
"Body": "data response ≠ error response"
|
||||
])
|
||||
|
||||
XCTAssertEqual(d2.compare(to: d1).differences, [
|
||||
"Body": "error response ≠ data response"
|
||||
])
|
||||
}
|
||||
|
||||
func test_differentErrors() {
|
||||
XCTAssertEqual(d2.compare(to: d4).differences, [
|
||||
"Body": "status: 500, title: Internal Error ≠ status: 404, title: Not Found"
|
||||
])
|
||||
}
|
||||
|
||||
func test_differentData() {
|
||||
XCTAssertEqual(d3.compare(to: d5).differences, [
|
||||
"Body": "(Includes: (include 2: missing)), (Primary Resource: (resource 2: missing))"
|
||||
])
|
||||
|
||||
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))"##
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum TestDescription: JSONAPI.ResourceObjectDescription {
|
||||
static let jsonType: String = "test_type"
|
||||
|
||||
struct Attributes: JSONAPI.Attributes {
|
||||
let name: Attribute<String>
|
||||
let age: Attribute<Int>
|
||||
let favoriteColor: Attribute<String?>
|
||||
}
|
||||
|
||||
struct Relationships: JSONAPI.Relationships {
|
||||
let bestFriend: ToOneRelationship<TestType2?, NoMetadata, NoLinks>
|
||||
let parents: ToManyRelationship<TestType, NoMetadata, NoLinks>
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate typealias TestType = ResourceObject<TestDescription, NoMetadata, NoLinks, String>
|
||||
|
||||
fileprivate enum TestDescription2: JSONAPI.ResourceObjectDescription {
|
||||
static let jsonType: String = "test_type2"
|
||||
|
||||
struct Attributes: JSONAPI.Attributes {
|
||||
let name: Attribute<String>
|
||||
let age: Attribute<Int>
|
||||
let favoriteColor: Attribute<String?>
|
||||
}
|
||||
|
||||
struct Relationships: JSONAPI.Relationships {
|
||||
let bestFriend: ToOneRelationship<TestType2?, NoMetadata, NoLinks>
|
||||
let parents: ToManyRelationship<TestType2, NoMetadata, NoLinks>
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate typealias TestType2 = ResourceObject<TestDescription2, NoMetadata, NoLinks, String>
|
||||
|
||||
fileprivate typealias SingleDocument = 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(
|
||||
id: "1",
|
||||
attributes: .init(
|
||||
name: "name",
|
||||
age: 10,
|
||||
favoriteColor: nil
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: "2",
|
||||
parents: ["3", "4"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let r2 = TestType(
|
||||
id: "5",
|
||||
attributes: .init(
|
||||
name: "Fig",
|
||||
age: 12,
|
||||
favoriteColor: "blue"
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: ["3", "4"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let r3 = TestType2(
|
||||
id: "2",
|
||||
attributes: .init(
|
||||
name: "Tully",
|
||||
age: 100,
|
||||
favoriteColor: "red"
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: []
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d1 = SingleDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObject: r1),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d2 = SingleDocument(
|
||||
apiDescription: .none,
|
||||
errors: [.error(.init(id: nil, status: "500", title: "Internal Error"))]
|
||||
)
|
||||
|
||||
fileprivate let d3 = ManyDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObjects: [r1, r2]),
|
||||
includes: .init(values: [.init(r3)]),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d4 = SingleDocument(
|
||||
apiDescription: .none,
|
||||
errors: [.error(.init(id: nil, status: "404", title: "Not Found"))]
|
||||
)
|
||||
|
||||
fileprivate let d5 = ManyDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObjects: [r1]),
|
||||
includes: .init(values: [.init(r3), .init(r2)]),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
|
||||
fileprivate let d6 = ManyDocument(
|
||||
apiDescription: .none,
|
||||
body: .init(resourceObjects: [r1, r1, r2]),
|
||||
includes: .init(values: [.init(r3), .init(r2)]),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
@@ -0,0 +1,239 @@
|
||||
//
|
||||
// IncludeCompareTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/4/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
import JSONAPITesting
|
||||
import Poly
|
||||
|
||||
final class IncludesCompareTests: XCTestCase {
|
||||
func test_same() {
|
||||
let includes1 = Includes(values: justTypeOnes)
|
||||
let includes2 = Includes(values: justTypeOnes)
|
||||
XCTAssertTrue(includes1.compare(to: includes2).differences.isEmpty)
|
||||
|
||||
let includes3 = Includes(values: longerTypeOnes)
|
||||
let includes4 = Includes(values: longerTypeOnes)
|
||||
XCTAssertTrue(includes3.compare(to: includes4).differences.isEmpty)
|
||||
|
||||
let includes5 = Includes(values: onesAndTwos)
|
||||
let includes6 = Includes(values: onesAndTwos)
|
||||
XCTAssertTrue(includes5.compare(to: includes6).differences.isEmpty)
|
||||
}
|
||||
|
||||
func test_missing() {
|
||||
let includes1 = Includes(values: justTypeOnes)
|
||||
let includes2 = Includes(values: longerTypeOnes)
|
||||
XCTAssertEqual(includes1.compare(to: includes2).differences, ["include 3": "missing"])
|
||||
XCTAssertEqual(includes2.compare(to: includes1).differences, ["include 3": "missing"])
|
||||
}
|
||||
|
||||
func test_typeMismatch() {
|
||||
let includes1 = Includes(values: onesAndTwos)
|
||||
let includes2 = Includes(values: justTypeOnes)
|
||||
XCTAssertEqual(includes1.compare(to: includes2).differences, ["include 2": "ResourceObject<TestDescription2, NoMetadata, NoLinks, String> ≠ ResourceObject<TestDescription1, NoMetadata, NoLinks, String>"])
|
||||
XCTAssertEqual(includes2.compare(to: includes1).differences, ["include 2": "ResourceObject<TestDescription1, NoMetadata, NoLinks, String> ≠ ResourceObject<TestDescription2, NoMetadata, NoLinks, String>"])
|
||||
}
|
||||
|
||||
func test_valueMismatch() {
|
||||
let includes1 = Includes(values: onesAndTwos)
|
||||
let includes2 = Includes(values: differentOnesAndTwos)
|
||||
XCTAssertEqual(includes1.compare(to: includes2).differences, [
|
||||
"include 1": #"'favoriteColor' attribute: Optional("red") ≠ nil, 'name' attribute: Matt ≠ Todd, 'parents' relationship: 4, 5 ≠ 7, 8, id: 1 ≠ 2"#
|
||||
])
|
||||
}
|
||||
|
||||
fileprivate let justTypeOnes: [Poly2<TestType1, TestType2>] = [
|
||||
.a(
|
||||
TestType1(
|
||||
id: "1",
|
||||
attributes: .init(
|
||||
name: "Matt",
|
||||
age: 23,
|
||||
favoriteColor: "red"
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: "3",
|
||||
parents: ["4", "5"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
),
|
||||
.a(
|
||||
TestType1(
|
||||
id: "3",
|
||||
attributes: .init(
|
||||
name: "Helen",
|
||||
age: 24,
|
||||
favoriteColor: nil
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: ["2"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
fileprivate let longerTypeOnes: [Poly2<TestType1, TestType2>] = [
|
||||
.a(
|
||||
TestType1(
|
||||
id: "1",
|
||||
attributes: .init(
|
||||
name: "Matt",
|
||||
age: 23,
|
||||
favoriteColor: "red"
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: "3",
|
||||
parents: ["4", "5"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
),
|
||||
.a(
|
||||
TestType1(
|
||||
id: "3",
|
||||
attributes: .init(
|
||||
name: "Helen",
|
||||
age: 24,
|
||||
favoriteColor: nil
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: ["2"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
),
|
||||
.a(
|
||||
TestType1(
|
||||
id: "2",
|
||||
attributes: .init(
|
||||
name: "Troy",
|
||||
age: 45,
|
||||
favoriteColor: "blue"
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: []
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
fileprivate let onesAndTwos: [Poly2<TestType1, TestType2>] = [
|
||||
.a(
|
||||
TestType1(
|
||||
id: "1",
|
||||
attributes: .init(
|
||||
name: "Matt",
|
||||
age: 23,
|
||||
favoriteColor: "red"
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: "3",
|
||||
parents: ["4", "5"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
),
|
||||
.b(
|
||||
TestType2(
|
||||
id: "1",
|
||||
attributes: .init(
|
||||
name: "Lucy",
|
||||
age: 33,
|
||||
favoriteColor: nil
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: []
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
fileprivate let differentOnesAndTwos: [Poly2<TestType1, TestType2>] = [
|
||||
.a(
|
||||
TestType1(
|
||||
id: "2",
|
||||
attributes: .init(
|
||||
name: "Todd",
|
||||
age: 23,
|
||||
favoriteColor: nil
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: "3",
|
||||
parents: ["7", "8"]
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
),
|
||||
.b(
|
||||
TestType2(
|
||||
id: "1",
|
||||
attributes: .init(
|
||||
name: "Lucy",
|
||||
age: 33,
|
||||
favoriteColor: nil
|
||||
),
|
||||
relationships: .init(
|
||||
bestFriend: nil,
|
||||
parents: []
|
||||
),
|
||||
meta: .none,
|
||||
links: .none
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
private enum TestDescription1: JSONAPI.ResourceObjectDescription {
|
||||
static let jsonType: String = "test_type1"
|
||||
|
||||
struct Attributes: JSONAPI.Attributes {
|
||||
let name: Attribute<String>
|
||||
let age: Attribute<Int>
|
||||
let favoriteColor: Attribute<String?>
|
||||
}
|
||||
|
||||
struct Relationships: JSONAPI.Relationships {
|
||||
let bestFriend: ToOneRelationship<TestType1?, NoMetadata, NoLinks>
|
||||
let parents: ToManyRelationship<TestType1, NoMetadata, NoLinks>
|
||||
}
|
||||
}
|
||||
|
||||
private typealias TestType1 = ResourceObject<TestDescription1, NoMetadata, NoLinks, String>
|
||||
|
||||
private enum TestDescription2: JSONAPI.ResourceObjectDescription {
|
||||
static let jsonType: String = "test_type2"
|
||||
|
||||
struct Attributes: JSONAPI.Attributes {
|
||||
let name: Attribute<String>
|
||||
let age: Attribute<Int>
|
||||
let favoriteColor: Attribute<String?>
|
||||
}
|
||||
|
||||
struct Relationships: JSONAPI.Relationships {
|
||||
let bestFriend: ToOneRelationship<TestType2?, NoMetadata, NoLinks>
|
||||
let parents: ToManyRelationship<TestType2, NoMetadata, NoLinks>
|
||||
}
|
||||
}
|
||||
|
||||
private typealias TestType2 = ResourceObject<TestDescription2, NoMetadata, NoLinks, String>
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 11/5/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
import JSONAPITesting
|
||||
|
||||
final class RelationshipsCompareTests: XCTestCase {
|
||||
// TODO: write tests
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user