update everything except for IdType to operate off of EntityType rather than EntityDescription. This feels much more natural.

This commit is contained in:
Mathew Polzin
2018-11-15 21:05:07 -08:00
parent ea85593655
commit 971e486cb1
12 changed files with 209 additions and 162 deletions
+8 -4
View File
@@ -7,10 +7,12 @@
import JSONAPI
typealias StringId<E: EntityDescription> = Id<String, E>
enum PersonDescription: IdentifiedEntityDescription {
static var type: String { return "people" }
typealias Identifier = Id<String, PersonDescription>
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<PersonDescription>
let friends: ToManyRelationship<Person>
}
}
typealias Person = Entity<PersonDescription>
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)
}
+3 -3
View File
@@ -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<PersonDescription>`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<PersonDescription>`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<SingleResourceBody<PersonDescription>, NoIncludes, BasicJSONAPIError>.self
let responseStructure = JSONAPIDocument<SingleResourceBody<Person>, 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<PersonDescription>` instead of `NoIncludes`.
To specify that we expect friends of a person to be included in the above example `JSONAPIDocument`, we would use `Include1<Person>` instead of `NoIncludes`.
#### `Error`
+71 -95
View File
@@ -42,14 +42,14 @@ public struct Includes<I: IncludeDecoder>: Decodable {
// MARK: - Decoding
func decode<EntityDescription: JSONAPI.EntityDescription>(_ type: EntityDescription.Type, from container: SingleValueDecodingContainer) throws -> Result<Entity<EntityDescription>, EncodingError> {
let ret: Result<Entity<EntityDescription>, EncodingError>
func decode<Entity: JSONAPI.EntityType>(_ type: Entity.Type, from container: SingleValueDecodingContainer) throws -> Result<Entity, EncodingError> {
let ret: Result<Entity, EncodingError>
do {
ret = try .success(container.decode(Entity<EntityDescription>.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<A>? { get }
associatedtype A: EntityType
var a: A? { get }
}
public enum Include1<A: EntityDescription>: _Include1 {
case a(Entity<A>)
public enum Include1<A: EntityType>: _Include1 {
case a(A)
public var a: Entity<A>? {
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
}
@@ -83,35 +83,31 @@ public enum Include1<A: EntityDescription>: _Include1 {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self = .a(try container.decode(Entity<A>.self))
self = .a(try container.decode(A.self))
}
}
extension Includes where I: _Include1 {
public subscript(_ lookup: I.A.Type) -> [Entity<I.A>] {
public subscript(_ lookup: I.A.Type) -> [I.A] {
return values.compactMap { $0.a }
}
public subscript(_ lookup: Entity<I.A>.Type) -> [Entity<I.A>] {
return values.compactMap { $0.a}
}
}
// MARK: - 2 includes
public protocol _Include2: _Include1 {
associatedtype B: EntityDescription
var b: Entity<B>? { get }
associatedtype B: EntityType
var b: B? { get }
}
public enum Include2<A: EntityDescription, B: EntityDescription>: _Include2 {
case a(Entity<A>)
case b(Entity<B>)
public enum Include2<A: EntityType, B: EntityType>: _Include2 {
case a(A)
case b(B)
public var a: Entity<A>? {
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
}
public var b: Entity<B>? {
public var b: B? {
guard case let .b(ret) = self else { return nil }
return ret
}
@@ -136,36 +132,32 @@ public enum Include2<A: EntityDescription, B: EntityDescription>: _Include2 {
}
extension Includes where I: _Include2 {
public subscript(_ lookup: I.B.Type) -> [Entity<I.B>] {
public subscript(_ lookup: I.B.Type) -> [I.B] {
return values.compactMap { $0.b }
}
public subscript(_ lookup: Entity<I.B>.Type) -> [Entity<I.B>] {
return values.compactMap { $0.b}
}
}
// MARK: - 3 includes
public protocol _Include3: _Include2 {
associatedtype C: EntityDescription
var c: Entity<C>? { get }
associatedtype C: EntityType
var c: C? { get }
}
public enum Include3<A: EntityDescription, B: EntityDescription, C: EntityDescription>: _Include3 {
case a(Entity<A>)
case b(Entity<B>)
case c(Entity<C>)
public enum Include3<A: EntityType, B: EntityType, C: EntityType>: _Include3 {
case a(A)
case b(B)
case c(C)
public var a: Entity<A>? {
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
}
public var b: Entity<B>? {
public var b: B? {
guard case let .b(ret) = self else { return nil }
return ret
}
public var c: Entity<C>? {
public var c: C? {
guard case let .c(ret) = self else { return nil }
return ret
}
@@ -191,42 +183,38 @@ public enum Include3<A: EntityDescription, B: EntityDescription, C: EntityDescri
}
extension Includes where I: _Include3 {
public subscript(_ lookup: I.C.Type) -> [Entity<I.C>] {
public subscript(_ lookup: I.C.Type) -> [I.C] {
return values.compactMap { $0.c }
}
public subscript(_ lookup: Entity<I.C>.Type) -> [Entity<I.C>] {
return values.compactMap { $0.c}
}
}
// MARK: - 4 includes
public protocol _Include4: _Include3 {
associatedtype D: EntityDescription
var d: Entity<D>? { get }
associatedtype D: EntityType
var d: D? { get }
}
public enum Include4<A: EntityDescription, B: EntityDescription, C: EntityDescription, D: EntityDescription>: _Include4 {
case a(Entity<A>)
case b(Entity<B>)
case c(Entity<C>)
case d(Entity<D>)
public enum Include4<A: EntityType, B: EntityType, C: EntityType, D: EntityType>: _Include4 {
case a(A)
case b(B)
case c(C)
case d(D)
public var a: Entity<A>? {
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
}
public var b: Entity<B>? {
public var b: B? {
guard case let .b(ret) = self else { return nil }
return ret
}
public var c: Entity<C>? {
public var c: C? {
guard case let .c(ret) = self else { return nil }
return ret
}
public var d: Entity<D>? {
public var d: D? {
guard case let .d(ret) = self else { return nil }
return ret
}
@@ -253,48 +241,44 @@ public enum Include4<A: EntityDescription, B: EntityDescription, C: EntityDescri
}
extension Includes where I: _Include4 {
public subscript(_ lookup: I.D.Type) -> [Entity<I.D>] {
public subscript(_ lookup: I.D.Type) -> [I.D] {
return values.compactMap { $0.d }
}
public subscript(_ lookup: Entity<I.D>.Type) -> [Entity<I.D>] {
return values.compactMap { $0.d}
}
}
// MARK: - 5 includes
public protocol _Include5: _Include4 {
associatedtype E: EntityDescription
var e: Entity<E>? { get }
associatedtype E: EntityType
var e: E? { get }
}
public enum Include5<A: EntityDescription, B: EntityDescription, C: EntityDescription, D: EntityDescription, E: EntityDescription>: _Include5 {
case a(Entity<A>)
case b(Entity<B>)
case c(Entity<C>)
case d(Entity<D>)
case e(Entity<E>)
public enum Include5<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType>: _Include5 {
case a(A)
case b(B)
case c(C)
case d(D)
case e(E)
public var a: Entity<A>? {
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
}
public var b: Entity<B>? {
public var b: B? {
guard case let .b(ret) = self else { return nil }
return ret
}
public var c: Entity<C>? {
public var c: C? {
guard case let .c(ret) = self else { return nil }
return ret
}
public var d: Entity<D>? {
public var d: D? {
guard case let .d(ret) = self else { return nil }
return ret
}
public var e: Entity<E>? {
public var e: E? {
guard case let .e(ret) = self else { return nil }
return ret
}
@@ -322,54 +306,50 @@ public enum Include5<A: EntityDescription, B: EntityDescription, C: EntityDescri
}
extension Includes where I: _Include5 {
public subscript(_ lookup: I.E.Type) -> [Entity<I.E>] {
public subscript(_ lookup: I.E.Type) -> [I.E] {
return values.compactMap { $0.e }
}
public subscript(_ lookup: Entity<I.E>.Type) -> [Entity<I.E>] {
return values.compactMap { $0.e}
}
}
// MARK: - 6 includes
public protocol _Include6: _Include5 {
associatedtype F: EntityDescription
var f: Entity<F>? { get }
associatedtype F: EntityType
var f: F? { get }
}
public enum Include6<A: EntityDescription, B: EntityDescription, C: EntityDescription, D: EntityDescription, E: EntityDescription, F: EntityDescription>: _Include6 {
case a(Entity<A>)
case b(Entity<B>)
case c(Entity<C>)
case d(Entity<D>)
case e(Entity<E>)
case f(Entity<F>)
public enum Include6<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType>: _Include6 {
case a(A)
case b(B)
case c(C)
case d(D)
case e(E)
case f(F)
public var a: Entity<A>? {
public var a: A? {
guard case let .a(ret) = self else { return nil }
return ret
}
public var b: Entity<B>? {
public var b: B? {
guard case let .b(ret) = self else { return nil }
return ret
}
public var c: Entity<C>? {
public var c: C? {
guard case let .c(ret) = self else { return nil }
return ret
}
public var d: Entity<D>? {
public var d: D? {
guard case let .d(ret) = self else { return nil }
return ret
}
public var e: Entity<E>? {
public var e: E? {
guard case let .e(ret) = self else { return nil }
return ret
}
public var f: Entity<F>? {
public var f: F? {
guard case let .f(ret) = self else { return nil }
return ret
}
@@ -398,11 +378,7 @@ public enum Include6<A: EntityDescription, B: EntityDescription, C: EntityDescri
}
extension Includes where I: _Include6 {
public subscript(_ lookup: I.F.Type) -> [Entity<I.F>] {
public subscript(_ lookup: I.F.Type) -> [I.F] {
return values.compactMap { $0.f }
}
public subscript(_ lookup: Entity<I.F>.Type) -> [Entity<I.F>] {
return values.compactMap { $0.f}
}
}
+7 -7
View File
@@ -8,28 +8,28 @@
public protocol ResourceBody: Decodable {
}
public struct SingleResourceBody<EntityDescription: JSONAPI.EntityDescription>: ResourceBody {
public let value: Entity<EntityDescription>?
public struct SingleResourceBody<Entity: JSONAPI.EntityType>: ResourceBody {
public let value: Entity?
}
public struct ManyResourceBody<EntityDescription: JSONAPI.EntityDescription>: ResourceBody {
public let values: [Entity<EntityDescription>]
public struct ManyResourceBody<Entity: JSONAPI.EntityType>: 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<EntityDescription>.self)
value = try container.decode(Entity.self)
}
}
extension ManyResourceBody {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var valueAggregator = [Entity<EntityDescription>]()
var valueAggregator = [Entity]()
while !container.isAtEnd {
valueAggregator.append(try container.decode(Entity<EntityDescription>.self))
valueAggregator.append(try container.decode(Entity.self))
}
values = valueAggregator
}
+41 -33
View File
@@ -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<EntityDescription: JSONAPI.EntityDescription>: Codable, Equatable {
public typealias Identifier = EntityDescription.Identifier
public struct Entity<Description: JSONAPI.EntityDescription>: 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<EntityDescription: JSONAPI.EntityDescription>: 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<EntityDescription: JSONAPI.EntityDescription>: 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<Entity> {
@@ -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<T, TFRM: Transformer>(_ path: KeyPath<EntityDescription.Attributes, TransformAttribute<T, TFRM>>) -> TFRM.To {
subscript<T, TFRM: Transformer>(_ path: KeyPath<Description.Attributes, TransformAttribute<T, TFRM>>) -> 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<T, TFRM: Transformer>(_ path: KeyPath<EntityDescription.Attributes, TransformAttribute<T, TFRM>?>) -> TFRM.To? {
subscript<T, TFRM: Transformer>(_ path: KeyPath<Description.Attributes, TransformAttribute<T, TFRM>?>) -> 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<T, TFRM: Transformer, U>(_ path: KeyPath<EntityDescription.Attributes, TransformAttribute<T, TFRM>?>) -> U? where TFRM.To == U? {
subscript<T, TFRM: Transformer, U>(_ path: KeyPath<Description.Attributes, TransformAttribute<T, TFRM>?>) -> 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 ~><OtherEntity: OptionalRelatable>(entity: Entity, path: KeyPath<EntityDescription.Relationships, ToOneRelationship<OtherEntity>>) -> OtherEntity.Identifier {
public static func ~><OtherEntity: OptionalRelatable>(entity: Entity, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity>>) -> 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 ~><OtherEntity: Relatable>(entity: Entity, path: KeyPath<EntityDescription.Relationships, ToManyRelationship<OtherEntity>>) -> [OtherEntity.Identifier] {
public static func ~><OtherEntity: Relatable>(entity: Entity, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity>>) -> [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)
}
}
+1
View File
@@ -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<RawType: RawIdType, EntityDescription: JSONAPI.EntityDescription>: IdType {
public let rawValue: RawType
public init(rawValue: RawType) {
+3 -5
View File
@@ -53,8 +53,8 @@ extension ToManyRelationship where Relatable.Description.Identifier == Relatable
/// The OptionalRelatable protocol ONLY describes
/// Optional<T: Relatable> 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
@@ -11,7 +11,7 @@ import JSONAPI
class DocumentTests: XCTestCase {
func test_singleDocumentNoIncludes() {
let document = try? JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<ArticleType>, Include0, BasicJSONAPIError>.self, from: single_document_no_includes)
let document = try? JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<Article>, 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<SingleResourceBody<ArticleType>, Include1<AuthorType>, BasicJSONAPIError>.self, from: single_document_some_includes)
let document = try? JSONDecoder().decode(JSONAPIDocument<SingleResourceBody<Article>, Include1<Author>, 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<ManyResourceBody<ArticleType>, Include0, BasicJSONAPIError>.self, from: many_document_no_includes)
let document = try? JSONDecoder().decode(JSONAPIDocument<ManyResourceBody<Article>, 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<ManyResourceBody<ArticleType>, Include1<AuthorType>, BasicJSONAPIError>.self, from: many_document_some_includes)
let document = try? JSONDecoder().decode(JSONAPIDocument<ManyResourceBody<Article>, Include1<Author>, BasicJSONAPIError>.self, from: many_document_some_includes)
XCTAssertNotNil(document)
+46 -2
View File
@@ -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<TestEntityType9>
enum TestEntityType10: EntityDescription {
public static var type: String { return "tenth_test_entities" }
typealias Identifier = Id<String, TestEntityType10>
typealias Attributes = NoAttributes
public struct Relationships: JSONAPI.Relationships {
let selfRef: ToOneRelationship<TestEntity10>
let selfRefs: ToManyRelationship<TestEntity10>
}
}
typealias TestEntity10 = Entity<TestEntityType10>
enum UnidentifiedTestEntityType: UnidentifiedEntityDescription {
public static var type: String { return "unidentified_test_entities" }
typealias Attributes = NoAttributes
typealias Relationships = NoRelatives
}
typealias UnidentifiedTestEntity = Entity<UnidentifiedTestEntityType>
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<EntityTests.TestEntity1>) {
self.init(relationships: .init(other: other))
}
}
extension Entity where EntityDescription == EntityTests.TestEntityType3 {
extension Entity where Description == EntityTests.TestEntityType3 {
init(others: ToManyRelationship<EntityTests.TestEntity1>) {
self.init(relationships: .init(others: others))
}
@@ -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)!
@@ -19,7 +19,7 @@ class IncludedTests: XCTestCase {
}
func test_OneInclude() {
let maybeIncludes = try? decoder.decode(Includes<Include1<TestEntityType>>.self, from: one_include)
let maybeIncludes = try? decoder.decode(Includes<Include1<TestEntity>>.self, from: one_include)
XCTAssertNotNil(maybeIncludes)
@@ -31,7 +31,7 @@ class IncludedTests: XCTestCase {
}
func test_TwoSameIncludes() {
let maybeIncludes = try? decoder.decode(Includes<Include1<TestEntityType>>.self, from: two_same_type_includes)
let maybeIncludes = try? decoder.decode(Includes<Include1<TestEntity>>.self, from: two_same_type_includes)
XCTAssertNotNil(maybeIncludes)
@@ -43,7 +43,7 @@ class IncludedTests: XCTestCase {
}
func test_TwoDifferentIncludes() {
let maybeIncludes = try? decoder.decode(Includes<Include2<TestEntityType, TestEntityType2>>.self, from: two_different_type_includes)
let maybeIncludes = try? decoder.decode(Includes<Include2<TestEntity, TestEntity2>>.self, from: two_different_type_includes)
XCTAssertNotNil(maybeIncludes)
@@ -56,7 +56,7 @@ class IncludedTests: XCTestCase {
}
func test_ThreeDifferentIncludes() {
let maybeIncludes = try? decoder.decode(Includes<Include3<TestEntityType, TestEntityType2, TestEntityType4>>.self, from: three_different_type_includes)
let maybeIncludes = try? decoder.decode(Includes<Include3<TestEntity, TestEntity2, TestEntity4>>.self, from: three_different_type_includes)
XCTAssertNotNil(maybeIncludes)
@@ -70,7 +70,7 @@ class IncludedTests: XCTestCase {
}
func test_FourDifferentIncludes() {
let maybeIncludes = try? decoder.decode(Includes<Include4<TestEntityType, TestEntityType2, TestEntityType4, TestEntityType6>>.self, from: four_different_type_includes)
let maybeIncludes = try? decoder.decode(Includes<Include4<TestEntity, TestEntity2, TestEntity4, TestEntity6>>.self, from: four_different_type_includes)
XCTAssertNotNil(maybeIncludes)
@@ -85,7 +85,7 @@ class IncludedTests: XCTestCase {
}
func test_FiveDifferentIncludes() {
let maybeIncludes = try? decoder.decode(Includes<Include5<TestEntityType, TestEntityType2, TestEntityType3, TestEntityType4, TestEntityType6>>.self, from: five_different_type_includes)
let maybeIncludes = try? decoder.decode(Includes<Include5<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6>>.self, from: five_different_type_includes)
XCTAssertNotNil(maybeIncludes)
@@ -101,7 +101,7 @@ class IncludedTests: XCTestCase {
}
func test_SixDifferentIncludes() {
let maybeIncludes = try? decoder.decode(Includes<Include6<TestEntityType, TestEntityType2, TestEntityType3, TestEntityType4, TestEntityType5, TestEntityType6>>.self, from: six_different_type_includes)
let maybeIncludes = try? decoder.decode(Includes<Include6<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6>>.self, from: six_different_type_includes)
XCTAssertNotNil(maybeIncludes)
@@ -11,7 +11,7 @@ import JSONAPI
class ResourceBodyTests: XCTestCase {
func test_singleResourceBody() {
let body = try? JSONDecoder().decode(SingleResourceBody<ArticleType>.self, from: single_resource_body)
let body = try? JSONDecoder().decode(SingleResourceBody<Article>.self, from: single_resource_body)
XCTAssertNotNil(body)
@@ -22,7 +22,7 @@ class ResourceBodyTests: XCTestCase {
}
func test_manyResourceBody() {
let body = try? JSONDecoder().decode(ManyResourceBody<ArticleType>.self, from: many_resource_body)
let body = try? JSONDecoder().decode(ManyResourceBody<Article>.self, from: many_resource_body)
XCTAssertNotNil(body)