Skip to content

Commit

Permalink
Merge pull request #46 from mattpolzin/feature/testing-comparisons
Browse files Browse the repository at this point in the history
Feature/testing comparisons
  • Loading branch information
mattpolzin authored Nov 6, 2019
2 parents c24b70b + adcc6bf commit 83233a7
Show file tree
Hide file tree
Showing 22 changed files with 1,477 additions and 15 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
19 changes: 12 additions & 7 deletions Sources/JSONAPI/Document/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions Sources/JSONAPI/Document/Includes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion Sources/JSONAPI/Error/BasicJSONAPIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion Sources/JSONAPI/Error/GenericJSONAPIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
78 changes: 78 additions & 0 deletions Sources/JSONAPITesting/Comparisons/ArrayCompare.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
78 changes: 78 additions & 0 deletions Sources/JSONAPITesting/Comparisons/AttributesCompare.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
68 changes: 68 additions & 0 deletions Sources/JSONAPITesting/Comparisons/Comparison.swift
Original file line number Diff line number Diff line change
@@ -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 }
}
Loading

0 comments on commit 83233a7

Please sign in to comment.