diff --git a/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift index 7a83541..f7b8052 100644 --- a/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift @@ -86,7 +86,7 @@ typealias SingleArticleDocument = Document, NoInclud func articleDocument(includeAuthor: Bool) -> Either { // Let's pretend all of this is coming from a database: - let authorId = Author.ID(rawValue: "1234") + let authorId = Author.Id(rawValue: "1234") let article = Article(id: .init(rawValue: "5678"), attributes: .init(title: .init(value: "JSON:API in Swift"), diff --git a/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift index 2628c38..678296e 100644 --- a/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift @@ -129,9 +129,9 @@ enum ArticleDocumentError: String, JSONAPIError, Codable { typealias SingleArticleDocument = JSONAPI.Document, DocumentMetadata, SingleArticleDocumentLinks, Include1, APIDescription, ArticleDocumentError> // MARK: - Instantiations -let authorId1 = Author.ID() -let authorId2 = Author.ID() -let authorId3 = Author.ID() +let authorId1 = Author.Id() +let authorId2 = Author.Id() +let authorId3 = Author.Id() let now = Date() let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: now)! @@ -155,7 +155,7 @@ let author1Links = EntityLinks(selfLink: .init(url: URL(string: "https://article meta: .init(expiry: tomorrow))) let author1 = Author(id: authorId1, attributes: .init(name: .init(value: "James Kinney")), - relationships: .init(articles: .init(ids: [article.id, Article.ID(), Article.ID()], + relationships: .init(articles: .init(ids: [article.id, Article.Id(), Article.Id()], meta: .init(pagination: .init(total: 3, limit: 50, offset: 0)), @@ -167,7 +167,7 @@ let author2Links = EntityLinks(selfLink: .init(url: URL(string: "https://article meta: .init(expiry: tomorrow))) let author2 = Author(id: authorId2, attributes: .init(name: .init(value: "James Kinney")), - relationships: .init(articles: .init(ids: [article.id, Article.ID()], + relationships: .init(articles: .init(ids: [article.id, Article.Id()], meta: .init(pagination: .init(total: 2, limit: 50, offset: 0)), diff --git a/JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift index 48296f0..1d3da07 100644 --- a/JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift @@ -20,18 +20,18 @@ let singleDogData = try! JSONEncoder().encode(singleDogDocument) // MARK: - Parse a request or response body with one Dog in it let dogResponse = try! JSONDecoder().decode(SingleDogDocument.self, from: singleDogData) let dogFromData = dogResponse.body.primaryResource?.value -let dogOwner: Person.ID? = dogFromData.flatMap { $0 ~> \.owner } +let dogOwner: Person.Id? = dogFromData.flatMap { $0 ~> \.owner } // MARK: - Parse a request or response body with one Dog in it using an alternative model typealias AltSingleDogDocument = JSONAPI.Document, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, BasicJSONAPIError> let altDogResponse = try! JSONDecoder().decode(AltSingleDogDocument.self, from: singleDogData) let altDogFromData = altDogResponse.body.primaryResource?.value -let altDogHuman: Person.ID? = altDogFromData.flatMap { $0 ~> \.human } +let altDogHuman: Person.Id? = altDogFromData.flatMap { $0 ~> \.human } // MARK: - Create a request or response with multiple people and dogs and houses included -let personIds = [Person.ID(), Person.ID()] +let personIds = [Person.Id(), Person.Id()] let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner: personIds[0]), Dog(name: "Travis", owner: personIds[1])] let houses = [House(attributes: .none, relationships: .none, meta: .none, links: .none), House(attributes: .none, relationships: .none, meta: .none, links: .none)] let people = try! [Person(id: personIds[0], name: ["Gary", "Doe"], favoriteColor: "Orange-Red", friends: [], dogs: [dogs[0], dogs[1]], home: houses[0]), Person(id: personIds[1], name: ["Elise", "Joy"], favoriteColor: "Red", friends: [], dogs: [dogs[2]], home: houses[1])] diff --git a/JSONAPI.playground/Sources/Entities.swift b/JSONAPI.playground/Sources/Entities.swift index 8b1564a..e64cfbb 100644 --- a/JSONAPI.playground/Sources/Entities.swift +++ b/JSONAPI.playground/Sources/Entities.swift @@ -63,8 +63,8 @@ public enum PersonDescription: ResourceObjectDescription { public typealias Person = ExampleEntity public extension ResourceObject where Description == PersonDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String { - init(id: Person.ID? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws { - self = Person(id: id ?? Person.ID(), attributes: .init(name: .init(value: name), favoriteColor: .init(value: favoriteColor)), relationships: .init(friends: .init(resourceObjects: friends), dogs: .init(resourceObjects: dogs), home: .init(resourceObject: home)), meta: .none, links: .none) + init(id: Person.Id? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws { + self = Person(id: id ?? Person.Id(), attributes: .init(name: .init(value: name), favoriteColor: .init(value: favoriteColor)), relationships: .init(friends: .init(resourceObjects: friends), dogs: .init(resourceObjects: dogs), home: .init(resourceObject: home)), meta: .none, links: .none) } } @@ -147,7 +147,7 @@ public extension ResourceObject where Description == DogDescription, MetaType == self = Dog(attributes: .init(name: .init(value: name)), relationships: DogDescription.Relationships(owner: .init(resourceObject: owner)), meta: .none, links: .none) } - init(name: String, owner: Person.ID) throws { + init(name: String, owner: Person.Id) throws { self = Dog(attributes: .init(name: .init(value: name)), relationships: .init(owner: .init(id: owner)), meta: .none, links: .none) } } diff --git a/README.md b/README.md index e735354..22ebb8f 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ extension String: CreatableRawIdType { // Create a typealias because we do not expect JSON:API Resource // Objects for this particular API to have Metadata or Links associated -// with them. We also expect them to have String Identifiers. +// with them. We also expect them to have String Ids. typealias JSONEntity = JSONAPI.ResourceObject // Similarly, create a typealias for unidentified entities. JSON:API @@ -220,7 +220,7 @@ typealias SingleArticleDocument = Document, NoInclud func articleDocument(includeAuthor: Bool) -> Either { // Let's pretend all of this is coming from a database: - let authorId = Author.ID(rawValue: "1234") + let authorId = Author.Id(rawValue: "1234") let article = Article(id: .init(rawValue: "5678"), attributes: .init(title: .init(value: "JSON:API in Swift"), diff --git a/Sources/JSONAPI/Resource/Relationship.swift b/Sources/JSONAPI/Resource/Relationship.swift index 0ab0dbf..88b54d5 100644 --- a/Sources/JSONAPI/Resource/Relationship.swift +++ b/Sources/JSONAPI/Resource/Relationship.swift @@ -56,25 +56,25 @@ extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks { } extension ToOneRelationship { - public init(resourceObject: T, meta: MetaType, links: LinksType) where T.ID == Identifiable.ID { + public init(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.ID { self.init(id: resourceObject.id, meta: meta, links: links) } } extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks { - public init(resourceObject: T) where T.ID == Identifiable.ID { + public init(resourceObject: T) where T.Id == Identifiable.ID { self.init(id: resourceObject.id, meta: .none, links: .none) } } extension ToOneRelationship where Identifiable: OptionalRelatable { - public init(resourceObject: T?, meta: MetaType, links: LinksType) where T.ID == Identifiable.Wrapped.ID { + public init(resourceObject: T?, meta: MetaType, links: LinksType) where T.Id == Identifiable.Wrapped.ID { self.init(id: resourceObject?.id, meta: meta, links: links) } } extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks { - public init(resourceObject: T?) where T.ID == Identifiable.Wrapped.ID { + public init(resourceObject: T?) where T.Id == Identifiable.Wrapped.ID { self.init(id: resourceObject?.id, meta: .none, links: .none) } } @@ -96,13 +96,13 @@ public struct ToManyRelationship(pointers: [ToOneRelationship], meta: MetaType, links: LinksType) where T.ID == Relatable.ID { + public init(pointers: [ToOneRelationship], meta: MetaType, links: LinksType) where T.ID == Relatable.ID { ids = pointers.map(\.id) self.meta = meta self.links = links } - public init(resourceObjects: [T], meta: MetaType, links: LinksType) where T.ID == Relatable.ID { + public init(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.ID { self.init(ids: resourceObjects.map(\.id), meta: meta, links: links) } @@ -121,7 +121,7 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks self.init(ids: ids, meta: .none, links: .none) } - public init(pointers: [ToOneRelationship]) where T.ID == Relatable.ID { + public init(pointers: [ToOneRelationship]) where T.ID == Relatable.ID { self.init(pointers: pointers, meta: .none, links: .none) } @@ -129,7 +129,7 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks return .none(withMeta: .none, links: .none) } - public init(resourceObjects: [T]) where T.ID == Relatable.ID { + public init(resourceObjects: [T]) where T.Id == Relatable.ID { self.init(resourceObjects: resourceObjects, meta: .none, links: .none) } } diff --git a/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift b/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift index 88ae539..9ac95d8 100644 --- a/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift +++ b/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift @@ -73,6 +73,8 @@ public protocol ResourceObjectProxy: Equatable, JSONTyped { associatedtype Description: ResourceObjectProxyDescription associatedtype EntityRawIdType: JSONAPI.MaybeRawId + typealias Id = JSONAPI.Id + typealias Attributes = Description.Attributes typealias Relationships = Description.Relationships @@ -80,7 +82,7 @@ public protocol ResourceObjectProxy: Equatable, JSONTyped { /// 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`. - var id: JSONAPI.Id { get } + var id: Id { get } /// The JSON API compliant attributes of this `Entity`. var attributes: Attributes { get } @@ -89,10 +91,6 @@ public protocol ResourceObjectProxy: Equatable, JSONTyped { var relationships: Relationships { get } } -extension ResourceObjectProxy { - public typealias ID = JSONAPI.Id -} - extension ResourceObjectProxy { /// The JSON API compliant "type" of this `ResourceObject`. public static var jsonType: String { return Description.jsonType } @@ -130,7 +128,7 @@ public struct ResourceObject { switch self { case .a(let a): - return ID(rawValue: a.id.rawValue) + return Id(rawValue: a.id.rawValue) case .b(let b): - return ID(rawValue: b.id.rawValue) + return Id(rawValue: b.id.rawValue) } } diff --git a/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift b/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift index bf4b6be..b7b6dca 100644 --- a/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift +++ b/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift @@ -92,8 +92,8 @@ class ResourceObjectTests: XCTestCase { let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(meta: .init(meta: .init(x: "hello", y: 5), links: .none), optionalMeta: nil, one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(resourceObject: entity1, meta: .none, links: .none), optionalMany: nil), meta: .none, links: .none) let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(meta: .init(meta: .init(x: "hello", y: 5), links: .none), optionalMeta: nil, one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(resourceObject: entity1, meta: .none, links: .none), optionalMany: .init(resourceObjects: [], meta: .none, links: .none)), meta: .none, links: .none) let e10id1 = TestEntity10.ID(rawValue: "hello") - let e10id2 = TestEntity10.ID(rawValue: "world") - let e10id3: TestEntity10.ID = "!" + let e10id2 = TestEntity10.Id(rawValue: "world") + let e10id3: TestEntity10.Id = "!" let _ = TestEntity10(id: .init(rawValue: "10"), attributes: .none, relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])), meta: .none, links: .none) XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11)), relationships: .none, meta: .none, links: .none)) let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .none) diff --git a/Tests/JSONAPITests/SwiftIdentifiableTests.swift b/Tests/JSONAPITests/SwiftIdentifiableTests.swift index d5de133..70f6738 100644 --- a/Tests/JSONAPITests/SwiftIdentifiableTests.swift +++ b/Tests/JSONAPITests/SwiftIdentifiableTests.swift @@ -25,6 +25,19 @@ final class SwiftIdentifiableTests: XCTestCase { XCTAssertEqual(hash[t1.id], String(describing: t1.id)) XCTAssertEqual(hash[t2.id], String(describing: t2.id)) } + + func test_Id_ID_equivalence() { + // it's not at all great to have both of these names for + // the Id type, but I could not do better than this and + // still have a typealias for the Id type on the + // ResourceObjectProxy protocol. One protocol's typealias + // will collide with anotehr protocol's associatedtype in + // very ugly ways. + + XCTAssert(TestType.ID.self == TestType.Id.self) + + XCTAssertEqual(TestType.ID(rawValue: "hello"), TestType.Id(rawValue: "hello")) + } } fileprivate enum TestDescription: JSONAPI.ResourceObjectDescription { diff --git a/documentation/usage.md b/documentation/usage.md index 7c11597..f138cac 100644 --- a/documentation/usage.md +++ b/documentation/usage.md @@ -167,7 +167,7 @@ typealias Relationships = NoRelationships `Relationship` values boil down to `Ids` of other resource objects. To access the `Id` of a related `ResourceObject`, you can use the custom `~>` operator with the `KeyPath` of the `Relationship` from which you want the `Id`. The friends of the above `Person` `ResourceObject` can be accessed as follows (type annotations for clarity): ```swift -let friendIds: [Person.ID] = person ~> \.friends +let friendIds: [Person.Id] = person ~> \.friends ``` ### `JSONAPI.Attributes` @@ -244,8 +244,8 @@ If your computed property is wrapped in a `AttributeType` then you can still use ### Copying/Mutating `ResourceObjects` `ResourceObject` is a value type, so copying is its default behavior. There are three common mutations you might want to make when copying a `ResourceObject`: -1. Assigning a new `ID` to the copy of an identified `ResourceObject`. -2. Assigning a new `ID` to the copy of an unidentified `ResourceObject`. +1. Assigning a new `Id` to the copy of an identified `ResourceObject`. +2. Assigning a new `Id` to the copy of an unidentified `ResourceObject`. 3. Change attribute or relationship values. The first two can be accomplished with code like the following: @@ -595,9 +595,9 @@ enum UserDescription: ResourceObjectDescription { } struct Relationships: JSONAPI.Relationships { - public var friend: (User) -> User.ID { + public var friend: (User) -> User.Id { return { user in - return User.ID(rawValue: user.friend_id) + return User.Id(rawValue: user.friend_id) } } } @@ -612,4 +612,4 @@ Given a value `user` of the above resource object type, you can access the `frie let friendId = user ~> \.friend ``` -This works because `friend` is defined in the form: `var {name}: ({ResourceObject}) -> {ID}` where `{ResourceObject}` is the `JSONAPI.ResourceObject` described by the `ResourceObjectDescription` containing the meta-relationship. +This works because `friend` is defined in the form: `var {name}: ({ResourceObject}) -> {Id}` where `{ResourceObject}` is the `JSONAPI.ResourceObject` described by the `ResourceObjectDescription` containing the meta-relationship.