merge and fix conflicts

This commit is contained in:
Mathew Polzin
2019-11-05 23:00:18 -08:00
32 changed files with 1855 additions and 342 deletions
+2 -2
View File
@@ -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
View File
@@ -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(
+1 -3
View File
@@ -332,13 +332,11 @@ As of Swift 5.1, `Attributes` can be accessed via dynamic member keypath lookup
let favoriteColor: String = person.favoriteColor
```
🗒 `Attributes` can also be accessed via the older `subscript` operator as follows:
:warning: `Attributes` can also be accessed via the older `subscript` operator, but this is a deprecated feature that will be removed in the next major version:
```swift
let favoriteColor: String = person[\.favoriteColor]
```
In both cases you retain type-safety. It is best practice to pick an attribute access syntax and stick with it. At some point in the future the syntax deemed less desirable may be deprecated.
#### `Transformer`
Sometimes you need to use a type that does not encode or decode itself in the way you need to represent it as a serialized JSON object. For example, the Swift `Foundation` type `Date` can encode/decode itself to `Double` out of the box, but you might want to represent dates as ISO 8601 compliant `String`s instead. The Foundation library `JSONDecoder` has a setting to make this adjustment, but for the sake of an example, you could create a `Transformer`.
+16 -11
View File
@@ -91,16 +91,26 @@ 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
/// compliant Document.
public protocol CodableJSONAPIDocument: EncodableJSONAPIDocument, Decodable where PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: Decodable {}
public protocol CodableJSONAPIDocument: EncodableJSONAPIDocument, Decodable where PrimaryResourceBody: JSONAPI.CodableResourceBody, IncludeType: Decodable {}
/// 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,
@@ -340,7 +345,7 @@ extension Document {
}
}
extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootCodingKeys.self)
@@ -557,7 +562,7 @@ extension Document {
}
extension Document.ErrorDocument: Decodable, CodableJSONAPIDocument
where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
@@ -570,7 +575,7 @@ extension Document.ErrorDocument: Decodable, CodableJSONAPIDocument
}
extension Document.SuccessDocument: Decodable, CodableJSONAPIDocument
where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
where PrimaryResourceBody: CodableResourceBody, IncludeType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
+3 -3
View File
@@ -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
+10 -12
View File
@@ -10,33 +10,31 @@
/// array should be used for no results).
public protocol OptionalEncodablePrimaryResource: Equatable, Encodable {}
/// An `EncodablePrimaryResource` is a `PrimaryResource` that only supports encoding.
/// This is actually more restrictave than `PrimaryResource`, which supports both encoding and
/// decoding.
/// An `EncodablePrimaryResource` is a `CodablePrimaryResource` that only supports encoding.
public protocol EncodablePrimaryResource: OptionalEncodablePrimaryResource {}
/// This protocol allows for `SingleResourceBody` to contain a `null`
/// data object where `ManyResourceBody` cannot (because an empty
/// array should be used for no results).
public protocol OptionalPrimaryResource: OptionalEncodablePrimaryResource, Decodable {}
public protocol OptionalCodablePrimaryResource: OptionalEncodablePrimaryResource, Decodable {}
/// A `PrimaryResource` is a type that can be used in the body of a JSON API
/// A `CodablePrimaryResource` is a type that can be used in the body of a JSON API
/// document as the primary resource.
public protocol PrimaryResource: EncodablePrimaryResource, OptionalPrimaryResource {}
public protocol CodablePrimaryResource: EncodablePrimaryResource, OptionalCodablePrimaryResource {}
extension Optional: OptionalEncodablePrimaryResource where Wrapped: EncodablePrimaryResource {}
extension Optional: OptionalPrimaryResource where Wrapped: PrimaryResource {}
extension Optional: OptionalCodablePrimaryResource where Wrapped: CodablePrimaryResource {}
/// 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 {}
/// A ResourceBody is a representation of the body of the JSON API Document.
/// 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)
/// or it can contain many resources (and array with zero or more entries).
public protocol ResourceBody: Decodable, EncodableResourceBody {}
public protocol CodableResourceBody: Decodable, EncodableResourceBody {}
/// A `ResourceBody` that has the ability to take on more primary
/// resources by appending another similarly typed `ResourceBody`.
@@ -74,7 +72,7 @@ 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: ResourceBody {
public struct NoResourceBody: CodableResourceBody {
public static var none: NoResourceBody { return NoResourceBody() }
}
@@ -94,7 +92,7 @@ extension SingleResourceBody {
}
}
extension SingleResourceBody: Decodable, ResourceBody where Entity: OptionalPrimaryResource {
extension SingleResourceBody: Decodable, CodableResourceBody where Entity: OptionalCodablePrimaryResource {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
@@ -119,7 +117,7 @@ extension ManyResourceBody {
}
}
extension ManyResourceBody: Decodable, ResourceBody where Entity: PrimaryResource {
extension ManyResourceBody: Decodable, CodableResourceBody where Entity: CodablePrimaryResource {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var valueAggregator = [Entity]()
@@ -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?
@@ -64,6 +64,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 {
+4 -4
View File
@@ -6,17 +6,17 @@
//
/// Conform a type to this protocol to indicate it can be encoded to or decoded from
/// the meta data attached to a component of a JSON API document. Different meta data
/// the meta data attached to a component of a JSON:API document. Different meta data
/// can be stored all over the place: On the root document, on a resource object, on
/// link objects, etc.
///
/// JSON API Metadata is totally open ended. It can take whatever JSON-compliant structure
/// JSON:API Metadata is totally open ended. It can take whatever JSON-compliant structure
/// the server and client agree upon.
public protocol Meta: Codable, Equatable {
}
// We make Optional a Meta if it wraps a Meta so that Metadata can be specified as
// nullable.
// We make Optional a Meta if it wraps a Meta so that
// Metadata can be specified as nullable.
extension Optional: Meta where Wrapped: Meta {}
/// Use this type when you want to specify not to encode or decode any metadata
@@ -20,7 +20,7 @@ public typealias EncodableJSONPoly = Poly & EncodablePrimaryResource
public typealias EncodablePolyWrapped = Encodable & Equatable
public typealias PolyWrapped = EncodablePolyWrapped & Decodable
extension Poly0: PrimaryResource {
extension Poly0: CodablePrimaryResource {
public init(from decoder: Decoder) throws {
throw JSONAPIEncodingError.illegalDecoding("Attempted to decode Poly0, which should represent a thing that is not expected to be found in a document.")
}
@@ -34,42 +34,42 @@ extension Poly0: PrimaryResource {
extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource
where A: EncodablePolyWrapped {}
extension Poly1: PrimaryResource, OptionalPrimaryResource
extension Poly1: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped {}
// MARK: - 2 types
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource
where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
extension Poly2: PrimaryResource, OptionalPrimaryResource
extension Poly2: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped {}
// MARK: - 3 types
extension Poly3: EncodablePrimaryResource, OptionalEncodablePrimaryResource
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped {}
extension Poly3: PrimaryResource, OptionalPrimaryResource
extension Poly3: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
// MARK: - 4 types
extension Poly4: EncodablePrimaryResource, OptionalEncodablePrimaryResource
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped {}
extension Poly4: PrimaryResource, OptionalPrimaryResource
extension Poly4: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
// MARK: - 5 types
extension Poly5: EncodablePrimaryResource, OptionalEncodablePrimaryResource
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped {}
extension Poly5: PrimaryResource, OptionalPrimaryResource
extension Poly5: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
// MARK: - 6 types
extension Poly6: EncodablePrimaryResource, OptionalEncodablePrimaryResource
where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped {}
extension Poly6: PrimaryResource, OptionalPrimaryResource
extension Poly6: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
// MARK: - 7 types
@@ -83,7 +83,7 @@ extension Poly7: EncodablePrimaryResource, OptionalEncodablePrimaryResource
F: EncodablePolyWrapped,
G: EncodablePolyWrapped {}
extension Poly7: PrimaryResource, OptionalPrimaryResource
extension Poly7: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
// MARK: - 8 types
@@ -98,7 +98,7 @@ extension Poly8: EncodablePrimaryResource, OptionalEncodablePrimaryResource
G: EncodablePolyWrapped,
H: EncodablePolyWrapped {}
extension Poly8: PrimaryResource, OptionalPrimaryResource
extension Poly8: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
// MARK: - 9 types
@@ -114,7 +114,7 @@ extension Poly9: EncodablePrimaryResource, OptionalEncodablePrimaryResource
H: EncodablePolyWrapped,
I: EncodablePolyWrapped {}
extension Poly9: PrimaryResource, OptionalPrimaryResource
extension Poly9: CodablePrimaryResource, OptionalCodablePrimaryResource
where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
// MARK: - 10 types
@@ -131,7 +131,7 @@ extension Poly10: EncodablePrimaryResource, OptionalEncodablePrimaryResource
I: EncodablePolyWrapped,
J: EncodablePolyWrapped {}
extension Poly10: PrimaryResource, OptionalPrimaryResource
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 {}
// MARK: - 11 types
@@ -149,7 +149,7 @@ extension Poly11: EncodablePrimaryResource, OptionalEncodablePrimaryResource
J: EncodablePolyWrapped,
K: EncodablePolyWrapped {}
extension Poly11: PrimaryResource, OptionalPrimaryResource
extension Poly11: CodablePrimaryResource, OptionalCodablePrimaryResource
where
A: PolyWrapped,
B: PolyWrapped,
@@ -99,9 +99,15 @@ extension ResourceObjectProxy {
/// ResourceObjectType is the protocol that ResourceObject conforms to. This
/// protocol lets other types accept any ResourceObject as a generic
/// specialization.
public protocol ResourceObjectType: ResourceObjectProxy, PrimaryResource where Description: ResourceObjectDescription {
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 {}
@@ -173,236 +179,6 @@ extension ResourceObject where EntityRawIdType == Unidentified {
}
}
/*
extension ResourceObject where Description.Attributes == NoAttributes {
public init(id: ResourceObject.Id, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.init(id: id, attributes: NoAttributes(), relationships: relationships, meta: meta, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, MetaType == NoMetadata {
public init(id: ResourceObject.Id, relationships: Description.Relationships, links: LinksType) {
self.init(id: id, relationships: relationships, meta: .none, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, LinksType == NoLinks {
public init(id: ResourceObject.Id, relationships: Description.Relationships, meta: MetaType) {
self.init(id: id, relationships: relationships, meta: meta, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, MetaType == NoMetadata, LinksType == NoLinks {
public init(id: ResourceObject.Id, relationships: Description.Relationships) {
self.init(id: id, relationships: relationships, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, EntityRawIdType: CreatableRawIdType {
public init(relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.init(attributes: NoAttributes(), relationships: relationships, meta: meta, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, MetaType == NoMetadata, EntityRawIdType: CreatableRawIdType {
public init(relationships: Description.Relationships, links: LinksType) {
self.init(attributes: NoAttributes(), relationships: relationships, meta: .none, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(relationships: Description.Relationships, meta: MetaType) {
self.init(attributes: NoAttributes(), relationships: relationships, meta: meta, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(relationships: Description.Relationships) {
self.init(attributes: NoAttributes(), relationships: relationships, meta: .none, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, EntityRawIdType == Unidentified {
public init(relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.init(attributes: NoAttributes(), relationships: relationships, meta: meta, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships {
public init(id: ResourceObject.Id, attributes: Description.Attributes, meta: MetaType, links: LinksType) {
self.init(id: id, attributes: attributes, relationships: NoRelationships(), meta: meta, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, MetaType == NoMetadata {
public init(id: ResourceObject.Id, attributes: Description.Attributes, links: LinksType) {
self.init(id: id, attributes: attributes, meta: .none, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, LinksType == NoLinks {
public init(id: ResourceObject.Id, attributes: Description.Attributes, meta: MetaType) {
self.init(id: id, attributes: attributes, meta: meta, links: .none)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, MetaType == NoMetadata, LinksType == NoLinks {
public init(id: ResourceObject.Id, attributes: Description.Attributes) {
self.init(id: id, attributes: attributes, meta: .none, links: .none)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, meta: MetaType, links: LinksType) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: meta, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, MetaType == NoMetadata, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, links: LinksType) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: .none, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, meta: MetaType) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: meta, links: .none)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: .none, links: .none)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, meta: MetaType, links: LinksType) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: meta, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, MetaType == NoMetadata, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, links: LinksType) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: .none, links: links)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, LinksType == NoLinks, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, meta: MetaType) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: meta, links: .none)
}
}
extension ResourceObject where Description.Relationships == NoRelationships, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes) {
self.init(attributes: attributes, relationships: NoRelationships(), meta: .none, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships {
public init(id: ResourceObject.Id, meta: MetaType, links: LinksType) {
self.init(id: id, attributes: NoAttributes(), relationships: NoRelationships(), meta: meta, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, MetaType == NoMetadata {
public init(id: ResourceObject.Id, links: LinksType) {
self.init(id: id, attributes: NoAttributes(), relationships: NoRelationships(), meta: .none, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, LinksType == NoLinks {
public init(id: ResourceObject.Id, meta: MetaType) {
self.init(id: id, attributes: NoAttributes(), relationships: NoRelationships(), meta: meta, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, MetaType == NoMetadata, LinksType == NoLinks {
public init(id: ResourceObject.Id) {
self.init(id: id, attributes: NoAttributes(), relationships: NoRelationships(), meta: .none, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, EntityRawIdType: CreatableRawIdType {
public init(meta: MetaType, links: LinksType) {
self.init(attributes: NoAttributes(), relationships: NoRelationships(), meta: meta, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, MetaType == NoMetadata, EntityRawIdType: CreatableRawIdType {
public init(links: LinksType) {
self.init(attributes: NoAttributes(), relationships: NoRelationships(), meta: .none, links: links)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(meta: MetaType) {
self.init(attributes: NoAttributes(), relationships: NoRelationships(), meta: meta, links: .none)
}
}
extension ResourceObject where Description.Attributes == NoAttributes, Description.Relationships == NoRelationships, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init() {
self.init(attributes: NoAttributes(), relationships: NoRelationships(), meta: .none, links: .none)
}
}
extension ResourceObject where MetaType == NoMetadata {
public init(id: ResourceObject.Id, attributes: Description.Attributes, relationships: Description.Relationships, links: LinksType) {
self.init(id: id, attributes: attributes, relationships: relationships, meta: .none, links: links)
}
}
extension ResourceObject where MetaType == NoMetadata, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, relationships: Description.Relationships, links: LinksType) {
self.init(attributes: attributes, relationships: relationships, meta: .none, links: links)
}
}
extension ResourceObject where MetaType == NoMetadata, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships, links: LinksType) {
self.init(attributes: attributes, relationships: relationships, meta: .none, links: links)
}
}
extension ResourceObject where LinksType == NoLinks {
public init(id: ResourceObject.Id, attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType) {
self.init(id: id, attributes: attributes, relationships: relationships, meta: meta, links: .none)
}
}
extension ResourceObject where LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType) {
self.init(attributes: attributes, relationships: relationships, meta: meta, links: .none)
}
}
extension ResourceObject where LinksType == NoLinks, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType) {
self.init(attributes: attributes, relationships: relationships, meta: meta, links: .none)
}
}
extension ResourceObject where MetaType == NoMetadata, LinksType == NoLinks {
public init(id: ResourceObject.Id, attributes: Description.Attributes, relationships: Description.Relationships) {
self.init(id: id, attributes: attributes, relationships: relationships, meta: .none, links: .none)
}
}
extension ResourceObject where MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, relationships: Description.Relationships) {
self.init(attributes: attributes, relationships: relationships, meta: .none, links: .none)
}
}
extension ResourceObject where MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships) {
self.init(attributes: attributes, relationships: relationships, meta: .none, links: .none)
}
}
*/
// MARK: - Pointer for Relationships use
public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
@@ -456,6 +232,7 @@ public extension ResourceObjectProxy {
/// Access the attribute at the given keypath. This just
/// allows you to write `resourceObject[\.propertyName]` instead
/// of `resourceObject.attributes.propertyName.value`.
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T>) -> T.ValueType {
return attributes[keyPath: path].value
}
@@ -463,6 +240,7 @@ public extension ResourceObjectProxy {
/// Access the attribute at the given keypath. This just
/// allows you to write `resourceObject[\.propertyName]` instead
/// of `resourceObject.attributes.propertyName.value`.
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T?>) -> T.ValueType? {
return attributes[keyPath: path]?.value
}
@@ -470,6 +248,7 @@ public extension ResourceObjectProxy {
/// Access the attribute at the given keypath. This just
/// allows you to write `resourceObject[\.propertyName]` instead
/// of `resourceObject.attributes.propertyName.value`.
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
subscript<T: AttributeType, U>(_ path: KeyPath<Description.Attributes, T?>) -> U? where T.ValueType == U? {
// Implementation Note: Handles Transform that returns optional
// type.
@@ -517,6 +296,7 @@ public extension ResourceObjectProxy {
// MARK: Keypath Subscript Lookup
/// Access an attribute requiring a transformation on the RawValue _and_
/// a secondary transformation on this entity (self).
@available(*, deprecated, message: "This will be removed in a future version in favor of `resource.<attribute_name>` (dynamic member lookup)")
subscript<T>(_ path: KeyPath<Description.Attributes, (Self) -> T>) -> T {
return attributes[keyPath: path](self)
}
@@ -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) }}
}

Some files were not shown because too many files have changed in this diff Show More