From 3c8bdcaacf5f948760b55fe359a7d18b6d6b63c4 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Fri, 29 May 2020 14:20:44 -0700 Subject: [PATCH 1/2] Rename Identifiable to JSONAPIIdentifiable and add Swift Identifiable conformance to the same types --- Sources/JSONAPI/Resource/Relationship.swift | 54 +++++++++---------- .../Resource Object/ResourceObject.swift | 19 ++++--- .../JSONAPITesting/Relationship+Literal.swift | 24 ++++----- .../NonJSONAPIRelatableTests.swift | 2 +- .../ResourceObject/ResourceObjectTests.swift | 10 ++-- .../JSONAPITests/SwiftIdentifiableTests.swift | 37 +++++++++++++ 6 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 Tests/JSONAPITests/SwiftIdentifiableTests.swift diff --git a/Sources/JSONAPI/Resource/Relationship.swift b/Sources/JSONAPI/Resource/Relationship.swift index c1003d8..8b44a81 100644 --- a/Sources/JSONAPI/Resource/Relationship.swift +++ b/Sources/JSONAPI/Resource/Relationship.swift @@ -35,14 +35,14 @@ public struct MetaRelationship /// 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: RelationshipType, Equatable { +public struct ToOneRelationship: RelationshipType, Equatable { - public let id: Identifiable.Identifier + public let id: Identifiable.ID public let meta: MetaType public let links: LinksType - public init(id: Identifiable.Identifier, meta: MetaType, links: LinksType) { + public init(id: Identifiable.ID, meta: MetaType, links: LinksType) { self.id = id self.meta = meta self.links = links @@ -50,31 +50,31 @@ public struct ToOneRelationship(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.Identifier { + 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.Identifier { + 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.Identifier { + 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.Identifier { + public init(resourceObject: T?) where T.Id == Identifiable.Wrapped.ID { self.init(id: resourceObject?.id, meta: .none, links: .none) } } @@ -85,24 +85,24 @@ extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == N /// A convenient typealias might make your code much more legible: `Many` public struct ToManyRelationship: RelationshipType, Equatable { - public let ids: [Relatable.Identifier] + public let ids: [Relatable.ID] public let meta: MetaType public let links: LinksType - public init(ids: [Relatable.Identifier], meta: MetaType, links: LinksType) { + public init(ids: [Relatable.ID], meta: MetaType, links: LinksType) { self.ids = ids self.meta = meta self.links = links } - public init(pointers: [ToOneRelationship], meta: MetaType, links: LinksType) where T.Identifier == Relatable.Identifier { + 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.Identifier { + public init(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.ID { self.init(ids: resourceObjects.map(\.id), meta: meta, links: links) } @@ -117,11 +117,11 @@ public struct ToManyRelationship(pointers: [ToOneRelationship]) where T.Identifier == Relatable.Identifier { + public init(pointers: [ToOneRelationship]) where T.ID == Relatable.ID { self.init(pointers: pointers, meta: .none, links: .none) } @@ -129,28 +129,28 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks return .none(withMeta: .none, links: .none) } - public init(resourceObjects: [T]) where T.Id == Relatable.Identifier { + public init(resourceObjects: [T]) where T.Id == Relatable.ID { self.init(resourceObjects: resourceObjects, meta: .none, links: .none) } } -public protocol Identifiable: JSONTyped { - associatedtype Identifier: Equatable +public protocol JSONAPIIdentifiable: JSONTyped { + associatedtype ID: Equatable } /// The Relatable protocol describes anything that /// has an IdType Identifier -public protocol Relatable: Identifiable where Identifier: JSONAPI.IdType { +public protocol Relatable: JSONAPIIdentifiable where ID: JSONAPI.IdType { } /// OptionalRelatable just describes an Optional /// with a Reltable Wrapped type. -public protocol OptionalRelatable: Identifiable where Identifier == Wrapped.Identifier? { +public protocol OptionalRelatable: JSONAPIIdentifiable where ID == Wrapped.ID? { associatedtype Wrapped: JSONAPI.Relatable } -extension Optional: Identifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable { - public typealias Identifier = Wrapped.Identifier? +extension Optional: JSONAPIIdentifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable { + public typealias ID = Wrapped.ID? public static var jsonType: String { return Wrapped.jsonType } } @@ -196,7 +196,7 @@ extension MetaRelationship: Codable { } } -extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId { +extension ToOneRelationship: Codable where Identifiable.ID: OptionalId { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self) @@ -219,7 +219,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId { // type at which point we can store nil in `id`. let anyNil: Any? = nil if try container.decodeNil(forKey: .data) { - guard let val = anyNil as? Identifiable.Identifier else { + guard let val = anyNil as? Identifiable.ID else { throw DecodingError.valueNotFound( Self.self, DecodingError.Context( @@ -256,7 +256,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId { ) } - id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id)) + id = Identifiable.ID(rawValue: try identifier.decode(Identifiable.ID.RawType.self, forKey: .id)) } public func encode(to encoder: Encoder) throws { @@ -273,7 +273,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId { // If id is nil, instead of {id: , type: } we will just // encode `null` let anyNil: Any? = nil - let nilId = anyNil as? Identifiable.Identifier + let nilId = anyNil as? Identifiable.ID guard id != nilId else { try container.encodeNil(forKey: .data) return @@ -314,7 +314,7 @@ extension ToManyRelationship: Codable { path: context.codingPath) } - var newIds = [Relatable.Identifier]() + var newIds = [Relatable.ID]() while !identifiers.isAtEnd { let identifier = try identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self) @@ -324,7 +324,7 @@ extension ToManyRelationship: Codable { throw JSONAPICodingError.typeMismatch(expected: Relatable.jsonType, found: type, path: decoder.codingPath) } - newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id))) + newIds.append(Relatable.ID(rawValue: try identifier.decode(Relatable.ID.RawType.self, forKey: .id))) } ids = newIds } diff --git a/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift b/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift index f5f8901..9ac95d8 100644 --- a/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift +++ b/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift @@ -163,10 +163,13 @@ extension ResourceObject: Hashable where EntityRawIdType: RawIdType { } } -extension ResourceObject: Identifiable, IdentifiableResourceObjectType, Relatable where EntityRawIdType: JSONAPI.RawIdType { - public typealias Identifier = ResourceObject.Id +extension ResourceObject: JSONAPIIdentifiable, IdentifiableResourceObjectType, Relatable where EntityRawIdType: JSONAPI.RawIdType { + public typealias ID = ResourceObject.Id } +@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension ResourceObject: Swift.Identifiable where EntityRawIdType: JSONAPI.RawIdType {} + extension ResourceObject: CustomStringConvertible { public var description: String { return "ResourceObject<\(ResourceObject.jsonType)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))" @@ -230,7 +233,7 @@ public extension ResourceObject where EntityRawIdType == Unidentified { /// Create a new `ResourceObject` from this one with the given Id. func identified(by id: RawIdType) -> ResourceObject { - return .init(id: ResourceObject.Identifier(rawValue: id), attributes: attributes, relationships: relationships, meta: meta, links: links) + return .init(id: ResourceObject.ID(rawValue: id), attributes: attributes, relationships: relationships, meta: meta, links: links) } } @@ -294,14 +297,14 @@ public extension ResourceObjectProxy { /// Access to an Id of a `ToOneRelationship`. /// This allows you to write `resourceObject ~> \.other` instead /// of `resourceObject.relationships.other.id`. - static func ~>(entity: Self, path: KeyPath>) -> OtherEntity.Identifier { + static func ~>(entity: Self, path: KeyPath>) -> OtherEntity.ID { return entity.relationships[keyPath: path].id } /// Access to an Id of an optional `ToOneRelationship`. /// This allows you to write `resourceObject ~> \.other` instead /// of `resourceObject.relationships.other?.id`. - static func ~>(entity: Self, path: KeyPath?>) -> OtherEntity.Identifier { + static func ~>(entity: Self, path: KeyPath?>) -> OtherEntity.ID { // Implementation Note: This signature applies to `ToOneRelationship?` // whereas the one below applies to `ToOneRelationship?` return entity.relationships[keyPath: path]?.id @@ -310,7 +313,7 @@ public extension ResourceObjectProxy { /// Access to an Id of an optional `ToOneRelationship`. /// This allows you to write `resourceObject ~> \.other` instead /// of `resourceObject.relationships.other?.id`. - static func ~>(entity: Self, path: KeyPath?>) -> OtherEntity.Identifier? { + static func ~>(entity: Self, path: KeyPath?>) -> OtherEntity.ID? { // Implementation Note: This signature applies to `ToOneRelationship?` // whereas the one above applies to `ToOneRelationship?` return entity.relationships[keyPath: path]?.id @@ -319,14 +322,14 @@ public extension ResourceObjectProxy { /// Access to all Ids of a `ToManyRelationship`. /// This allows you to write `resourceObject ~> \.others` instead /// of `resourceObject.relationships.others.ids`. - static func ~>(entity: Self, path: KeyPath>) -> [OtherEntity.Identifier] { + static func ~>(entity: Self, path: KeyPath>) -> [OtherEntity.ID] { return entity.relationships[keyPath: path].ids } /// Access to all Ids of an optional `ToManyRelationship`. /// This allows you to write `resourceObject ~> \.others` instead /// of `resourceObject.relationships.others?.ids`. - static func ~>(entity: Self, path: KeyPath?>) -> [OtherEntity.Identifier]? { + static func ~>(entity: Self, path: KeyPath?>) -> [OtherEntity.ID]? { return entity.relationships[keyPath: path]?.ids } } diff --git a/Sources/JSONAPITesting/Relationship+Literal.swift b/Sources/JSONAPITesting/Relationship+Literal.swift index 551fd88..6b00511 100644 --- a/Sources/JSONAPITesting/Relationship+Literal.swift +++ b/Sources/JSONAPITesting/Relationship+Literal.swift @@ -7,39 +7,39 @@ import JSONAPI -extension ToOneRelationship: ExpressibleByNilLiteral where Identifiable.Identifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks { +extension ToOneRelationship: ExpressibleByNilLiteral where Identifiable.ID: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks { public init(nilLiteral: ()) { - self.init(id: Identifiable.Identifier(nilLiteral: ())) + self.init(id: Identifiable.ID(nilLiteral: ())) } } -extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Identifiable.Identifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks { - public typealias UnicodeScalarLiteralType = Identifiable.Identifier.UnicodeScalarLiteralType +extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Identifiable.ID: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks { + public typealias UnicodeScalarLiteralType = Identifiable.ID.UnicodeScalarLiteralType public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { - self.init(id: Identifiable.Identifier(unicodeScalarLiteral: value)) + self.init(id: Identifiable.ID(unicodeScalarLiteral: value)) } } -extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Identifiable.Identifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks { - public typealias ExtendedGraphemeClusterLiteralType = Identifiable.Identifier.ExtendedGraphemeClusterLiteralType +extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Identifiable.ID: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks { + public typealias ExtendedGraphemeClusterLiteralType = Identifiable.ID.ExtendedGraphemeClusterLiteralType public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) { - self.init(id: Identifiable.Identifier(extendedGraphemeClusterLiteral: value)) + self.init(id: Identifiable.ID(extendedGraphemeClusterLiteral: value)) } } -extension ToOneRelationship: ExpressibleByStringLiteral where Identifiable.Identifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks { - public typealias StringLiteralType = Identifiable.Identifier.StringLiteralType +extension ToOneRelationship: ExpressibleByStringLiteral where Identifiable.ID: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks { + public typealias StringLiteralType = Identifiable.ID.StringLiteralType public init(stringLiteral value: StringLiteralType) { - self.init(id: Identifiable.Identifier(stringLiteral: value)) + self.init(id: Identifiable.ID(stringLiteral: value)) } } extension ToManyRelationship: ExpressibleByArrayLiteral where MetaType == NoMetadata, LinksType == NoLinks { - public typealias ArrayLiteralElement = Relatable.Identifier + public typealias ArrayLiteralElement = Relatable.ID public init(arrayLiteral elements: ArrayLiteralElement...) { self.init(ids: elements) diff --git a/Tests/JSONAPITests/NonJSONAPIRelatable/NonJSONAPIRelatableTests.swift b/Tests/JSONAPITests/NonJSONAPIRelatable/NonJSONAPIRelatableTests.swift index 1947c8e..30e14a8 100644 --- a/Tests/JSONAPITests/NonJSONAPIRelatable/NonJSONAPIRelatableTests.swift +++ b/Tests/JSONAPITests/NonJSONAPIRelatable/NonJSONAPIRelatableTests.swift @@ -83,7 +83,7 @@ extension NonJSONAPIRelatableTests { struct NonJSONAPIEntity: Relatable, JSONTyped { static var jsonType: String { return "other" } - typealias Identifier = NonJSONAPIEntity.Id + typealias ID = NonJSONAPIEntity.Id let id: Id diff --git a/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift b/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift index bdc5bae..b7b6dca 100644 --- a/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift +++ b/Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift @@ -91,7 +91,7 @@ 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: entity1.pointer, optionalNullableOne: nil, 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: 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.Identifier(rawValue: "hello") + let e10id1 = TestEntity10.ID(rawValue: "hello") 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) @@ -901,15 +901,15 @@ extension ResourceObjectTests { typealias Attributes = NoAttributes struct Relationships: JSONAPI.Relationships { - var metaRelationship: (TestEntityWithMetaRelationship) -> TestEntity1.Identifier { + var metaRelationship: (TestEntityWithMetaRelationship) -> TestEntity1.ID { return { entity in - return TestEntity1.Identifier(rawValue: "hello") + return TestEntity1.ID(rawValue: "hello") } } - var toManyMetaRelationship: (TestEntityWithMetaRelationship) -> [TestEntity1.Identifier] { + var toManyMetaRelationship: (TestEntityWithMetaRelationship) -> [TestEntity1.ID] { return { entity in - return [TestEntity1.Identifier.id(from: "hello")] + return [TestEntity1.ID.id(from: "hello")] } } } diff --git a/Tests/JSONAPITests/SwiftIdentifiableTests.swift b/Tests/JSONAPITests/SwiftIdentifiableTests.swift new file mode 100644 index 0000000..d5de133 --- /dev/null +++ b/Tests/JSONAPITests/SwiftIdentifiableTests.swift @@ -0,0 +1,37 @@ +// +// SwiftIdentifiableTests.swift +// +// +// Created by Mathew Polzin on 5/29/20. +// + +import JSONAPI +import XCTest + +final class SwiftIdentifiableTests: XCTestCase { + @available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *) + func test_identifiableConformance() { + let t1 = TestType(attributes: .none, relationships: .none, meta: .none, links: .none) + let t2 = TestType(attributes: .none, relationships: .none, meta: .none, links: .none) + + var hash = [AnyHashable: String]() + func storeErased(_ thing: T) { + hash[thing.id] = String(describing: thing.id) + } + + storeErased(t1) + storeErased(t2) + + XCTAssertEqual(hash[t1.id], String(describing: t1.id)) + XCTAssertEqual(hash[t2.id], String(describing: t2.id)) + } +} + +fileprivate enum TestDescription: JSONAPI.ResourceObjectDescription { + static let jsonType: String = "test" + + typealias Attributes = NoAttributes + typealias Relationships = NoRelationships +} + +fileprivate typealias TestType = ResourceObject From 754255b4bdadacc310a5602660aca7b2915ea72b Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Fri, 29 May 2020 14:50:47 -0700 Subject: [PATCH 2/2] rename ResourceObject.Id to ResourceObject.ID so there is only one typealias for a ResourceObject identifying type. --- .../Contents.swift | 4 ++-- .../Contents.swift | 10 +++++----- .../PATCHing.xcplaygroundpage/Contents.swift | 2 +- .../Pages/Usage.xcplaygroundpage/Contents.swift | 6 +++--- JSONAPI.playground/Sources/Entities.swift | 8 ++++---- README.md | 4 ++-- Sources/JSONAPI/Resource/Relationship.swift | 12 ++++++------ .../Resource Object/ResourceObject.swift | 17 ++++++++--------- .../DocumentCompoundResourceTests.swift | 6 +++--- Tests/JSONAPITests/Poly/PolyProxyTests.swift | 4 ++-- .../ResourceObject/ResourceObjectTests.swift | 4 ++-- documentation/usage.md | 12 ++++++------ 12 files changed, 44 insertions(+), 45 deletions(-) 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 280a614..7a83541 100644 --- a/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift @@ -30,7 +30,7 @@ typealias UnidentifiedJSONEntity = JSONA // Create relationship typealiases because we do not expect // JSON:API Relationships for this particular API to have // Metadata or Links associated with them. -typealias ToOneRelationship = JSONAPI.ToOneRelationship +typealias ToOneRelationship = JSONAPI.ToOneRelationship typealias ToManyRelationship = JSONAPI.ToManyRelationship // Create a typealias for a Document because we do not expect @@ -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.Identifier(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 81f985b..2628c38 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.Identifier() -let authorId2 = Author.Identifier() -let authorId3 = Author.Identifier() +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.Identifier(), Article.Identifier()], + 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.Identifier()], + 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/PATCHing.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift index f916054..ded305e 100644 --- a/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift @@ -108,7 +108,7 @@ print("Received dog with owner: \(dog3 ~> \.owner)") // give the dog an owner let changedDog3 = dog3.replacingRelationships { _ in - return .init(owner: .init(id: Id(rawValue: "1"))) + return .init(owner: .init(id: ID(rawValue: "1"))) } // create a document to be used as a request body for a PATCH request diff --git a/JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift index 5917bef..48296f0 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.Identifier? = 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.Identifier? = 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.Identifier(), Person.Identifier()] +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 1bd6109..8b1564a 100644 --- a/JSONAPI.playground/Sources/Entities.swift +++ b/JSONAPI.playground/Sources/Entities.swift @@ -25,7 +25,7 @@ extension String: CreatableRawIdType { // MARK: - typealiases for convenience public typealias ExampleEntity = ResourceObject -public typealias ToOne = ToOneRelationship +public typealias ToOne = ToOneRelationship public typealias ToMany = ToManyRelationship // MARK: - A few resource objects (entities) @@ -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 d1b28a7..e735354 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ typealias UnidentifiedJSONEntity = JSONA // Create relationship typealiases because we do not expect // JSON:API Relationships for this particular API to have // Metadata or Links associated with them. -typealias ToOneRelationship = JSONAPI.ToOneRelationship +typealias ToOneRelationship = JSONAPI.ToOneRelationship typealias ToManyRelationship = JSONAPI.ToManyRelationship // Create a typealias for a Document because we do not expect @@ -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.Identifier(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 8b44a81..0ab0dbf 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) } } @@ -102,7 +102,7 @@ public struct ToManyRelationship(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) } @@ -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 9ac95d8..b068ebd 100644 --- a/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift +++ b/Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift @@ -73,7 +73,7 @@ public protocol ResourceObjectProxy: Equatable, JSONTyped { associatedtype Description: ResourceObjectProxyDescription associatedtype EntityRawIdType: JSONAPI.MaybeRawId - typealias Id = JSONAPI.Id + typealias ID = JSONAPI.Id typealias Attributes = Description.Attributes typealias Relationships = Description.Relationships @@ -82,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: Id { get } + var id: ID { get } /// The JSON API compliant attributes of this `Entity`. var attributes: Attributes { get } @@ -121,6 +121,7 @@ public protocol IdentifiableResourceObjectType: ResourceObjectType, Relatable wh /// See https://jsonapi.org/format/#document-resource-objects public struct ResourceObject: ResourceObjectType { + public typealias ID = JSONAPI.Id public typealias Meta = MetaType public typealias Links = LinksType @@ -128,7 +129,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 b7b6dca..bf4b6be 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/documentation/usage.md b/documentation/usage.md index a295347..7c11597 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.Identifier] = 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 `Identifier` to the copy of an identified `ResourceObject`. -2. Assigning a new `Identifier` 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.Identifier { + public var friend: (User) -> User.ID { return { user in - return User.Identifier(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}) -> {Identifier}` 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.