Skip to content

Commit 8aa20f3

Browse files
authored
Merge pull request #70 from mattpolzin/feature/swift-identifiable
Add Swift Identifiable conformance
2 parents 1e2a87a + 754255b commit 8aa20f3

15 files changed

Lines changed: 129 additions & 90 deletions

File tree

JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ typealias UnidentifiedJSONEntity<Description: ResourceObjectDescription> = JSONA
3030
// Create relationship typealiases because we do not expect
3131
// JSON:API Relationships for this particular API to have
3232
// Metadata or Links associated with them.
33-
typealias ToOneRelationship<Entity: Identifiable> = JSONAPI.ToOneRelationship<Entity, NoMetadata, NoLinks>
33+
typealias ToOneRelationship<Entity: JSONAPIIdentifiable> = JSONAPI.ToOneRelationship<Entity, NoMetadata, NoLinks>
3434
typealias ToManyRelationship<Entity: Relatable> = JSONAPI.ToManyRelationship<Entity, NoMetadata, NoLinks>
3535

3636
// Create a typealias for a Document because we do not expect
@@ -86,7 +86,7 @@ typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoInclud
8686
func articleDocument(includeAuthor: Bool) -> Either<SingleArticleDocument, SingleArticleDocumentWithIncludes> {
8787
// Let's pretend all of this is coming from a database:
8888

89-
let authorId = Author.Identifier(rawValue: "1234")
89+
let authorId = Author.ID(rawValue: "1234")
9090

9191
let article = Article(id: .init(rawValue: "5678"),
9292
attributes: .init(title: .init(value: "JSON:API in Swift"),

JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ enum ArticleDocumentError: String, JSONAPIError, Codable {
129129
typealias SingleArticleDocument = JSONAPI.Document<SingleResourceBody<Article>, DocumentMetadata, SingleArticleDocumentLinks, Include1<Author>, APIDescription<APIDescriptionMetadata>, ArticleDocumentError>
130130

131131
// MARK: - Instantiations
132-
let authorId1 = Author.Identifier()
133-
let authorId2 = Author.Identifier()
134-
let authorId3 = Author.Identifier()
132+
let authorId1 = Author.ID()
133+
let authorId2 = Author.ID()
134+
let authorId3 = Author.ID()
135135

136136
let now = Date()
137137
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
155155
meta: .init(expiry: tomorrow)))
156156
let author1 = Author(id: authorId1,
157157
attributes: .init(name: .init(value: "James Kinney")),
158-
relationships: .init(articles: .init(ids: [article.id, Article.Identifier(), Article.Identifier()],
158+
relationships: .init(articles: .init(ids: [article.id, Article.ID(), Article.ID()],
159159
meta: .init(pagination: .init(total: 3,
160160
limit: 50,
161161
offset: 0)),
@@ -167,7 +167,7 @@ let author2Links = EntityLinks(selfLink: .init(url: URL(string: "https://article
167167
meta: .init(expiry: tomorrow)))
168168
let author2 = Author(id: authorId2,
169169
attributes: .init(name: .init(value: "James Kinney")),
170-
relationships: .init(articles: .init(ids: [article.id, Article.Identifier()],
170+
relationships: .init(articles: .init(ids: [article.id, Article.ID()],
171171
meta: .init(pagination: .init(total: 2,
172172
limit: 50,
173173
offset: 0)),

JSONAPI.playground/Pages/PATCHing.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ print("Received dog with owner: \(dog3 ~> \.owner)")
108108

109109
// give the dog an owner
110110
let changedDog3 = dog3.replacingRelationships { _ in
111-
return .init(owner: .init(id: Id(rawValue: "1")))
111+
return .init(owner: .init(id: ID(rawValue: "1")))
112112
}
113113

114114
// create a document to be used as a request body for a PATCH request

JSONAPI.playground/Pages/Usage.xcplaygroundpage/Contents.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@ let singleDogData = try! JSONEncoder().encode(singleDogDocument)
2020
// MARK: - Parse a request or response body with one Dog in it
2121
let dogResponse = try! JSONDecoder().decode(SingleDogDocument.self, from: singleDogData)
2222
let dogFromData = dogResponse.body.primaryResource?.value
23-
let dogOwner: Person.Identifier? = dogFromData.flatMap { $0 ~> \.owner }
23+
let dogOwner: Person.ID? = dogFromData.flatMap { $0 ~> \.owner }
2424

2525

2626
// MARK: - Parse a request or response body with one Dog in it using an alternative model
2727
typealias AltSingleDogDocument = JSONAPI.Document<SingleResourceBody<AlternativeDog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, BasicJSONAPIError<String>>
2828
let altDogResponse = try! JSONDecoder().decode(AltSingleDogDocument.self, from: singleDogData)
2929
let altDogFromData = altDogResponse.body.primaryResource?.value
30-
let altDogHuman: Person.Identifier? = altDogFromData.flatMap { $0 ~> \.human }
30+
let altDogHuman: Person.ID? = altDogFromData.flatMap { $0 ~> \.human }
3131

3232

3333
// MARK: - Create a request or response with multiple people and dogs and houses included
34-
let personIds = [Person.Identifier(), Person.Identifier()]
34+
let personIds = [Person.ID(), Person.ID()]
3535
let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner: personIds[0]), Dog(name: "Travis", owner: personIds[1])]
3636
let houses = [House(attributes: .none, relationships: .none, meta: .none, links: .none), House(attributes: .none, relationships: .none, meta: .none, links: .none)]
3737
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])]

JSONAPI.playground/Sources/Entities.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extension String: CreatableRawIdType {
2525

2626
// MARK: - typealiases for convenience
2727
public typealias ExampleEntity<Description: ResourceObjectDescription> = ResourceObject<Description, NoMetadata, NoLinks, String>
28-
public typealias ToOne<E: Identifiable> = ToOneRelationship<E, NoMetadata, NoLinks>
28+
public typealias ToOne<E: JSONAPIIdentifiable> = ToOneRelationship<E, NoMetadata, NoLinks>
2929
public typealias ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLinks>
3030

3131
// MARK: - A few resource objects (entities)
@@ -63,8 +63,8 @@ public enum PersonDescription: ResourceObjectDescription {
6363
public typealias Person = ExampleEntity<PersonDescription>
6464

6565
public extension ResourceObject where Description == PersonDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
66-
init(id: Person.Id? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws {
67-
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)
66+
init(id: Person.ID? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws {
67+
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)
6868
}
6969
}
7070

@@ -147,7 +147,7 @@ public extension ResourceObject where Description == DogDescription, MetaType ==
147147
self = Dog(attributes: .init(name: .init(value: name)), relationships: DogDescription.Relationships(owner: .init(resourceObject: owner)), meta: .none, links: .none)
148148
}
149149

150-
init(name: String, owner: Person.Id) throws {
150+
init(name: String, owner: Person.ID) throws {
151151
self = Dog(attributes: .init(name: .init(value: name)), relationships: .init(owner: .init(id: owner)), meta: .none, links: .none)
152152
}
153153
}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ typealias UnidentifiedJSONEntity<Description: ResourceObjectDescription> = JSONA
163163
// Create relationship typealiases because we do not expect
164164
// JSON:API Relationships for this particular API to have
165165
// Metadata or Links associated with them.
166-
typealias ToOneRelationship<Entity: Identifiable> = JSONAPI.ToOneRelationship<Entity, NoMetadata, NoLinks>
166+
typealias ToOneRelationship<Entity: JSONAPIIdentifiable> = JSONAPI.ToOneRelationship<Entity, NoMetadata, NoLinks>
167167
typealias ToManyRelationship<Entity: Relatable> = JSONAPI.ToManyRelationship<Entity, NoMetadata, NoLinks>
168168

169169
// Create a typealias for a Document because we do not expect
@@ -220,7 +220,7 @@ typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoInclud
220220
func articleDocument(includeAuthor: Bool) -> Either<SingleArticleDocument, SingleArticleDocumentWithIncludes> {
221221
// Let's pretend all of this is coming from a database:
222222

223-
let authorId = Author.Identifier(rawValue: "1234")
223+
let authorId = Author.ID(rawValue: "1234")
224224

225225
let article = Article(id: .init(rawValue: "5678"),
226226
attributes: .init(title: .init(value: "JSON:API in Swift"),

Sources/JSONAPI/Resource/Relationship.swift

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,46 +35,46 @@ public struct MetaRelationship<MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>
3535
/// a JSON API "Resource Linkage."
3636
/// See https://jsonapi.org/format/#document-resource-object-linkage
3737
/// A convenient typealias might make your code much more legible: `One<ResourceObjectDescription>`
38-
public struct ToOneRelationship<Identifiable: JSONAPI.Identifiable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
38+
public struct ToOneRelationship<Identifiable: JSONAPI.JSONAPIIdentifiable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
3939

40-
public let id: Identifiable.Identifier
40+
public let id: Identifiable.ID
4141

4242
public let meta: MetaType
4343
public let links: LinksType
4444

45-
public init(id: Identifiable.Identifier, meta: MetaType, links: LinksType) {
45+
public init(id: Identifiable.ID, meta: MetaType, links: LinksType) {
4646
self.id = id
4747
self.meta = meta
4848
self.links = links
4949
}
5050
}
5151

5252
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
53-
public init(id: Identifiable.Identifier) {
53+
public init(id: Identifiable.ID) {
5454
self.init(id: id, meta: .none, links: .none)
5555
}
5656
}
5757

5858
extension ToOneRelationship {
59-
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.Identifier {
59+
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.ID == Identifiable.ID {
6060
self.init(id: resourceObject.id, meta: meta, links: links)
6161
}
6262
}
6363

6464
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
65-
public init<T: ResourceObjectType>(resourceObject: T) where T.Id == Identifiable.Identifier {
65+
public init<T: ResourceObjectType>(resourceObject: T) where T.ID == Identifiable.ID {
6666
self.init(id: resourceObject.id, meta: .none, links: .none)
6767
}
6868
}
6969

7070
extension ToOneRelationship where Identifiable: OptionalRelatable {
71-
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.Id == Identifiable.Wrapped.Identifier {
71+
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.ID == Identifiable.Wrapped.ID {
7272
self.init(id: resourceObject?.id, meta: meta, links: links)
7373
}
7474
}
7575

7676
extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks {
77-
public init<T: ResourceObjectType>(resourceObject: T?) where T.Id == Identifiable.Wrapped.Identifier {
77+
public init<T: ResourceObjectType>(resourceObject: T?) where T.ID == Identifiable.Wrapped.ID {
7878
self.init(id: resourceObject?.id, meta: .none, links: .none)
7979
}
8080
}
@@ -85,24 +85,24 @@ extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == N
8585
/// A convenient typealias might make your code much more legible: `Many<ResourceObjectDescription>`
8686
public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
8787

88-
public let ids: [Relatable.Identifier]
88+
public let ids: [Relatable.ID]
8989

9090
public let meta: MetaType
9191
public let links: LinksType
9292

93-
public init(ids: [Relatable.Identifier], meta: MetaType, links: LinksType) {
93+
public init(ids: [Relatable.ID], meta: MetaType, links: LinksType) {
9494
self.ids = ids
9595
self.meta = meta
9696
self.links = links
9797
}
9898

99-
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.Identifier == Relatable.Identifier {
99+
public init<T: JSONAPI.JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.ID == Relatable.ID {
100100
ids = pointers.map(\.id)
101101
self.meta = meta
102102
self.links = links
103103
}
104104

105-
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.Identifier {
105+
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.ID == Relatable.ID {
106106
self.init(ids: resourceObjects.map(\.id), meta: meta, links: links)
107107
}
108108

@@ -117,40 +117,40 @@ public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI
117117

118118
extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks {
119119

120-
public init(ids: [Relatable.Identifier]) {
120+
public init(ids: [Relatable.ID]) {
121121
self.init(ids: ids, meta: .none, links: .none)
122122
}
123123

124-
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.Identifier == Relatable.Identifier {
124+
public init<T: JSONAPI.JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.ID == Relatable.ID {
125125
self.init(pointers: pointers, meta: .none, links: .none)
126126
}
127127

128128
public static var none: ToManyRelationship {
129129
return .none(withMeta: .none, links: .none)
130130
}
131131

132-
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.Id == Relatable.Identifier {
132+
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.ID == Relatable.ID {
133133
self.init(resourceObjects: resourceObjects, meta: .none, links: .none)
134134
}
135135
}
136136

137-
public protocol Identifiable: JSONTyped {
138-
associatedtype Identifier: Equatable
137+
public protocol JSONAPIIdentifiable: JSONTyped {
138+
associatedtype ID: Equatable
139139
}
140140

141141
/// The Relatable protocol describes anything that
142142
/// has an IdType Identifier
143-
public protocol Relatable: Identifiable where Identifier: JSONAPI.IdType {
143+
public protocol Relatable: JSONAPIIdentifiable where ID: JSONAPI.IdType {
144144
}
145145

146146
/// OptionalRelatable just describes an Optional
147147
/// with a Reltable Wrapped type.
148-
public protocol OptionalRelatable: Identifiable where Identifier == Wrapped.Identifier? {
148+
public protocol OptionalRelatable: JSONAPIIdentifiable where ID == Wrapped.ID? {
149149
associatedtype Wrapped: JSONAPI.Relatable
150150
}
151151

152-
extension Optional: Identifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
153-
public typealias Identifier = Wrapped.Identifier?
152+
extension Optional: JSONAPIIdentifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
153+
public typealias ID = Wrapped.ID?
154154

155155
public static var jsonType: String { return Wrapped.jsonType }
156156
}
@@ -196,7 +196,7 @@ extension MetaRelationship: Codable {
196196
}
197197
}
198198

199-
extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
199+
extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
200200
public init(from decoder: Decoder) throws {
201201
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
202202

@@ -219,7 +219,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
219219
// type at which point we can store nil in `id`.
220220
let anyNil: Any? = nil
221221
if try container.decodeNil(forKey: .data) {
222-
guard let val = anyNil as? Identifiable.Identifier else {
222+
guard let val = anyNil as? Identifiable.ID else {
223223
throw DecodingError.valueNotFound(
224224
Self.self,
225225
DecodingError.Context(
@@ -256,7 +256,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
256256
)
257257
}
258258

259-
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
259+
id = Identifiable.ID(rawValue: try identifier.decode(Identifiable.ID.RawType.self, forKey: .id))
260260
}
261261

262262
public func encode(to encoder: Encoder) throws {
@@ -273,7 +273,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
273273
// If id is nil, instead of {id: , type: } we will just
274274
// encode `null`
275275
let anyNil: Any? = nil
276-
let nilId = anyNil as? Identifiable.Identifier
276+
let nilId = anyNil as? Identifiable.ID
277277
guard id != nilId else {
278278
try container.encodeNil(forKey: .data)
279279
return
@@ -314,7 +314,7 @@ extension ToManyRelationship: Codable {
314314
path: context.codingPath)
315315
}
316316

317-
var newIds = [Relatable.Identifier]()
317+
var newIds = [Relatable.ID]()
318318
while !identifiers.isAtEnd {
319319
let identifier = try identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
320320

@@ -324,7 +324,7 @@ extension ToManyRelationship: Codable {
324324
throw JSONAPICodingError.typeMismatch(expected: Relatable.jsonType, found: type, path: decoder.codingPath)
325325
}
326326

327-
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
327+
newIds.append(Relatable.ID(rawValue: try identifier.decode(Relatable.ID.RawType.self, forKey: .id)))
328328
}
329329
ids = newIds
330330
}

0 commit comments

Comments
 (0)