diff --git a/Examples.swift b/Examples.swift index 5519340..3b5bf57 100644 --- a/Examples.swift +++ b/Examples.swift @@ -5,7 +5,25 @@ // Created by Mathew Polzin on 11/12/18. // -import Foundation import JSONAPI +enum PersonDescription: IdentifiedEntityDescription { + static var type: String { return "people" } + + typealias Identifier = Id + + struct Attributes: JSONAPI.Attributes { + let name: [String] + let favoriteColor: String + } + + struct Relationships: JSONAPI.Relationships { + let friends: ToManyRelationship + } +} +typealias Person = Entity + +func tmp() { + let x: Person.Identifier +} diff --git a/README.md b/README.md index c3c6505..230782d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,201 @@ # JSONAPI -A description of this package. +A Swift package for encoding and decoding to *JSON API* compliant requests and responses. + +See the JSON API Spec here: https://jsonapi.org/format/ + +## Primary Goals + +The primary goals of this framework are: +1. Allow creation of Swift types that are easy to use in code but also can be encoded to- or decoded from *JSON API* compliant payloads without lots of boilerplate code. +2. Leverage `Codable` to avoid additional outside dependencies and get operability with non-JSON encoders/decoders for free. +3. Do not sacrifice type safety. + +## Project Status + +### Decoding +#### Document +[x] `data` +[x] `included` +[x] `errors` (untested) +[ ] `meta` +[ ] `jsonapi` +[ ] `links` + +#### Resource Object +[x] `id` +[x] `type` +[x] `attributes` +[x] `relationships` +[ ] `links` +[ ] `meta` + +#### Relationship Object +[x] `data` +[ ] `links` +[ ] `meta` + +### Encoding +#### Document +[ ] `data` +[ ] `included` +[ ] `errors` +[ ] `meta` +[ ] `jsonapi` +[ ] `links` + +#### Resource Object +[x] `id` (untested) +[x] `type` (untested) +[x] `attributes` (untested) +[x] `relationships` (untested) +[ ] `links` +[ ] `meta` + +#### Relationship Object +[x] `data` (untested) +[ ] `links` +[ ] `meta` + +### Misc +[ ] `EntityType` validator (using reflection). +[ ] Property-based testing (using `SwiftCheck`). + +## Usage +### Prerequisites +1. Swift 4.2+ and Swift Package Manager + +### `EntityDescription` + +An `EntityDescription` is the `JSONAPI` framework's specification for what the JSON API spec calls a *Resource Objects*. You might create the following `EntityDescription` to represent a person in a network of friends: + +``` +enum PersonDescription: IdentifiedEntityDescription { + static var type: String { return "people" } + + typealias Identifier = Id + + struct Attributes: JSONAPI.Attributes { + let name: [String] + let favoriteColor: String + } + + struct Relationships: JSONAPI.Relationships { + let friends: ToManyRelationship + } +} +``` + +Note that an `enum` type is used here; it could have been a `struct`, but `EntityDescription`s do not ever need to be created so an `enum` with no `case`s is a nice fit for the job. + +This readme doesn't go into detail on the JSON API Spec, but the following JSON API *Resource Object* would be described by the above `PersonDescription`: + +``` +{ + "type": "people", + "id": "9", + "attributes": { + "name": [ + "Jane", + "Doe" + ], + "favoriteColor": "Green" + }, + "relationships": { + "friends": { + "data": [ + { + "id": "7", + "type": "people" + }, + { + "id": "8", + "type": "people" + } + ] + } + } +} +``` + +### `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. + +The `Entity` and `EntityDescription` together embody the rules and properties of a JSON API *Resource Object*. + +It can be nice to create a `typealias` for each type of entity you want to work with: + +``` +typealias Person = Entity +``` + +### `Relationships` + +There are two types of `Relationship`s: `ToOneRelationship` and `ToManyRelationship`. An `EntityDescription`'s `Relationships` type can contain any number of `Relationship`s of either of these types. Do not store anything other than `Relationship`s in the `Relationships` type of an `EntityDescription`. + +An entity that does not have relationships can be described by adding the following to an `EntityDescription`: +``` +typealias Relationships = NoRelatives +``` + +`Relationship`s boil down to Ids of other entities. To access the Id of a related entity, you can use the shorthand `~>` operator with the `KeyPath` of the `Relationship` from which you want the Id. The friends of the above `Person` entity could be accessed as follows (type annotations for clarity): +``` +let friendIds: [Person.Identifier] = person ~> \.friends +``` + +### `Attributes` + +The `Attributes` of an `EntityDescription` can contain any JSON encodable/decodable types. This is the place to store all attributes of an entity. + +An entity that does not have attributes can be described by adding the following to an `EntityDescription`: +``` +typealias Attributes = NoAttributes +``` + +`Attributes` can be accessed via the `subscript` operator of the `Entity` type as follows: +``` +let favoriteColor: String = person[\.favoriteColor] +``` + +### `JSONAPIDocument` + +The entirety of a JSON API request or response is encoded or decoded from- or to a `JSONAPIDocument`. As an example, a JSON API response containing one `Person` and no included entities could be decoded as follows: +``` +let decoder = JSONDecoder() + +let responseStructure = JSONAPIDocument, NoIncludes, BasicJSONAPIError>.self + +let document = try decoder.decode(responseStructure, from: data) +``` + +#### `ResourceBody` + +The first generic type of a `JSONAPIDocument` is a `ResourceBody`. This can either be a `SingleResourceBody` or a `ManyResourceBody`. You will find zero or one `Entity` values in a JSON API document that has a `SingleResourceBody` and you will find zero or more `Entity` values in a JSON API document that has a `ManyResourceBody`. + +#### `IncludeDecoder` + +The second generic type of a `JSONAPIDocument` is an `IncludeDecoder`. This type controls which types of `Entity` are looked for when decoding the "included" part of the JSON API document. If you do not expect any included entities to be in the document, `NoIncludes` is the way to go. The `JSONAPI` framework provides `IncludeDecoder`s for up to six types of included entities. These are named `Include1`, `Include2`, `Include3`, and so on. + +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` + +The final generic type of a `JSONAPIDocument` is the `Error`. You should create an error type that can decode all the errors you expect your `JSONAPIDocument` to be able to decode. As prescribed by the JSON API Spec, these errors will be found in the root document member `errors`. + +### `RawIdType` + +If you want to create new `JSONAPI.Entity` values and assign them Ids then you will need to conform at least one type to `CreatableRawIdType`. Doing so is easy; here are two example conformances for `UUID` and `String` (via `UUID`): +``` +extension UUID: CreatableRawIdType { + public static func unique() -> UUID { + return UUID() + } +} + +extension String: CreatableRawIdType { + public static func unique() -> String { + return UUID().uuidString + } +} +``` diff --git a/Sources/JSONAPI/Document/Document.swift b/Sources/JSONAPI/Document/Document.swift index 0643aa1..3a6a62a 100644 --- a/Sources/JSONAPI/Document/Document.swift +++ b/Sources/JSONAPI/Document/Document.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/5/18. // -import Foundation - /// A JSON API Document represents the entire body /// of a JSON API request or the entire body of /// a JSON API response. diff --git a/Sources/JSONAPI/Document/Error.swift b/Sources/JSONAPI/Document/Error.swift index 6fcab7c..18ed2c5 100644 --- a/Sources/JSONAPI/Document/Error.swift +++ b/Sources/JSONAPI/Document/Error.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/10/18. // -import Foundation - public protocol JSONAPIError: Swift.Error { static var unknown: Self { get } } diff --git a/Sources/JSONAPI/Document/Includes.swift b/Sources/JSONAPI/Document/Includes.swift index 15767e2..3227ae2 100644 --- a/Sources/JSONAPI/Document/Includes.swift +++ b/Sources/JSONAPI/Document/Includes.swift @@ -5,7 +5,6 @@ // Created by Mathew Polzin on 11/10/18. // -import Foundation import Result public protocol IncludeDecoder: Decodable {} @@ -37,7 +36,7 @@ public struct Includes: Decodable { // MARK: - Decoding -func decode(_ type: EntityType.Type, from container: SingleValueDecodingContainer) throws -> Result, EncodingError> { +func decode(_ type: EntityType.Type, from container: SingleValueDecodingContainer) throws -> Result, EncodingError> { let ret: Result, EncodingError> do { ret = try .success(container.decode(Entity.self)) @@ -64,10 +63,10 @@ public typealias NoIncludes = Include0 // MARK: - 1 include public protocol _Include1: _Include0 { - associatedtype A: EntityType + associatedtype A: EntityDescription var a: Entity? { get } } -public enum Include1: _Include1 { +public enum Include1: _Include1 { case a(Entity) public var a: Entity? { @@ -94,10 +93,10 @@ extension Includes where I: _Include1 { // MARK: - 2 includes public protocol _Include2: _Include1 { - associatedtype B: EntityType + associatedtype B: EntityDescription var b: Entity? { get } } -public enum Include2: _Include2 { +public enum Include2: _Include2 { case a(Entity) case b(Entity) @@ -142,10 +141,10 @@ extension Includes where I: _Include2 { // MARK: - 3 includes public protocol _Include3: _Include2 { - associatedtype C: EntityType + associatedtype C: EntityDescription var c: Entity? { get } } -public enum Include3: _Include3 { +public enum Include3: _Include3 { case a(Entity) case b(Entity) case c(Entity) @@ -197,10 +196,10 @@ extension Includes where I: _Include3 { // MARK: - 4 includes public protocol _Include4: _Include3 { - associatedtype D: EntityType + associatedtype D: EntityDescription var d: Entity? { get } } -public enum Include4: _Include4 { +public enum Include4: _Include4 { case a(Entity) case b(Entity) case c(Entity) @@ -259,10 +258,10 @@ extension Includes where I: _Include4 { // MARK: - 5 includes public protocol _Include5: _Include4 { - associatedtype E: EntityType + associatedtype E: EntityDescription var e: Entity? { get } } -public enum Include5: _Include5 { +public enum Include5: _Include5 { case a(Entity) case b(Entity) case c(Entity) @@ -328,10 +327,10 @@ extension Includes where I: _Include5 { // MARK: - 6 includes public protocol _Include6: _Include5 { - associatedtype F: EntityType + associatedtype F: EntityDescription var f: Entity? { get } } -public enum Include6: _Include6 { +public enum Include6: _Include6 { case a(Entity) case b(Entity) case c(Entity) diff --git a/Sources/JSONAPI/Document/ResourceBody.swift b/Sources/JSONAPI/Document/ResourceBody.swift index bab1dec..2b05240 100644 --- a/Sources/JSONAPI/Document/ResourceBody.swift +++ b/Sources/JSONAPI/Document/ResourceBody.swift @@ -5,18 +5,16 @@ // Created by Mathew Polzin on 11/10/18. // -import Foundation - public protocol ResourceBody: Decodable { - typealias Single = SingleResourceBody - typealias Many = ManyResourceBody + typealias Single = SingleResourceBody + typealias Many = ManyResourceBody } -public struct SingleResourceBody: ResourceBody { +public struct SingleResourceBody: ResourceBody { public let value: Entity? } -public struct ManyResourceBody: ResourceBody { +public struct ManyResourceBody: ResourceBody { public let values: [Entity] } diff --git a/Sources/JSONAPI/Resource/Entity.swift b/Sources/JSONAPI/Resource/Entity.swift index 38cfa7a..4de7686 100644 --- a/Sources/JSONAPI/Resource/Entity.swift +++ b/Sources/JSONAPI/Resource/Entity.swift @@ -22,40 +22,42 @@ public struct NoRelatives: Relationships {} /// have any Attributes. public struct NoAttributes: Attributes {} -/// An `EntityType` describes a JSON API +/// An `EntityDescription` describes a JSON API /// Resource Object. The Resource Object /// itself is encoded and decoded as an /// `Entity`, which gets specialized on an -/// `EntityType`. -public protocol EntityType { +/// `EntityDescription`. +public protocol EntityDescription { associatedtype Identifier: JSONAPI.Identifier - associatedtype AttributeType: Attributes - associatedtype RelatedType: Relationships + associatedtype Attributes: JSONAPI.Attributes + associatedtype Relationships: JSONAPI.Relationships static var type: String { get } } -/// Shorthand for an `EntityType` that has an Id. -/// The only times you would not want an `EntityType` -/// to not be an `IdentifiedEntityType` are when you +/// Shorthand for an `EntityDescription` that has an Id. +/// The only times you would not want an `EntityDescription` +/// to not be an `IdentifiedEntityDescription` are when you /// are a client making a request to create a new `Entity` /// or you are a server receiving a request to create a /// new `Entity`. -public protocol IdentifiedEntityType: EntityType where Identifier: IdType {} +public protocol IdentifiedEntityDescription: EntityDescription where Identifier: IdType {} -/// Shorthand for an `EntityType` that does not have an Id. -/// The only times you would not want an `EntityType` -/// to not be an `IdentifiedEntityType` are when you +/// Shorthand for an `EntityDescription` that does not have an Id. +/// The only times you would not want an `EntityDescription` +/// to not be an `IdentifiedEntityDescription` are when you /// are a client making a request to create a new `Entity` /// or you are a server receiving a request to create a /// new `Entity`. -public protocol UnidentifiedEntityType: EntityType where Identifier == Unidentified {} +public protocol UnidentifiedEntityDescription: EntityDescription where Identifier == Unidentified {} /// 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 struct Entity: Codable, Equatable { + public typealias Identifier = EntityType.Identifier + /// The JSON API compliant "type" of this `Entity`. public static var type: String { return EntityType.type } @@ -63,59 +65,67 @@ public struct Entity: Codable, Equatable { /// the entity is being created clientside and the /// server is being asked to create a unique Id. Otherwise, /// this should be of a type conforming to `IdType`. - public let id: EntityType.Identifier + public let id: Identifier /// The JSON API compliant attributes of this `Entity`. - public let attributes: EntityType.AttributeType + public let attributes: EntityType.Attributes /// The JSON API compliant relationships of this `Entity`. - public let relationships: EntityType.RelatedType + public let relationships: EntityType.Relationships - public init(id: EntityType.Identifier, attributes: EntityType.AttributeType, relationships: EntityType.RelatedType) { + public init(id: EntityType.Identifier, attributes: EntityType.Attributes, relationships: EntityType.Relationships) { self.id = id self.attributes = attributes self.relationships = relationships } - - public init(attributes: EntityType.AttributeType, relationships: EntityType.RelatedType) { - self.id = .init() +} + +// MARK: Convenience initializers +extension Entity where EntityType.Identifier: CreatableIdType { + public init(attributes: EntityType.Attributes, relationships: EntityType.Relationships) { + self.id = EntityType.Identifier() self.attributes = attributes self.relationships = relationships } } -extension Entity where EntityType.AttributeType == NoAttributes { - public init(id: EntityType.Identifier, relationships: EntityType.RelatedType) { +extension Entity where EntityType.Attributes == NoAttributes { + public init(id: EntityType.Identifier, relationships: EntityType.Relationships) { self.init(id: id, attributes: NoAttributes(), relationships: relationships) } - - public init(relationships: EntityType.RelatedType) { +} + +extension Entity where EntityType.Attributes == NoAttributes, EntityType.Identifier: CreatableIdType { + public init(relationships: EntityType.Relationships) { self.init(attributes: NoAttributes(), relationships: relationships) } } -extension Entity where EntityType.RelatedType == NoRelatives { - public init(id: EntityType.Identifier, attributes: EntityType.AttributeType) { +extension Entity where EntityType.Relationships == NoRelatives { + public init(id: EntityType.Identifier, attributes: EntityType.Attributes) { self.init(id: id, attributes: attributes, relationships: NoRelatives()) } - - public init(attributes: EntityType.AttributeType) { +} + +extension Entity where EntityType.Relationships == NoRelatives, EntityType.Identifier: CreatableIdType { + public init(attributes: EntityType.Attributes) { self.init(attributes: attributes, relationships: NoRelatives()) } } -extension Entity where EntityType.AttributeType == NoAttributes, EntityType.RelatedType == NoRelatives { +extension Entity where EntityType.Attributes == NoAttributes, EntityType.Relationships == NoRelatives { public init(id: EntityType.Identifier) { self.init(id: id, attributes: NoAttributes(), relationships: NoRelatives()) } - +} + +extension Entity where EntityType.Attributes == NoAttributes, EntityType.Relationships == NoRelatives, EntityType.Identifier: CreatableIdType { public init() { self.init(attributes: NoAttributes(), relationships: NoRelatives()) } } -//public protocol IdentifiedEntityType: JSONAPI.EntityType where IdentifiedEntityType.Identifier: IdType, Identifier.Entity == Self {} - +// MARK: Pointer for Relationships use. public extension Entity where EntityType.Identifier: IdType { /// Get a pointer to this entity that can be used as a /// relationship to another entity. @@ -126,18 +136,27 @@ public extension Entity where EntityType.Identifier: IdType { // MARK: Attribute Access public extension Entity { - subscript(_ path: KeyPath) -> T { + /// Access the attribute at the given keypath. This just + /// allows you to write `entity[\.propertyName]` instead + /// of `entity.relationships.propertyName`. + subscript(_ path: KeyPath) -> T { return attributes[keyPath: path] } } // MARK: Relationship Access public extension Entity { - public static func ~>(entity: Entity, path: KeyPath>) -> OtherEntityType.Identifier { + /// 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>) -> OtherEntityType.Identifier { return entity.relationships[keyPath: path].id } - public static func ~>(entity: Entity, path: KeyPath>) -> [OtherEntityType.Identifier] { + /// 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>) -> [OtherEntityType.Identifier] { return entity.relationships[keyPath: path].ids } } @@ -162,11 +181,11 @@ public extension Entity { try container.encode(id, forKey: .id) } - if EntityType.AttributeType.self != NoAttributes.self { + if EntityType.Attributes.self != NoAttributes.self { try container.encode(attributes, forKey: .attributes) } - if EntityType.RelatedType.self != NoRelatives.self { + if EntityType.Relationships.self != NoRelatives.self { try container.encode(relationships, forKey: .relationships) } } @@ -183,8 +202,8 @@ public extension Entity { id = try (Unidentified() as? EntityType.Identifier) ?? container.decode(EntityType.Identifier.self, forKey: .id) - attributes = try (NoAttributes() as? EntityType.AttributeType) ?? container.decode(EntityType.AttributeType.self, forKey: .attributes) + attributes = try (NoAttributes() as? EntityType.Attributes) ?? container.decode(EntityType.Attributes.self, forKey: .attributes) - relationships = try (NoRelatives() as? EntityType.RelatedType) ?? container.decode(EntityType.RelatedType.self, forKey: .relationships) + relationships = try (NoRelatives() as? EntityType.Relationships) ?? container.decode(EntityType.Relationships.self, forKey: .relationships) } } diff --git a/Sources/JSONAPI/Resource/Id.swift b/Sources/JSONAPI/Resource/Id.swift index b734d4e..1c9f616 100644 --- a/Sources/JSONAPI/Resource/Id.swift +++ b/Sources/JSONAPI/Resource/Id.swift @@ -5,46 +5,43 @@ // Created by Mathew Polzin on 7/24/18. // -import Foundation - /// Any type that you would like to be encoded to and /// decoded from JSON API ids should conform to this -/// protocol. Conformance for `String` and `UUID` -/// is given by this library. -public protocol RawIdType: Codable, Equatable { +/// protocol. Conformance for `String` is given. +public protocol RawIdType: Codable, Equatable {} + +/// If you would like to be able to create new +/// Entities with Ids backed by a RawIdType then +/// your Id type should conform to +/// `CreatableRawIdType`. +/// Conformances for `String` and `UUID` +/// are given in the README for this library. +public protocol CreatableRawIdType: RawIdType { static func unique() -> Self } -public protocol Identifier: Codable, Equatable { - init() -} +extension String: RawIdType {} + +public protocol Identifier: Codable, Equatable {} public struct Unidentified: Identifier { public init() {} } public protocol IdType: Identifier { - associatedtype EntityType: JSONAPI.EntityType + associatedtype EntityType: JSONAPI.EntityDescription associatedtype RawType: RawIdType var rawValue: RawType { get } } -extension UUID: RawIdType { - public static func unique() -> UUID { - return UUID() - } -} - -extension String: RawIdType { - public static func unique() -> String { - return UUID().uuidString - } +public protocol CreatableIdType: IdType { + init() } /// An Entity ID. These IDs can be encoded to or decoded from /// JSON API IDs. -public struct Id: IdType { +public struct Id: IdType { public let rawValue: RawType public init(rawValue: RawType) { @@ -60,7 +57,9 @@ public struct Id: IdType { var container = encoder.singleValueContainer() try container.encode(rawValue) } - +} + +extension Id: CreatableIdType where RawType: CreatableRawIdType { public init() { rawValue = .unique() } diff --git a/Sources/JSONAPI/Resource/Relationship.swift b/Sources/JSONAPI/Resource/Relationship.swift index c697c09..5845d11 100644 --- a/Sources/JSONAPI/Resource/Relationship.swift +++ b/Sources/JSONAPI/Resource/Relationship.swift @@ -5,23 +5,21 @@ // Created by Mathew Polzin on 8/31/18. // -import Foundation - /// An Entity relationship that can be encoded to or decoded from /// a JSON API "Resource Linkage." /// You should use the `ToOneRelationship` and `ToManyRelationship` /// concrete types. /// See https://jsonapi.org/format/#document-resource-object-linkage public protocol Relationship: Equatable, Encodable { - associatedtype EntityType: JSONAPI.EntityType where EntityType.Identifier: IdType + associatedtype EntityType: JSONAPI.EntityDescription where EntityType.Identifier: IdType var ids: [EntityType.Identifier] { get } } /// An Entity relationship that can be encoded to or decoded from /// a JSON API "Resource Linkage." /// See https://jsonapi.org/format/#document-resource-object-linkage -/// A convenient typealias might make your code much more legible: `One` -public struct ToOneRelationship: Equatable, Relationship, Decodable where EntityType.Identifier: IdType { +/// A convenient typealias might make your code much more legible: `One` +public struct ToOneRelationship: Equatable, Relationship, Decodable where EntityType.Identifier: IdType { public let id: EntityType.Identifier public init(entity: Entity) { @@ -36,8 +34,8 @@ public struct ToOneRelationship: Equatable, Rela /// An Entity relationship that can be encoded to or decoded from /// a JSON API "Resource Linkage." /// See https://jsonapi.org/format/#document-resource-object-linkage -/// A convenient typealias might make your code much more legible: `Many` -public struct ToManyRelationship: Equatable, Relationship, Decodable where EntityType.Identifier: IdType { +/// A convenient typealias might make your code much more legible: `Many` +public struct ToManyRelationship: Equatable, Relationship, Decodable where EntityType.Identifier: IdType { public let ids: [EntityType.Identifier] public init(entities: [Entity]) { @@ -47,6 +45,10 @@ public struct ToManyRelationship: Equatable, Rel public init(relationships: [T]) where T.EntityType == EntityType { ids = relationships.flatMap { $0.ids } } + + public static var none: ToManyRelationship { + return .init(entities: []) + } } // MARK: Codable diff --git a/Tests/JSONAPITests/Document/DocumentTests.swift b/Tests/JSONAPITests/Document/DocumentTests.swift index 2b2424f..b1f6fca 100644 --- a/Tests/JSONAPITests/Document/DocumentTests.swift +++ b/Tests/JSONAPITests/Document/DocumentTests.swift @@ -74,22 +74,21 @@ class DocumentTests: XCTestCase { XCTAssertEqual(d.body.data?.included[Author.self][2].id.rawValue, "11") } - enum AuthorType: EntityType { + enum AuthorType: EntityDescription { static var type: String { return "authors" } typealias Identifier = Id - typealias AttributeType = NoAttributes - typealias RelatedType = NoRelatives + typealias Attributes = NoAttributes + typealias Relationships = NoRelatives } typealias Author = Entity - enum ArticleType: EntityType { + enum ArticleType: EntityDescription { static var type: String { return "articles" } typealias Identifier = Id - typealias AttributeType = NoAttributes - typealias RelatedType = Relationships + typealias Attributes = NoAttributes struct Relationships: JSONAPI.Relationships { let author: ToOneRelationship diff --git a/Tests/JSONAPITests/Document/stubs/DocumentStubs.swift b/Tests/JSONAPITests/Document/stubs/DocumentStubs.swift index 6233b8e..5a4ce19 100644 --- a/Tests/JSONAPITests/Document/stubs/DocumentStubs.swift +++ b/Tests/JSONAPITests/Document/stubs/DocumentStubs.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/12/18. // -import Foundation - let single_document_no_includes = """ { "data": { diff --git a/Tests/JSONAPITests/Entity/EntityTests.swift b/Tests/JSONAPITests/Entity/EntityTests.swift index bca4b32..8ac2605 100644 --- a/Tests/JSONAPITests/Entity/EntityTests.swift +++ b/Tests/JSONAPITests/Entity/EntityTests.swift @@ -6,7 +6,6 @@ // import XCTest -import Foundation import JSONAPI class EntityTests: XCTestCase { @@ -68,7 +67,7 @@ class EntityTests: XCTestCase { guard let e = entity else { return } - XCTAssertEqual((e ~> \.others).map { $0.rawValue.uuidString }, ["364B3B69-4DF1-467F-B52E-B0C9E44F666E"]) + XCTAssertEqual((e ~> \.others).map { $0.rawValue }, ["364B3B69-4DF1-467F-B52E-B0C9E44F666E"]) } func test_EntitySomeRelationshipsSomeAttributes() { @@ -80,25 +79,24 @@ class EntityTests: XCTestCase { XCTAssertEqual(e[\.word], "coolio") XCTAssertEqual(e[\.number], 992299) - XCTAssertEqual((e ~> \.other).rawValue.uuidString, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF") + XCTAssertEqual((e ~> \.other).rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF") } - enum TestEntityType1: EntityType { + enum TestEntityType1: EntityDescription { static var type: String { return "test_entities"} - typealias Identifier = Id - typealias AttributeType = NoAttributes - typealias RelatedType = NoRelatives + typealias Identifier = Id + typealias Attributes = NoAttributes + typealias Relationships = NoRelatives } typealias TestEntity1 = Entity - enum TestEntityType2: EntityType { + enum TestEntityType2: EntityDescription { static var type: String { return "second_test_entities"} - typealias Identifier = Id - typealias RelatedType = Relationships - typealias AttributeType = NoAttributes + typealias Identifier = Id + typealias Attributes = NoAttributes struct Relationships: JSONAPI.Relationships { let other: ToOneRelationship @@ -107,12 +105,11 @@ class EntityTests: XCTestCase { typealias TestEntity2 = Entity - enum TestEntityType3: EntityType { + enum TestEntityType3: EntityDescription { static var type: String { return "third_test_entities"} - typealias Identifier = Id - typealias RelatedType = Relationships - typealias AttributeType = NoAttributes + typealias Identifier = Id + typealias Attributes = NoAttributes struct Relationships: JSONAPI.Relationships { let others: ToManyRelationship @@ -121,18 +118,16 @@ class EntityTests: XCTestCase { typealias TestEntity3 = Entity - enum TestEntityType4: EntityType { + enum TestEntityType4: EntityDescription { static var type: String { return "fourth_test_entities"} - typealias Identifier = Id - typealias RelatedType = Relationships - typealias AttributeType = Atts + typealias Identifier = Id struct Relationships: JSONAPI.Relationships { let other: ToOneRelationship } - struct Atts: Attributes { + struct Attributes: JSONAPI.Attributes { let word: String let number: Int } @@ -140,14 +135,13 @@ class EntityTests: XCTestCase { typealias TestEntity4 = Entity - enum TestEntityType5: EntityType { + enum TestEntityType5: EntityDescription { static var type: String { return "fifth_test_entities"} - typealias Identifier = Id - typealias RelatedType = NoRelatives - typealias AttributeType = Atts + typealias Identifier = Id + typealias Relationships = NoRelatives - struct Atts: Attributes { + struct Attributes: JSONAPI.Attributes { let floater: Double } } diff --git a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift index b773fec..93cbb5f 100644 --- a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift +++ b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/12/18. // -import Foundation - let entity_no_relationships_no_attributes = """ { "id": "A24B3B69-4DF1-467F-B52E-B0C9E44F436A", diff --git a/Tests/JSONAPITests/Includes/IncludeTests.swift b/Tests/JSONAPITests/Includes/IncludeTests.swift index 996b48d..30a0d2a 100644 --- a/Tests/JSONAPITests/Includes/IncludeTests.swift +++ b/Tests/JSONAPITests/Includes/IncludeTests.swift @@ -107,16 +107,14 @@ class IncludedTests: XCTestCase { } extension IncludedTests { - enum TestEntityType: EntityType { - typealias Identifier = Id + enum TestEntityType: EntityDescription { + typealias Identifier = Id - typealias AttributeType = Atts - - typealias RelatedType = NoRelatives + typealias Relationships = NoRelatives public static var type: String { return "test_entity1" } - public struct Atts: Attributes { + public struct Attributes: JSONAPI.Attributes { let foo: String let bar: Int } @@ -124,12 +122,8 @@ extension IncludedTests { typealias TestEntity = Entity - enum TestEntityType2: EntityType { - typealias Identifier = Id - - typealias AttributeType = Atts - - typealias RelatedType = Relationships + enum TestEntityType2: EntityDescription { + typealias Identifier = Id public static var type: String { return "test_entity2" } @@ -137,7 +131,7 @@ extension IncludedTests { let entity1: ToOneRelationship } - public struct Atts: Attributes { + public struct Attributes: JSONAPI.Attributes { let foo: String let bar: Int } @@ -145,12 +139,10 @@ extension IncludedTests { typealias TestEntity2 = Entity - enum TestEntityType3: EntityType { - typealias Identifier = Id + enum TestEntityType3: EntityDescription { + typealias Identifier = Id - typealias AttributeType = NoAttributes - - typealias RelatedType = Relationships + typealias Attributes = NoAttributes public static var type: String { return "test_entity3" } @@ -162,36 +154,34 @@ extension IncludedTests { typealias TestEntity3 = Entity - enum TestEntityType4: EntityType { - typealias Identifier = Id + enum TestEntityType4: EntityDescription { + typealias Identifier = Id - typealias AttributeType = NoAttributes + typealias Attributes = NoAttributes - typealias RelatedType = NoRelatives + typealias Relationships = NoRelatives public static var type: String { return "test_entity4" } } typealias TestEntity4 = Entity - enum TestEntityType5: EntityType { - typealias Identifier = Id + enum TestEntityType5: EntityDescription { + typealias Identifier = Id - typealias AttributeType = NoAttributes + typealias Attributes = NoAttributes - typealias RelatedType = NoRelatives + typealias Relationships = NoRelatives public static var type: String { return "test_entity5" } } typealias TestEntity5 = Entity - enum TestEntityType6: EntityType { - typealias Identifier = Id + enum TestEntityType6: EntityDescription { + typealias Identifier = Id - typealias AttributeType = NoAttributes - - typealias RelatedType = Relationships + typealias Attributes = NoAttributes public static var type: String { return "test_entity6" } diff --git a/Tests/JSONAPITests/Includes/stubs/IncludeStubs.swift b/Tests/JSONAPITests/Includes/stubs/IncludeStubs.swift index 068f010..11014f0 100644 --- a/Tests/JSONAPITests/Includes/stubs/IncludeStubs.swift +++ b/Tests/JSONAPITests/Includes/stubs/IncludeStubs.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/10/18. // -import Foundation - let one_include = """ [ { diff --git a/Tests/JSONAPITests/Relationships/RelationshipTests.swift b/Tests/JSONAPITests/Relationships/RelationshipTests.swift index e07148c..7ef9204 100644 --- a/Tests/JSONAPITests/Relationships/RelationshipTests.swift +++ b/Tests/JSONAPITests/Relationships/RelationshipTests.swift @@ -37,7 +37,7 @@ class RelationshipTests: XCTestCase { XCTAssertNotNil(relationship) - XCTAssertEqual(relationship?.id.rawValue.uuidString, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF") + XCTAssertEqual(relationship?.id.rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF") XCTAssertEqual(relationship?.ids.count, 1) } @@ -46,15 +46,15 @@ class RelationshipTests: XCTestCase { XCTAssertNotNil(relationship) - XCTAssertEqual(relationship?.ids.map { $0.rawValue.uuidString }, ["2DF03B69-4B0A-467F-B52E-B0C9E44FCECF", "90F03B69-4DF1-467F-B52E-B0C9E44FC333", "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"]) + XCTAssertEqual(relationship?.ids.map { $0.rawValue }, ["2DF03B69-4B0A-467F-B52E-B0C9E44FCECF", "90F03B69-4DF1-467F-B52E-B0C9E44FC333", "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"]) } - enum TestEntityType1: EntityType { - typealias Identifier = Id + enum TestEntityType1: EntityDescription { + typealias Identifier = Id - typealias AttributeType = NoAttributes + typealias Attributes = NoAttributes - typealias RelatedType = NoRelatives + typealias Relationships = NoRelatives public static var type: String { return "test_entity1" } } diff --git a/Tests/JSONAPITests/Relationships/stubs/RelationshipStubs.swift b/Tests/JSONAPITests/Relationships/stubs/RelationshipStubs.swift index 8bdc9d6..e2508ad 100644 --- a/Tests/JSONAPITests/Relationships/stubs/RelationshipStubs.swift +++ b/Tests/JSONAPITests/Relationships/stubs/RelationshipStubs.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/12/18. // -import Foundation - let to_one_relationship = """ { "data": { diff --git a/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift b/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift index 95f10ef..5408a54 100644 --- a/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift +++ b/Tests/JSONAPITests/ResourceBody/ResourceBodyTests.swift @@ -35,12 +35,11 @@ class ResourceBodyTests: XCTestCase { ]) } - enum ArticleType: EntityType { + enum ArticleType: EntityDescription { public static var type: String { return "articles" } typealias Identifier = Id - typealias RelatedType = NoRelatives - typealias AttributeType = Attributes + typealias Relationships = NoRelatives struct Attributes: JSONAPI.Attributes { let title: String diff --git a/Tests/JSONAPITests/ResourceBody/stubs/ResourceBudyStubs.swift b/Tests/JSONAPITests/ResourceBody/stubs/ResourceBudyStubs.swift index 11166db..4f92e64 100644 --- a/Tests/JSONAPITests/ResourceBody/stubs/ResourceBudyStubs.swift +++ b/Tests/JSONAPITests/ResourceBody/stubs/ResourceBudyStubs.swift @@ -5,8 +5,6 @@ // Created by Mathew Polzin on 11/12/18. // -import Foundation - let single_resource_body = """ { "type": "articles", diff --git a/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift b/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift new file mode 100644 index 0000000..dd3c8f7 --- /dev/null +++ b/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift @@ -0,0 +1,17 @@ +// +// String+CreatableRawIdType.swift +// JSONAPITests +// +// Created by Mathew Polzin on 11/12/18. +// + +import JSONAPI + +private var uniqueStringCounter = 0 + +extension String: CreatableRawIdType { + public static func unique() -> String { + uniqueStringCounter += 1 + return String(uniqueStringCounter) + } +}