Compare commits

..

11 Commits

Author SHA1 Message Date
Mathew Polzin 440b649577 Merge pull request #49 from mattpolzin/alpha/3x
Alpha/3x
2019-11-12 18:38:31 -08:00
Mathew Polzin 54551617b4 Add more errors for the resource object type property 2019-11-12 18:34:34 -08:00
Mathew Polzin 8c3a82ec23 Merge pull request #53 from mattpolzin/feature/better-error-messages
Feature/better error messages
2019-11-10 23:47:05 -08:00
Mathew Polzin e9a3b35dc7 improve error messages for poly include types 2019-11-10 23:43:35 -08:00
Mathew Polzin 2eecf95995 Adding Document Decoding Errors for some common problems 2019-11-10 23:02:26 -08:00
Mathew Polzin 4dc30ddc1c Rounding out the Resource Object errors 2019-11-10 20:46:35 -08:00
Mathew Polzin 455ff64326 update linuxmain 2019-11-09 00:34:39 -08:00
Mathew Polzin 0b4baf35d5 got some attribute cases added and tested. added some descriptions (custom string convertible) 2019-11-09 00:33:42 -08:00
Mathew Polzin 11ef050d58 most common relationship errors tested. 2019-11-08 18:47:28 -08:00
Mathew Polzin 86344ef93f trivial refactor in sparse fieldset file 2019-11-07 08:08:45 -08:00
Mathew Polzin 7fabe2574e rename some of the new public protocols in JSONAPITesting 2019-11-07 07:56:58 -08:00
31 changed files with 1521 additions and 111 deletions
+2 -2
View File
@@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/mattpolzin/Poly.git",
"state": {
"branch": null,
"revision": "18cd995be5c28c4dfdc1464e54ee0efb03e215bf",
"version": "2.3.0"
"revision": "0c9c08204142babc480938d704a23513d11420e5",
"version": "2.3.1"
}
}
]
+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.3.0")),
.package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.3.1")),
],
targets: [
.target(
+22 -8
View File
@@ -349,8 +349,8 @@ extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootCodingKeys.self)
if let noData = NoAPIDescription() as? APIDescription {
apiDescription = noData
if let noAPIDescription = NoAPIDescription() as? APIDescription {
apiDescription = noAPIDescription
} else {
apiDescription = try container.decode(APIDescription.self, forKey: .jsonapi)
}
@@ -389,18 +389,32 @@ extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody:
if let noData = NoResourceBody() as? PrimaryResourceBody {
data = noData
} else {
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
do {
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
} catch let error as ResourceObjectDecodingError {
throw DocumentDecodingError(error)
} catch let error as ManyResourceBodyDecodingError {
throw DocumentDecodingError(error)
} catch let error as DecodingError {
throw DocumentDecodingError(error)
?? error
}
}
let maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
let maybeIncludes: Includes<Include>?
do {
maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
} catch let error as IncludesDecodingError {
throw DocumentDecodingError(error)
}
// TODO come back to this and make robust
guard let metaVal = meta else {
throw JSONAPIEncodingError.missingOrMalformedMetadata
throw JSONAPICodingError.missingOrMalformedMetadata(path: decoder.codingPath)
}
guard let linksVal = links else {
throw JSONAPIEncodingError.missingOrMalformedLinks
throw JSONAPICodingError.missingOrMalformedLinks(path: decoder.codingPath)
}
body = .data(.init(primary: data, includes: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal))
@@ -569,7 +583,7 @@ extension Document.ErrorDocument: Decodable, CodableJSONAPIDocument
document = try container.decode(Document.self)
guard document.body.isError else {
throw JSONAPIDocumentDecodingError.foundSuccessDocumentWhenExpectingError
throw DocumentDecodingError.foundSuccessDocumentWhenExpectingError
}
}
}
@@ -582,7 +596,7 @@ extension Document.SuccessDocument: Decodable, CodableJSONAPIDocument
document = try container.decode(Document.self)
guard !document.body.isError else {
throw JSONAPIDocumentDecodingError.foundErrorDocumentWhenExpectingSuccess
throw DocumentDecodingError.foundErrorDocumentWhenExpectingSuccess
}
}
}
@@ -1,11 +1,73 @@
//
// DocumentDecodingErro.swift
// DocumentDecodingError.swift
//
//
// Created by Mathew Polzin on 10/20/19.
//
public enum JSONAPIDocumentDecodingError: Swift.Error {
public enum DocumentDecodingError: Swift.Error, Equatable {
case primaryResource(error: ResourceObjectDecodingError, idx: Int?)
case primaryResourceMissing
case primaryResourcesMissing
case includes(error: IncludesDecodingError)
case foundErrorDocumentWhenExpectingSuccess
case foundSuccessDocumentWhenExpectingError
init(_ decodingError: ResourceObjectDecodingError) {
self = .primaryResource(error: decodingError, idx: nil)
}
init(_ decodingError: ManyResourceBodyDecodingError) {
self = .primaryResource(error: decodingError.error, idx: decodingError.idx)
}
init(_ decodingError: IncludesDecodingError) {
self = .includes(error: decodingError)
}
init?(_ decodingError: DecodingError) {
switch decodingError {
case .valueNotFound(let type, let context) where Location(context) == .data && type is AbstractResourceObject.Type:
self = .primaryResourceMissing
case .valueNotFound(let type, let context) where Location(context) == .data && type == UnkeyedDecodingContainer.self:
self = .primaryResourcesMissing
default:
return nil
}
}
private enum Location: Equatable {
case data
init?(_ context: DecodingError.Context) {
guard context.codingPath.contains(where: { $0.stringValue == "data" }) else {
return nil
}
self = .data
}
}
}
extension DocumentDecodingError: CustomStringConvertible {
public var description: String {
switch self {
case .primaryResource(error: let error, idx: let idx):
let idxString = idx.map { " \($0 + 1)" } ?? ""
return "Primary Resource\(idxString) failed to parse because \(error)"
case .primaryResourceMissing:
return "Primary Resource missing."
case .primaryResourcesMissing:
return "Primary Resources array missing."
case .includes(error: let error):
return "\(error)"
case .foundErrorDocumentWhenExpectingSuccess:
return "Expected a success document with a 'data' property but found an error document."
case .foundSuccessDocumentWhenExpectingError:
return "Expected an error document but found a success document with a 'data' property."
}
}
}
+58 -2
View File
@@ -32,7 +32,7 @@ public struct Includes<I: Include>: Encodable, Equatable {
var container = encoder.unkeyedContainer()
guard I.self != NoIncludes.self else {
throw JSONAPIEncodingError.illegalEncoding("Attempting to encode Include0, which should be represented by the absense of an 'included' entry altogether.")
throw JSONAPICodingError.illegalEncoding("Attempting to encode Include0, which should be represented by the absense of an 'included' entry altogether.", path: encoder.codingPath)
}
for value in values {
@@ -56,8 +56,35 @@ extension Includes: Decodable where I: Decodable {
}
var valueAggregator = [I]()
var idx = 0
while !container.isAtEnd {
valueAggregator.append(try container.decode(I.self))
do {
valueAggregator.append(try container.decode(I.self))
idx = idx + 1
} catch let error as PolyDecodeNoTypesMatchedError {
let errors: [ResourceObjectDecodingError] = error
.individualTypeFailures
.compactMap { decodingError in
switch decodingError.error {
case .typeMismatch(_, let context),
.valueNotFound(_, let context),
.keyNotFound(_, let context),
.dataCorrupted(let context):
return context.underlyingError as? ResourceObjectDecodingError
@unknown default:
return nil
}
}
guard errors.count == error.individualTypeFailures.count else {
throw IncludesDecodingError(error: error, idx: idx)
}
throw IncludesDecodingError(
error: IncludeDecodingError(failures: errors),
idx: idx
)
} catch let error {
throw IncludesDecodingError(error: error, idx: idx)
}
}
values = valueAggregator
@@ -177,3 +204,32 @@ extension Includes where I: _Poly11 {
return values.compactMap { $0.k }
}
}
// MARK: - DecodingError
public struct IncludesDecodingError: Swift.Error, Equatable {
public let error: Swift.Error
public let idx: Int
public static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.idx == rhs.idx
&& String(describing: lhs) == String(describing: rhs)
}
}
extension IncludesDecodingError: CustomStringConvertible {
public var description: String {
return "Include \(idx + 1) failed to parse: \(error)"
}
}
public struct IncludeDecodingError: Swift.Error, Equatable, CustomStringConvertible {
public let failures: [ResourceObjectDecodingError]
public var description: String {
return failures
.enumerated()
.map {
"\nCould not have been Include Type \($0.offset + 1) because:\n\($0.element)"
}.joined(separator: "\n")
}
}
+16 -1
View File
@@ -125,8 +125,17 @@ extension ManyResourceBody: Decodable, CodableResourceBody where PrimaryResource
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var valueAggregator = [PrimaryResource]()
var idx = 0
while !container.isAtEnd {
valueAggregator.append(try container.decode(PrimaryResource.self))
do {
valueAggregator.append(try container.decode(PrimaryResource.self))
} catch let error as ResourceObjectDecodingError {
throw ManyResourceBodyDecodingError(
error: error,
idx: idx
)
}
idx = idx + 1
}
values = valueAggregator
}
@@ -145,3 +154,9 @@ extension ManyResourceBody: CustomStringConvertible {
return "PrimaryResourceBody(\(String(describing: values)))"
}
}
// MARK: - DecodingError
public struct ManyResourceBodyDecodingError: Swift.Error, Equatable {
public let error: ResourceObjectDecodingError
public let idx: Int
}
-14
View File
@@ -1,14 +0,0 @@
//
// EncodingError.swift
// JSONAPI
//
// Created by Mathew Polzin on 12/7/18.
//
public enum JSONAPIEncodingError: Swift.Error {
case typeMismatch(expected: String, found: String)
case illegalEncoding(String)
case illegalDecoding(String)
case missingOrMalformedMetadata
case missingOrMalformedLinks
}
+27
View File
@@ -0,0 +1,27 @@
//
// JSONAPICodingError.swift
// JSONAPI
//
// Created by Mathew Polzin on 12/7/18.
//
public enum JSONAPICodingError: Swift.Error {
case typeMismatch(expected: String, found: String, path: [CodingKey])
case quantityMismatch(expected: Quantity, path: [CodingKey])
case illegalEncoding(String, path: [CodingKey])
case illegalDecoding(String, path: [CodingKey])
case missingOrMalformedMetadata(path: [CodingKey])
case missingOrMalformedLinks(path: [CodingKey])
public enum Quantity: String, Equatable {
case one
case many
public var other: Quantity {
switch self {
case .one: return .many
case .many: return .one
}
}
}
}
+9 -1
View File
@@ -5,13 +5,21 @@
// Created by Mathew Polzin on 11/13/18.
//
public protocol AttributeType: Codable {
public protocol AbstractAttributeType {
var rawValueType: Any.Type { get }
}
public protocol AttributeType: Codable, AbstractAttributeType {
associatedtype RawValue: Codable
associatedtype ValueType
var value: ValueType { get }
}
extension AttributeType {
public var rawValueType: Any.Type { return RawValue.self }
}
// MARK: TransformedAttribute
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
+5 -2
View File
@@ -45,7 +45,10 @@ public protocol OptionalId: Codable {
init(rawValue: RawType)
}
public protocol IdType: OptionalId, CustomStringConvertible, Hashable where RawType: RawIdType {}
/// marker protocol
public protocol AbstractId {}
public protocol IdType: AbstractId, OptionalId, CustomStringConvertible, Hashable where RawType: RawIdType {}
extension Optional: MaybeRawId where Wrapped: Codable & Equatable {}
extension Optional: OptionalId where Wrapped: IdType {
@@ -94,7 +97,7 @@ public struct Id<RawType: MaybeRawId, IdentifiableType: JSONAPI.JSONTyped>: Equa
}
}
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {
extension Id: Hashable, CustomStringConvertible, AbstractId, IdType where RawType: RawIdType {
public static func id(from rawValue: RawType) -> Id<RawType, IdentifiableType> {
return Id(rawValue: rawValue)
}
@@ -22,11 +22,11 @@ public typealias CodablePolyWrapped = EncodablePolyWrapped & Decodable
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.")
throw JSONAPICodingError.illegalDecoding("Attempted to decode Poly0, which should represent a thing that is not expected to be found in a document.", path: decoder.codingPath)
}
public func encode(to encoder: Encoder) throws {
throw JSONAPIEncodingError.illegalEncoding("Attempted to encode Poly0, which should represent a thing that is not expected to be found in a document.")
throw JSONAPICodingError.illegalEncoding("Attempted to encode Poly0, which should represent a thing that is not expected to be found in a document.", path: encoder.codingPath)
}
}
+40 -6
View File
@@ -170,18 +170,36 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
// succeeds and then attempt to coerce nil to a Identifier
// type at which point we can store nil in `id`.
let anyNil: Any? = nil
if try container.decodeNil(forKey: .data),
let val = anyNil as? Identifiable.Identifier {
if try container.decodeNil(forKey: .data) {
guard let val = anyNil as? Identifiable.Identifier else {
throw DecodingError.valueNotFound(
Self.self,
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected non-null relationship data."
)
)
}
id = val
return
}
let identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
let identifier: KeyedDecodingContainer<ResourceIdentifierCodingKeys>
do {
identifier = try container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
} catch let error as DecodingError {
guard case let .typeMismatch(type, context) = error,
type is _DictionaryType.Type else {
throw error
}
throw JSONAPICodingError.quantityMismatch(expected: .one,
path: context.codingPath)
}
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Identifiable.jsonType else {
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.jsonType, found: type)
throw JSONAPICodingError.typeMismatch(expected: Identifiable.jsonType, found: type, path: decoder.codingPath)
}
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
@@ -230,7 +248,17 @@ extension ToManyRelationship: Codable {
links = try container.decode(LinksType.self, forKey: .links)
}
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
var identifiers: UnkeyedDecodingContainer
do {
identifiers = try container.nestedUnkeyedContainer(forKey: .data)
} catch let error as DecodingError {
guard case let .typeMismatch(type, context) = error,
type is _ArrayType.Type else {
throw error
}
throw JSONAPICodingError.quantityMismatch(expected: .many,
path: context.codingPath)
}
var newIds = [Relatable.Identifier]()
while !identifiers.isAtEnd {
@@ -239,7 +267,7 @@ extension ToManyRelationship: Codable {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.jsonType else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.jsonType, found: type)
throw JSONAPICodingError.typeMismatch(expected: Relatable.jsonType, found: type, path: decoder.codingPath)
}
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
@@ -277,3 +305,9 @@ extension ToOneRelationship: CustomStringConvertible {
extension ToManyRelationship: CustomStringConvertible {
public var description: String { return "Relationship([\(ids.map(String.init(describing:)).joined(separator: ", "))])" }
}
private protocol _DictionaryType {}
extension Dictionary: _DictionaryType {}
private protocol _ArrayType {}
extension Array: _ArrayType {}
@@ -96,10 +96,13 @@ extension ResourceObjectProxy {
public static var jsonType: String { return Description.jsonType }
}
/// A marker protocol.
public protocol AbstractResourceObject {}
/// ResourceObjectType is the protocol that ResourceObject conforms to. This
/// protocol lets other types accept any ResourceObject as a generic
/// specialization.
public protocol ResourceObjectType: ResourceObjectProxy, CodablePrimaryResource where Description: ResourceObjectDescription {
public protocol ResourceObjectType: AbstractResourceObject, ResourceObjectProxy, CodablePrimaryResource where Description: ResourceObjectDescription {
associatedtype Meta: JSONAPI.Meta
associatedtype Links: JSONAPI.Links
@@ -411,21 +414,56 @@ public extension ResourceObject {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
let type: String
do {
type = try container.decode(String.self, forKey: .type)
} catch let error as DecodingError {
throw ResourceObjectDecodingError(error)
?? error
}
guard ResourceObject.jsonType == type else {
throw JSONAPIEncodingError.typeMismatch(expected: Description.jsonType, found: type)
throw ResourceObjectDecodingError(
expectedJSONAPIType: ResourceObject.jsonType,
found: type
)
}
let maybeUnidentified = Unidentified() as? EntityRawIdType
id = try maybeUnidentified.map { ResourceObject.Id(rawValue: $0) } ?? container.decode(ResourceObject.Id.self, forKey: .id)
attributes = try (NoAttributes() as? Description.Attributes) ??
container.decode(Description.Attributes.self, forKey: .attributes)
do {
attributes = try (NoAttributes() as? Description.Attributes)
?? container.decodeIfPresent(Description.Attributes.self, forKey: .attributes)
?? Description.Attributes(from: EmptyObjectDecoder())
} catch let decodingError as DecodingError {
throw ResourceObjectDecodingError(decodingError)
?? decodingError
} catch _ as EmptyObjectDecodingError {
throw ResourceObjectDecodingError(
subjectName: ResourceObjectDecodingError.entireObject,
cause: .keyNotFound,
location: .attributes
)
}
relationships = try (NoRelationships() as? Description.Relationships)
?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships)
?? Description.Relationships(from: EmptyObjectDecoder())
do {
relationships = try (NoRelationships() as? Description.Relationships)
?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships)
?? Description.Relationships(from: EmptyObjectDecoder())
} catch let decodingError as DecodingError {
throw ResourceObjectDecodingError(decodingError)
?? decodingError
} catch let decodingError as JSONAPICodingError {
throw ResourceObjectDecodingError(decodingError)
?? decodingError
} catch _ as EmptyObjectDecodingError {
throw ResourceObjectDecodingError(
subjectName: ResourceObjectDecodingError.entireObject,
cause: .keyNotFound,
location: .relationships
)
}
meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta)
@@ -0,0 +1,135 @@
//
// ResourceObjectDecodingError.swift
//
//
// Created by Mathew Polzin on 11/10/19.
//
public struct ResourceObjectDecodingError: Swift.Error, Equatable {
public let subjectName: String
public let cause: Cause
public let location: Location
static let entireObject = "entire object"
public enum Cause: Equatable {
case keyNotFound
case valueNotFound
case typeMismatch(expectedTypeName: String)
case jsonTypeMismatch(expectedType: String, foundType: String)
case quantityMismatch(expected: JSONAPICodingError.Quantity)
}
public enum Location: String, Equatable {
case attributes
case relationships
case type
var singular: String {
switch self {
case .attributes: return "attribute"
case .relationships: return "relationship"
case .type: return "type"
}
}
}
init?(_ decodingError: DecodingError) {
switch decodingError {
case .typeMismatch(let expectedType, let ctx):
(location, subjectName) = Self.context(ctx)
let typeString = String(describing: expectedType)
cause = .typeMismatch(expectedTypeName: typeString)
case .valueNotFound(_, let ctx):
(location, subjectName) = Self.context(ctx)
cause = .valueNotFound
case .keyNotFound(let missingKey, let ctx):
(location, _) = Self.context(ctx)
subjectName = missingKey.stringValue
cause = .keyNotFound
default:
return nil
}
}
init?(_ jsonAPIError: JSONAPICodingError) {
switch jsonAPIError {
case .typeMismatch(expected: let expected, found: let found, path: let path):
(location, subjectName) = Self.context(path: path)
cause = .jsonTypeMismatch(expectedType: expected, foundType: found)
case .quantityMismatch(expected: let expected, path: let path):
(location, subjectName) = Self.context(path: path)
cause = .quantityMismatch(expected: expected)
default:
return nil
}
}
init(expectedJSONAPIType: String, found: String) {
location = .type
subjectName = "self"
cause = .jsonTypeMismatch(expectedType: expectedJSONAPIType, foundType: found)
}
init(subjectName: String, cause: Cause, location: Location) {
self.subjectName = subjectName
self.cause = cause
self.location = location
}
static func context(_ decodingContext: DecodingError.Context) -> (Location, name: String) {
return context(path: decodingContext.codingPath)
}
static func context(path: [CodingKey]) -> (Location, name: String) {
let location: Location
if path.contains(where: { $0.stringValue == "attributes" }) {
location = .attributes
} else if path.contains(where: { $0.stringValue == "relationships" }) {
location = .relationships
} else {
location = .type
}
return (
location,
name: path.last?.stringValue ?? "unnamed"
)
}
}
extension ResourceObjectDecodingError: CustomStringConvertible {
public var description: String {
switch cause {
case .keyNotFound where subjectName == ResourceObjectDecodingError.entireObject:
return "\(location) object is required and missing."
case .keyNotFound where location == .type:
return "'type' (a.k.a. JSON:API type name) is required and missing."
case .keyNotFound:
return "'\(subjectName)' \(location.singular) is required and missing."
case .valueNotFound where location == .type:
return "'type' (a.k.a. JSON:API type name) is not nullable but null was found."
case .valueNotFound:
return "'\(subjectName)' \(location.singular) is not nullable but null was found."
case .typeMismatch(expectedTypeName: let expected) where location == .type:
return "'type' (a.k.a. the JSON:API type name) is not a \(expected) as expected."
case .typeMismatch(expectedTypeName: let expected):
return "'\(subjectName)' \(location.singular) is not a \(expected) as expected."
case .jsonTypeMismatch(expectedType: let expected, foundType: let found) where location == .type:
return "found JSON:API type \"\(found)\" but expected \"\(expected)\""
case .jsonTypeMismatch(expectedType: let expected, foundType: let found):
return "'\(subjectName)' \(location.singular) is of JSON:API type \"\(found)\" but it was expected to be \"\(expected)\""
case .quantityMismatch(expected: let expected):
let expecation: String = {
switch expected {
case .many:
return "\(expected) values"
case .one:
return "\(expected) value"
}
}()
return "'\(subjectName)' \(location.singular) should contain \(expecation) but found \(expected.other)"
}
}
}
@@ -36,15 +36,12 @@ public struct SparseFieldset<
public extension ResourceObject where Description.Attributes: SparsableAttributes {
/// The `SparseFieldset` type for this `ResourceObject`
typealias SparseType = SparseFieldset<Description, MetaType, LinksType, EntityRawIdType>
/// Get a Sparse Fieldset of this `ResourceObject` that can be encoded
/// as a `SparsePrimaryResource`.
func sparse(with fields: [Description.Attributes.CodingKeys]) -> SparseFieldset<Description, MetaType, LinksType, EntityRawIdType> {
func sparse(with fields: [Description.Attributes.CodingKeys]) -> SparseType {
return SparseFieldset(self, fields: fields)
}
}
public extension ResourceObject where Description.Attributes: SparsableAttributes {
/// The `SparseFieldset` type for this `ResourceObject`
typealias SparseType = SparseFieldset<Description, MetaType, LinksType, EntityRawIdType>
}
@@ -14,7 +14,7 @@ public enum ArrayElementComparison: Equatable, CustomStringConvertible {
case differentValues(String, String)
case prebuilt(String)
public init(sameTypeComparison: Comparison) {
public init(sameTypeComparison: BasicComparison) {
switch sameTypeComparison {
case .same:
self = .same
@@ -8,11 +8,11 @@
import JSONAPI
extension Attributes {
public func compare(to other: Self) -> [String: Comparison] {
public func compare(to other: Self) -> [String: BasicComparison] {
let mirror1 = Mirror(reflecting: self)
let mirror2 = Mirror(reflecting: other)
var comparisons = [String: Comparison]()
var comparisons = [String: BasicComparison]()
for child in mirror1.children {
guard let childLabel = child.label else { continue }
@@ -5,13 +5,13 @@
// Created by Mathew Polzin on 11/3/19.
//
public protocol Comparable: CustomStringConvertible {
public protocol Comparison: CustomStringConvertible {
var rawValue: String { get }
var isSame: Bool { get }
}
public enum Comparison: Comparable, Equatable {
public enum BasicComparison: Comparison, Equatable {
case same
case different(String, String)
case prebuilt(String)
@@ -56,11 +56,11 @@ public enum Comparison: Comparable, Equatable {
public typealias NamedDifferences = [String: String]
public protocol PropertyComparable: Comparable {
public protocol PropertyComparison: Comparison {
var differences: NamedDifferences { get }
}
extension PropertyComparable {
extension PropertyComparison {
public var description: String {
return differences
.map { "(\($0): \($1))" }
@@ -7,11 +7,11 @@
import JSONAPI
public struct DocumentComparison: Equatable, PropertyComparable {
public let apiDescription: Comparison
public struct DocumentComparison: Equatable, PropertyComparison {
public let apiDescription: BasicComparison
public let body: BodyComparison
init(apiDescription: Comparison, body: BodyComparison) {
init(apiDescription: BasicComparison, body: BodyComparison) {
self.apiDescription = apiDescription
self.body = body
}
@@ -33,7 +33,7 @@ public enum BodyComparison: Equatable, CustomStringConvertible {
case differentErrors(ErrorComparison)
case differentData(DocumentDataComparison)
public typealias ErrorComparison = [Comparison]
public typealias ErrorComparison = [BasicComparison]
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(
@@ -48,9 +48,9 @@ public enum BodyComparison: Equatable, CustomStringConvertible {
String(describing: error2)
)
}
).map(Comparison.init) + [
Comparison(meta1, meta2),
Comparison(links1, links2)
).map(BasicComparison.init) + [
BasicComparison(meta1, meta2),
BasicComparison(links1, links2)
]
}
@@ -79,10 +79,10 @@ public enum BodyComparison: Equatable, CustomStringConvertible {
public var rawValue: String { description }
}
extension EncodableJSONAPIDocument where Body: Equatable, PrimaryResourceBody: _OptionalResourceBody {
extension EncodableJSONAPIDocument where Body: Equatable, PrimaryResourceBody: TestableResourceBody {
public func compare(to other: Self) -> DocumentComparison {
return DocumentComparison(
apiDescription: Comparison(
apiDescription: BasicComparison(
String(describing: apiDescription),
String(describing: other.apiDescription)
),
@@ -91,7 +91,7 @@ extension EncodableJSONAPIDocument where Body: Equatable, PrimaryResourceBody: _
}
}
extension DocumentBody where Self: Equatable, PrimaryResourceBody: _OptionalResourceBody {
extension DocumentBody where Self: Equatable, PrimaryResourceBody: TestableResourceBody {
public func compare(to other: Self) -> BodyComparison {
// rule out case where they are the same
@@ -7,13 +7,13 @@
import JSONAPI
public struct DocumentDataComparison: Equatable, PropertyComparable {
public struct DocumentDataComparison: Equatable, PropertyComparison {
public let primary: PrimaryResourceBodyComparison
public let includes: IncludesComparison
public let meta: Comparison
public let links: Comparison
public let meta: BasicComparison
public let links: BasicComparison
init(primary: PrimaryResourceBodyComparison, includes: IncludesComparison, meta: Comparison, links: Comparison) {
init(primary: PrimaryResourceBodyComparison, includes: IncludesComparison, meta: BasicComparison, links: BasicComparison) {
self.primary = primary
self.includes = includes
self.meta = meta
@@ -33,20 +33,20 @@ public struct DocumentDataComparison: Equatable, PropertyComparable {
}
}
extension DocumentBodyData where PrimaryResourceBody: _OptionalResourceBody {
extension DocumentBodyData where PrimaryResourceBody: TestableResourceBody {
public func compare(to other: Self) -> DocumentDataComparison {
return .init(
primary: primary.compare(to: other.primary),
includes: includes.compare(to: other.includes),
meta: Comparison(meta, other.meta),
links: Comparison(links, other.links)
meta: BasicComparison(meta, other.meta),
links: BasicComparison(links, other.links)
)
}
}
public enum PrimaryResourceBodyComparison: Equatable, CustomStringConvertible {
case oneOrMore(ManyResourceObjectComparison)
case optionalSingle(Comparison)
case optionalSingle(BasicComparison)
public var isSame: Bool {
switch self {
@@ -69,7 +69,7 @@ public enum PrimaryResourceBodyComparison: Equatable, CustomStringConvertible {
public var rawValue: String { return description }
}
public struct ManyResourceObjectComparison: Equatable, PropertyComparable {
public struct ManyResourceObjectComparison: Equatable, PropertyComparison {
public let comparisons: [ArrayElementComparison]
public init(_ comparisons: [ArrayElementComparison]) {
@@ -86,16 +86,16 @@ public struct ManyResourceObjectComparison: Equatable, PropertyComparable {
}
}
extension _OptionalResourceBody where WrappedPrimaryResourceType: ResourceObjectType {
extension TestableResourceBody where TestablePrimaryResourceType: ResourceObjectType {
public func compare(to other: Self) -> PrimaryResourceBodyComparison {
guard let one = optionalResourceObject,
let two = other.optionalResourceObject else {
guard let one = testableResourceObject,
let two = other.testableResourceObject else {
func nilOrName<T>(_ resObj: [T]?) -> String {
resObj.map { _ in String(describing: T.self) } ?? "nil"
}
return .optionalSingle(Comparison(nilOrName(optionalResourceObject), nilOrName(other.optionalResourceObject)))
return .optionalSingle(BasicComparison(nilOrName(testableResourceObject), nilOrName(other.testableResourceObject)))
}
return .oneOrMore(.init(one.compare(to: two, using: { r1, r2 in
@@ -120,18 +120,18 @@ extension _OptionalResourceBody where WrappedPrimaryResourceType: ResourceObject
}
}
public protocol _OptionalResourceBody {
associatedtype WrappedPrimaryResourceType: ResourceObjectType
var optionalResourceObject: [WrappedPrimaryResourceType]? { get }
public protocol TestableResourceBody {
associatedtype TestablePrimaryResourceType: ResourceObjectType
var testableResourceObject: [TestablePrimaryResourceType]? { get }
}
public protocol _OptionalResourceObjectType {
public protocol OptionalResourceObjectType {
associatedtype Wrapped: ResourceObjectType
var maybeValue: Wrapped? { get }
}
extension Optional: _OptionalResourceObjectType where Wrapped: ResourceObjectType {
extension Optional: OptionalResourceObjectType where Wrapped: ResourceObjectType {
public var maybeValue: Wrapped? {
switch self {
case .none:
@@ -142,16 +142,16 @@ extension Optional: _OptionalResourceObjectType where Wrapped: ResourceObjectTyp
}
}
extension ResourceObject: _OptionalResourceObjectType {
extension ResourceObject: OptionalResourceObjectType {
public var maybeValue: Self? { self }
}
extension ManyResourceBody: _OptionalResourceBody where PrimaryResource: ResourceObjectType {
public var optionalResourceObject: [PrimaryResource]? { values }
extension ManyResourceBody: TestableResourceBody where PrimaryResource: ResourceObjectType {
public var testableResourceObject: [PrimaryResource]? { values }
}
extension SingleResourceBody: _OptionalResourceBody where PrimaryResource: _OptionalResourceObjectType {
public typealias WrappedPrimaryResourceType = PrimaryResource.Wrapped
extension SingleResourceBody: TestableResourceBody where PrimaryResource: OptionalResourceObjectType {
public typealias TestablePrimaryResourceType = PrimaryResource.Wrapped
public var optionalResourceObject: [WrappedPrimaryResourceType]? { value.maybeValue.map { [$0] } }
public var testableResourceObject: [TestablePrimaryResourceType]? { value.maybeValue.map { [$0] } }
}

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