mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
update everything except for IdType to operate off of EntityType rather than EntityDescription. This feels much more natural.
This commit is contained in:
+8
-4
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user