From 971e486cb17a1ab6e848493b3ee20cbc8a76e09c Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Thu, 15 Nov 2018 21:05:07 -0800 Subject: [PATCH] update everything except for IdType to operate off of EntityType rather than EntityDescription. This feels much more natural. --- Examples.swift | 12 +- README.md | 6 +- Sources/JSONAPI/Document/Includes.swift | 166 ++++++++---------- Sources/JSONAPI/Document/ResourceBody.swift | 14 +- Sources/JSONAPI/Resource/Entity.swift | 74 ++++---- Sources/JSONAPI/Resource/Id.swift | 1 + Sources/JSONAPI/Resource/Relationship.swift | 8 +- .../JSONAPITests/Document/DocumentTests.swift | 8 +- Tests/JSONAPITests/Entity/EntityTests.swift | 48 ++++- .../Entity/stubs/EntityStubs.swift | 16 ++ .../JSONAPITests/Includes/IncludeTests.swift | 14 +- .../ResourceBody/ResourceBodyTests.swift | 4 +- 12 files changed, 209 insertions(+), 162 deletions(-) diff --git a/Examples.swift b/Examples.swift index 3b5bf57..f36bc7e 100644 --- a/Examples.swift +++ b/Examples.swift @@ -7,10 +7,12 @@ import JSONAPI +typealias StringId = Id + enum PersonDescription: IdentifiedEntityDescription { - static var type: String { return "people" } - typealias Identifier = Id + + static var type: String { return "people" } struct Attributes: JSONAPI.Attributes { let name: [String] @@ -18,12 +20,14 @@ enum PersonDescription: IdentifiedEntityDescription { } struct Relationships: JSONAPI.Relationships { - let friends: ToManyRelationship + let friends: ToManyRelationship } } typealias Person = Entity func tmp() { - let x: Person.Identifier + let person = Person(id: .init(rawValue: "33"), attributes: PersonDescription.Attributes(name: [], favoriteColor: "Green"), relationships: PersonDescription.Relationships(friends: .none)) + + print(person.pointer) } diff --git a/README.md b/README.md index 857f087..d43dda5 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ This readme doesn't go into detail on the JSON API Spec, but the following JSON ### `Entity` -Once you have an `EntityDescription`, you _create_, _encode_, and _decode_ `Entity`s that "fit the description". If you have a `CreatableRawIdType` (see the section on `RawIdType`s below) then you can create new `Entity`s, but even without a `CreatableRawIdType` you can decode and work with entities. +Once you have an `EntityDescription`, you _create_, _encode_, and _decode_ `Entity`s that "fit the description". If you have a `CreatableRawIdType` (see the section on `RawIdType`s below) then you can create new `Entity`s, but even without a `CreatableRawIdType` you can encode, decode and work with entities. The `Entity` and `EntityDescription` together embody the rules and properties of a JSON API *Resource Object*. @@ -215,7 +215,7 @@ The entirety of a JSON API request or response is encoded or decoded from- or to ``` let decoder = JSONDecoder() -let responseStructure = JSONAPIDocument, NoIncludes, BasicJSONAPIError>.self +let responseStructure = JSONAPIDocument, NoIncludes, BasicJSONAPIError>.self let document = try decoder.decode(responseStructure, from: data) ``` @@ -230,7 +230,7 @@ The second generic type of a `JSONAPIDocument` is an `IncludeDecoder`. This type **IMPORTANT**: The number trailing "Include" in these type names does not indicate a number of included entities, it indicates a number of _types_ of included entities. `Include1` can be used to decode any number of included entities as long as all the entities are of the same _type_. -To specify that we expect friends of a person to be included in the above example `JSONAPIDocument`, we would use `Include1` instead of `NoIncludes`. +To specify that we expect friends of a person to be included in the above example `JSONAPIDocument`, we would use `Include1` instead of `NoIncludes`. #### `Error` diff --git a/Sources/JSONAPI/Document/Includes.swift b/Sources/JSONAPI/Document/Includes.swift index 94b801d..40ee3fa 100644 --- a/Sources/JSONAPI/Document/Includes.swift +++ b/Sources/JSONAPI/Document/Includes.swift @@ -42,14 +42,14 @@ public struct Includes: Decodable { // MARK: - Decoding -func decode(_ type: EntityDescription.Type, from container: SingleValueDecodingContainer) throws -> Result, EncodingError> { - let ret: Result, EncodingError> +func decode(_ type: Entity.Type, from container: SingleValueDecodingContainer) throws -> Result { + let ret: Result do { - ret = try .success(container.decode(Entity.self)) + ret = try .success(container.decode(Entity.self)) } catch (let err as EncodingError) { ret = .failure(err) } catch (let err) { - ret = .failure(EncodingError.invalidValue(EntityDescription.self, + ret = .failure(EncodingError.invalidValue(Entity.Description.self, .init(codingPath: container.codingPath, debugDescription: err.localizedDescription, underlyingError: err))) @@ -69,13 +69,13 @@ public typealias NoIncludes = Include0 // MARK: - 1 include public protocol _Include1: _Include0 { - associatedtype A: EntityDescription - var a: Entity? { get } + associatedtype A: EntityType + var a: A? { get } } -public enum Include1: _Include1 { - case a(Entity) +public enum Include1: _Include1 { + case a(A) - public var a: Entity? { + public var a: A? { guard case let .a(ret) = self else { return nil } return ret } @@ -83,35 +83,31 @@ public enum Include1: _Include1 { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - self = .a(try container.decode(Entity.self)) + self = .a(try container.decode(A.self)) } } extension Includes where I: _Include1 { - public subscript(_ lookup: I.A.Type) -> [Entity] { + public subscript(_ lookup: I.A.Type) -> [I.A] { return values.compactMap { $0.a } } - - public subscript(_ lookup: Entity.Type) -> [Entity] { - return values.compactMap { $0.a} - } } // MARK: - 2 includes public protocol _Include2: _Include1 { - associatedtype B: EntityDescription - var b: Entity? { get } + associatedtype B: EntityType + var b: B? { get } } -public enum Include2: _Include2 { - case a(Entity) - case b(Entity) +public enum Include2: _Include2 { + case a(A) + case b(B) - public var a: Entity? { + public var a: A? { guard case let .a(ret) = self else { return nil } return ret } - public var b: Entity? { + public var b: B? { guard case let .b(ret) = self else { return nil } return ret } @@ -136,36 +132,32 @@ public enum Include2: _Include2 { } extension Includes where I: _Include2 { - public subscript(_ lookup: I.B.Type) -> [Entity] { + public subscript(_ lookup: I.B.Type) -> [I.B] { return values.compactMap { $0.b } } - - public subscript(_ lookup: Entity.Type) -> [Entity] { - return values.compactMap { $0.b} - } } // MARK: - 3 includes public protocol _Include3: _Include2 { - associatedtype C: EntityDescription - var c: Entity? { get } + associatedtype C: EntityType + var c: C? { get } } -public enum Include3: _Include3 { - case a(Entity) - case b(Entity) - case c(Entity) +public enum Include3: _Include3 { + case a(A) + case b(B) + case c(C) - public var a: Entity? { + public var a: A? { guard case let .a(ret) = self else { return nil } return ret } - public var b: Entity? { + public var b: B? { guard case let .b(ret) = self else { return nil } return ret } - public var c: Entity? { + public var c: C? { guard case let .c(ret) = self else { return nil } return ret } @@ -191,42 +183,38 @@ public enum Include3 [Entity] { + public subscript(_ lookup: I.C.Type) -> [I.C] { return values.compactMap { $0.c } } - - public subscript(_ lookup: Entity.Type) -> [Entity] { - return values.compactMap { $0.c} - } } // MARK: - 4 includes public protocol _Include4: _Include3 { - associatedtype D: EntityDescription - var d: Entity? { get } + associatedtype D: EntityType + var d: D? { get } } -public enum Include4: _Include4 { - case a(Entity) - case b(Entity) - case c(Entity) - case d(Entity) +public enum Include4: _Include4 { + case a(A) + case b(B) + case c(C) + case d(D) - public var a: Entity? { + public var a: A? { guard case let .a(ret) = self else { return nil } return ret } - public var b: Entity? { + public var b: B? { guard case let .b(ret) = self else { return nil } return ret } - public var c: Entity? { + public var c: C? { guard case let .c(ret) = self else { return nil } return ret } - public var d: Entity? { + public var d: D? { guard case let .d(ret) = self else { return nil } return ret } @@ -253,48 +241,44 @@ public enum Include4 [Entity] { + public subscript(_ lookup: I.D.Type) -> [I.D] { return values.compactMap { $0.d } } - - public subscript(_ lookup: Entity.Type) -> [Entity] { - return values.compactMap { $0.d} - } } // MARK: - 5 includes public protocol _Include5: _Include4 { - associatedtype E: EntityDescription - var e: Entity? { get } + associatedtype E: EntityType + var e: E? { get } } -public enum Include5: _Include5 { - case a(Entity) - case b(Entity) - case c(Entity) - case d(Entity) - case e(Entity) +public enum Include5: _Include5 { + case a(A) + case b(B) + case c(C) + case d(D) + case e(E) - public var a: Entity? { + public var a: A? { guard case let .a(ret) = self else { return nil } return ret } - public var b: Entity? { + public var b: B? { guard case let .b(ret) = self else { return nil } return ret } - public var c: Entity? { + public var c: C? { guard case let .c(ret) = self else { return nil } return ret } - public var d: Entity? { + public var d: D? { guard case let .d(ret) = self else { return nil } return ret } - public var e: Entity? { + public var e: E? { guard case let .e(ret) = self else { return nil } return ret } @@ -322,54 +306,50 @@ public enum Include5 [Entity] { + public subscript(_ lookup: I.E.Type) -> [I.E] { return values.compactMap { $0.e } } - - public subscript(_ lookup: Entity.Type) -> [Entity] { - return values.compactMap { $0.e} - } } // MARK: - 6 includes public protocol _Include6: _Include5 { - associatedtype F: EntityDescription - var f: Entity? { get } + associatedtype F: EntityType + var f: F? { get } } -public enum Include6: _Include6 { - case a(Entity) - case b(Entity) - case c(Entity) - case d(Entity) - case e(Entity) - case f(Entity) +public enum Include6: _Include6 { + case a(A) + case b(B) + case c(C) + case d(D) + case e(E) + case f(F) - public var a: Entity? { + public var a: A? { guard case let .a(ret) = self else { return nil } return ret } - public var b: Entity? { + public var b: B? { guard case let .b(ret) = self else { return nil } return ret } - public var c: Entity? { + public var c: C? { guard case let .c(ret) = self else { return nil } return ret } - public var d: Entity? { + public var d: D? { guard case let .d(ret) = self else { return nil } return ret } - public var e: Entity? { + public var e: E? { guard case let .e(ret) = self else { return nil } return ret } - public var f: Entity? { + public var f: F? { guard case let .f(ret) = self else { return nil } return ret } @@ -398,11 +378,7 @@ public enum Include6 [Entity] { + public subscript(_ lookup: I.F.Type) -> [I.F] { return values.compactMap { $0.f } } - - public subscript(_ lookup: Entity.Type) -> [Entity] { - return values.compactMap { $0.f} - } } diff --git a/Sources/JSONAPI/Document/ResourceBody.swift b/Sources/JSONAPI/Document/ResourceBody.swift index 9774a05..1c7c55e 100644 --- a/Sources/JSONAPI/Document/ResourceBody.swift +++ b/Sources/JSONAPI/Document/ResourceBody.swift @@ -8,28 +8,28 @@ public protocol ResourceBody: Decodable { } -public struct SingleResourceBody: ResourceBody { - public let value: Entity? +public struct SingleResourceBody: ResourceBody { + public let value: Entity? } -public struct ManyResourceBody: ResourceBody { - public let values: [Entity] +public struct ManyResourceBody: ResourceBody { + public let values: [Entity] } // MARK: Decodable extension SingleResourceBody { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - value = try container.decode(Entity.self) + value = try container.decode(Entity.self) } } extension ManyResourceBody { public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() - var valueAggregator = [Entity]() + var valueAggregator = [Entity]() while !container.isAtEnd { - valueAggregator.append(try container.decode(Entity.self)) + valueAggregator.append(try container.decode(Entity.self)) } values = valueAggregator } diff --git a/Sources/JSONAPI/Resource/Entity.swift b/Sources/JSONAPI/Resource/Entity.swift index b782629..26cecd2 100644 --- a/Sources/JSONAPI/Resource/Entity.swift +++ b/Sources/JSONAPI/Resource/Entity.swift @@ -51,15 +51,23 @@ public protocol IdentifiedEntityDescription: EntityDescription where Identifier: /// new `Entity`. public protocol UnidentifiedEntityDescription: EntityDescription where Identifier == Unidentified {} +/// EntityType is the protocol that Entity conforms to. This +/// protocol lets other types accept any Entity as a generic +/// specialization. +public protocol EntityType: Codable, Equatable { + associatedtype Description: EntityDescription + associatedtype Identifier: Equatable & Codable +} + /// An `Entity` is a single model type that can be /// encoded to or decoded from a JSON API /// "Resource Object." /// See https://jsonapi.org/format/#document-resource-objects -public struct Entity: Codable, Equatable { - public typealias Identifier = EntityDescription.Identifier +public struct Entity: EntityType { + public typealias Identifier = Description.Identifier /// The JSON API compliant "type" of this `Entity`. - public static var type: String { return EntityDescription.type } + public static var type: String { return Description.type } /// The `Entity`'s Id. This can be of type `Unidentified` if /// the entity is being created clientside and the @@ -68,12 +76,12 @@ public struct Entity: Codable, Equ public let id: Identifier /// The JSON API compliant attributes of this `Entity`. - public let attributes: EntityDescription.Attributes + public let attributes: Description.Attributes /// The JSON API compliant relationships of this `Entity`. - public let relationships: EntityDescription.Relationships + public let relationships: Description.Relationships - public init(id: EntityDescription.Identifier, attributes: EntityDescription.Attributes, relationships: EntityDescription.Relationships) { + public init(id: Description.Identifier, attributes: Description.Attributes, relationships: Description.Relationships) { self.id = id self.attributes = attributes self.relationships = relationships @@ -81,52 +89,52 @@ public struct Entity: Codable, Equ } // MARK: Convenience initializers -extension Entity where EntityDescription.Identifier: CreatableIdType { - public init(attributes: EntityDescription.Attributes, relationships: EntityDescription.Relationships) { - self.id = EntityDescription.Identifier() +extension Entity where Description.Identifier: CreatableIdType { + public init(attributes: Description.Attributes, relationships: Description.Relationships) { + self.id = Description.Identifier() self.attributes = attributes self.relationships = relationships } } -extension Entity where EntityDescription.Attributes == NoAttributes { - public init(id: EntityDescription.Identifier, relationships: EntityDescription.Relationships) { +extension Entity where Description.Attributes == NoAttributes { + public init(id: Description.Identifier, relationships: Description.Relationships) { self.init(id: id, attributes: NoAttributes(), relationships: relationships) } } -extension Entity where EntityDescription.Attributes == NoAttributes, EntityDescription.Identifier: CreatableIdType { - public init(relationships: EntityDescription.Relationships) { +extension Entity where Description.Attributes == NoAttributes, Description.Identifier: CreatableIdType { + public init(relationships: Description.Relationships) { self.init(attributes: NoAttributes(), relationships: relationships) } } -extension Entity where EntityDescription.Relationships == NoRelatives { - public init(id: EntityDescription.Identifier, attributes: EntityDescription.Attributes) { +extension Entity where Description.Relationships == NoRelatives { + public init(id: Description.Identifier, attributes: Description.Attributes) { self.init(id: id, attributes: attributes, relationships: NoRelatives()) } } -extension Entity where EntityDescription.Relationships == NoRelatives, EntityDescription.Identifier: CreatableIdType { - public init(attributes: EntityDescription.Attributes) { +extension Entity where Description.Relationships == NoRelatives, Description.Identifier: CreatableIdType { + public init(attributes: Description.Attributes) { self.init(attributes: attributes, relationships: NoRelatives()) } } -extension Entity where EntityDescription.Attributes == NoAttributes, EntityDescription.Relationships == NoRelatives { - public init(id: EntityDescription.Identifier) { +extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelatives { + public init(id: Description.Identifier) { self.init(id: id, attributes: NoAttributes(), relationships: NoRelatives()) } } -extension Entity where EntityDescription.Attributes == NoAttributes, EntityDescription.Relationships == NoRelatives, EntityDescription.Identifier: CreatableIdType { +extension Entity where Description.Attributes == NoAttributes, Description.Relationships == NoRelatives, Description.Identifier: CreatableIdType { public init() { self.init(attributes: NoAttributes(), relationships: NoRelatives()) } } // MARK: Pointer for Relationships use. -public extension Entity where EntityDescription.Identifier: IdType { +public extension Entity where Description.Identifier: IdType { /// Get a pointer to this entity that can be used as a /// relationship to another entity. public var pointer: ToOneRelationship { @@ -139,21 +147,21 @@ public extension Entity { /// Access the attribute at the given keypath. This just /// allows you to write `entity[\.propertyName]` instead /// of `entity.relationships.propertyName`. - subscript(_ path: KeyPath>) -> TFRM.To { + subscript(_ path: KeyPath>) -> TFRM.To { return attributes[keyPath: path].value } /// Access the attribute at the given keypath. This just /// allows you to write `entity[\.propertyName]` instead /// of `entity.relationships.propertyName`. - subscript(_ path: KeyPath?>) -> TFRM.To? { + subscript(_ path: KeyPath?>) -> TFRM.To? { return attributes[keyPath: path]?.value } /// Access the attribute at the given keypath. This just /// allows you to write `entity[\.propertyName]` instead /// of `entity.relationships.propertyName`. - subscript(_ path: KeyPath?>) -> U? where TFRM.To == U? { + subscript(_ path: KeyPath?>) -> U? where TFRM.To == U? { return attributes[keyPath: path].flatMap { $0.value } } } @@ -163,14 +171,14 @@ public extension Entity { /// Access to an Id of a `ToOneRelationship`. /// This allows you to write `entity ~> \.other` instead /// of `entity.relationships.other.id`. - public static func ~>(entity: Entity, path: KeyPath>) -> OtherEntity.Identifier { + public static func ~>(entity: Entity, path: KeyPath>) -> OtherEntity.Identifier { return entity.relationships[keyPath: path].id } /// Access to all Ids of a `ToManyRelationship`. /// This allows you to write `entity ~> \.others` instead /// of `entity.relationships.others.ids`. - public static func ~>(entity: Entity, path: KeyPath>) -> [OtherEntity.Identifier] { + public static func ~>(entity: Entity, path: KeyPath>) -> [OtherEntity.Identifier] { return entity.relationships[keyPath: path].ids } } @@ -191,15 +199,15 @@ public extension Entity { try container.encode(Entity.type, forKey: .type) - if EntityDescription.Identifier.self != Unidentified.self { + if Description.Identifier.self != Unidentified.self { try container.encode(id, forKey: .id) } - if EntityDescription.Attributes.self != NoAttributes.self { + if Description.Attributes.self != NoAttributes.self { try container.encode(attributes, forKey: .attributes) } - if EntityDescription.Relationships.self != NoRelatives.self { + if Description.Relationships.self != NoRelatives.self { try container.encode(relationships, forKey: .relationships) } } @@ -211,13 +219,13 @@ public extension Entity { let type = try container.decode(String.self, forKey: .type) guard Entity.type == type else { - throw JSONAPIEncodingError.typeMismatch(expected: EntityDescription.type, found: type) + throw JSONAPIEncodingError.typeMismatch(expected: Description.type, found: type) } - id = try (Unidentified() as? EntityDescription.Identifier) ?? container.decode(EntityDescription.Identifier.self, forKey: .id) + id = try (Unidentified() as? Description.Identifier) ?? container.decode(Description.Identifier.self, forKey: .id) - attributes = try (NoAttributes() as? EntityDescription.Attributes) ?? container.decode(EntityDescription.Attributes.self, forKey: .attributes) + attributes = try (NoAttributes() as? Description.Attributes) ?? container.decode(Description.Attributes.self, forKey: .attributes) - relationships = try (NoRelatives() as? EntityDescription.Relationships) ?? container.decode(EntityDescription.Relationships.self, forKey: .relationships) + relationships = try (NoRelatives() as? Description.Relationships) ?? container.decode(Description.Relationships.self, forKey: .relationships) } } diff --git a/Sources/JSONAPI/Resource/Id.swift b/Sources/JSONAPI/Resource/Id.swift index ac5c67c..fa236a7 100644 --- a/Sources/JSONAPI/Resource/Id.swift +++ b/Sources/JSONAPI/Resource/Id.swift @@ -48,6 +48,7 @@ public protocol CreatableIdType: IdType { /// An Entity ID. These IDs can be encoded to or decoded from /// JSON API IDs. public struct Id: IdType { + public let rawValue: RawType public init(rawValue: RawType) { diff --git a/Sources/JSONAPI/Resource/Relationship.swift b/Sources/JSONAPI/Resource/Relationship.swift index f2e24ab..abaabf4 100644 --- a/Sources/JSONAPI/Resource/Relationship.swift +++ b/Sources/JSONAPI/Resource/Relationship.swift @@ -53,8 +53,8 @@ extension ToManyRelationship where Relatable.Description.Identifier == Relatable /// The OptionalRelatable protocol ONLY describes /// Optional types. -public protocol OptionalRelatable { - associatedtype Description: EntityDescription where Description.Identifier: IdType +public protocol OptionalRelatable: Codable, Equatable where Description.Identifier: IdType { + associatedtype Description: EntityDescription associatedtype Identifier: Equatable & Codable } @@ -62,9 +62,7 @@ public protocol OptionalRelatable { /// has an EntityDescription public protocol Relatable: OptionalRelatable {} -extension Entity: Relatable, OptionalRelatable where EntityDescription.Identifier: IdType { - public typealias Description = EntityDescription -} +extension Entity: Relatable, OptionalRelatable where Description.Identifier: IdType {} extension Optional: OptionalRelatable where Wrapped: Relatable { public typealias Description = Wrapped.Description diff --git a/Tests/JSONAPITests/Document/DocumentTests.swift b/Tests/JSONAPITests/Document/DocumentTests.swift index 74fbf02..f3c3650 100644 --- a/Tests/JSONAPITests/Document/DocumentTests.swift +++ b/Tests/JSONAPITests/Document/DocumentTests.swift @@ -11,7 +11,7 @@ import JSONAPI class DocumentTests: XCTestCase { func test_singleDocumentNoIncludes() { - let document = try? JSONDecoder().decode(JSONAPIDocument, Include0, BasicJSONAPIError>.self, from: single_document_no_includes) + let document = try? JSONDecoder().decode(JSONAPIDocument, Include0, BasicJSONAPIError>.self, from: single_document_no_includes) XCTAssertNotNil(document) @@ -24,7 +24,7 @@ class DocumentTests: XCTestCase { } func test_singleDocumentSomeIncludes() { - let document = try? JSONDecoder().decode(JSONAPIDocument, Include1, BasicJSONAPIError>.self, from: single_document_some_includes) + let document = try? JSONDecoder().decode(JSONAPIDocument, Include1, BasicJSONAPIError>.self, from: single_document_some_includes) XCTAssertNotNil(document) @@ -39,7 +39,7 @@ class DocumentTests: XCTestCase { } func test_manyDocumentNoIncludes() { - let document = try? JSONDecoder().decode(JSONAPIDocument, Include0, BasicJSONAPIError>.self, from: many_document_no_includes) + let document = try? JSONDecoder().decode(JSONAPIDocument, Include0, BasicJSONAPIError>.self, from: many_document_no_includes) XCTAssertNotNil(document) @@ -55,7 +55,7 @@ class DocumentTests: XCTestCase { } func test_manyDocumentSomeIncludes() { - let document = try? JSONDecoder().decode(JSONAPIDocument, Include1, BasicJSONAPIError>.self, from: many_document_some_includes) + let document = try? JSONDecoder().decode(JSONAPIDocument, Include1, BasicJSONAPIError>.self, from: many_document_some_includes) XCTAssertNotNil(document) diff --git a/Tests/JSONAPITests/Entity/EntityTests.swift b/Tests/JSONAPITests/Entity/EntityTests.swift index 2dba5b2..c81e817 100644 --- a/Tests/JSONAPITests/Entity/EntityTests.swift +++ b/Tests/JSONAPITests/Entity/EntityTests.swift @@ -207,6 +207,26 @@ extension EntityTests { } } +// MARK: Relationships of same type as root entity + +extension EntityTests { + func test_RleationshipsOfSameType() { + let entity = try? JSONDecoder().decode(TestEntity10.self, from: entity_self_ref_relationship) + + XCTAssertNotNil(entity) + + guard let e = entity else { return } + + XCTAssertEqual((e ~> \.selfRef).rawValue, "1") + } +} + +// MARK: Unidentified + +extension EntityTests { + +} + // MARK: Test Types extension EntityTests { @@ -349,6 +369,30 @@ extension EntityTests { typealias TestEntity9 = Entity + enum TestEntityType10: EntityDescription { + public static var type: String { return "tenth_test_entities" } + + typealias Identifier = Id + + typealias Attributes = NoAttributes + + public struct Relationships: JSONAPI.Relationships { + let selfRef: ToOneRelationship + let selfRefs: ToManyRelationship + } + } + + typealias TestEntity10 = Entity + + enum UnidentifiedTestEntityType: UnidentifiedEntityDescription { + public static var type: String { return "unidentified_test_entities" } + + typealias Attributes = NoAttributes + typealias Relationships = NoRelatives + } + + typealias UnidentifiedTestEntity = Entity + enum IntToString: Transformer { public static func transform(_ from: Int) -> String { return String(from) @@ -374,13 +418,13 @@ extension EntityTests { } } -extension Entity where EntityDescription == EntityTests.TestEntityType2 { +extension Entity where Description == EntityTests.TestEntityType2 { init(other: ToOneRelationship) { self.init(relationships: .init(other: other)) } } -extension Entity where EntityDescription == EntityTests.TestEntityType3 { +extension Entity where Description == EntityTests.TestEntityType3 { init(others: ToManyRelationship) { self.init(relationships: .init(others: others)) } diff --git a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift index 65c57e8..d5f9d82 100644 --- a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift +++ b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift @@ -189,3 +189,19 @@ let entity_nulled_relationship = """ } } """.data(using: .utf8)! + +let entity_self_ref_relationship = """ +{ + "id": "1", + "type": "tenth_test_entities", + "relationships": { + "selfRefs": { "data": [] }, + "selfRef": { + "data": { + "id": "1", + "type": "tenth_test_entities" + } + } + } +} +""".data(using: .utf8)! diff --git a/Tests/JSONAPITests/Includes/IncludeTests.swift b/Tests/JSONAPITests/Includes/IncludeTests.swift index 0027b81..fe9295f 100644 --- a/Tests/JSONAPITests/Includes/IncludeTests.swift +++ b/Tests/JSONAPITests/Includes/IncludeTests.swift @@ -19,7 +19,7 @@ class IncludedTests: XCTestCase { } func test_OneInclude() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: one_include) + let maybeIncludes = try? decoder.decode(Includes>.self, from: one_include) XCTAssertNotNil(maybeIncludes) @@ -31,7 +31,7 @@ class IncludedTests: XCTestCase { } func test_TwoSameIncludes() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: two_same_type_includes) + let maybeIncludes = try? decoder.decode(Includes>.self, from: two_same_type_includes) XCTAssertNotNil(maybeIncludes) @@ -43,7 +43,7 @@ class IncludedTests: XCTestCase { } func test_TwoDifferentIncludes() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: two_different_type_includes) + let maybeIncludes = try? decoder.decode(Includes>.self, from: two_different_type_includes) XCTAssertNotNil(maybeIncludes) @@ -56,7 +56,7 @@ class IncludedTests: XCTestCase { } func test_ThreeDifferentIncludes() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: three_different_type_includes) + let maybeIncludes = try? decoder.decode(Includes>.self, from: three_different_type_includes) XCTAssertNotNil(maybeIncludes) @@ -70,7 +70,7 @@ class IncludedTests: XCTestCase { } func test_FourDifferentIncludes() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: four_different_type_includes) + let maybeIncludes = try? decoder.decode(Includes>.self, from: four_different_type_includes) XCTAssertNotNil(maybeIncludes) @@ -85,7 +85,7 @@ class IncludedTests: XCTestCase { } func test_FiveDifferentIncludes() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: five_different_type_includes) + let maybeIncludes = try? decoder.decode(Includes>.self, from: five_different_type_includes) XCTAssertNotNil(maybeIncludes) @@ -101,7 +101,7 @@ class IncludedTests: XCTestCase { } func test_SixDifferentIncludes() { - let maybeIncludes = try? decoder.decode(Includes>.self, from: six_different_type_includes) + let maybeIncludes = try? decoder.decode(Includes>.self, from: six_different_type_includes) XCTAssertNotNil(maybeIncludes) diff --git a/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift b/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift index 1f089b6..dceae88 100644 --- a/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift +++ b/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift @@ -11,7 +11,7 @@ import JSONAPI class ResourceBodyTests: XCTestCase { func test_singleResourceBody() { - let body = try? JSONDecoder().decode(SingleResourceBody.self, from: single_resource_body) + let body = try? JSONDecoder().decode(SingleResourceBody
.self, from: single_resource_body) XCTAssertNotNil(body) @@ -22,7 +22,7 @@ class ResourceBodyTests: XCTestCase { } func test_manyResourceBody() { - let body = try? JSONDecoder().decode(ManyResourceBody.self, from: many_resource_body) + let body = try? JSONDecoder().decode(ManyResourceBody
.self, from: many_resource_body) XCTAssertNotNil(body)