Skip to content

Commit 641bf65

Browse files
committed
WIP adding id metadata to relationships.
1 parent f64ef95 commit 641bf65

22 files changed

Lines changed: 293 additions & 120 deletions

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/JSONAPI/Meta/Meta.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ public struct NoMetadata: Meta, CustomStringConvertible {
2828

2929
public var description: String { return "No Metadata" }
3030
}
31+
32+
/// The type of metadata found in a Resource Identifier Object.
33+
///
34+
/// It is sometimes more legible to differentiate between types of metadata
35+
/// even when the underlying type is the same. This typealias is only here
36+
/// to make code more easily understandable.
37+
public typealias NoIdMetadata = NoMetadata

Sources/JSONAPI/Resource/Relationship.swift

Lines changed: 119 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,81 +33,136 @@ public struct MetaRelationship<MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>
3333

3434
/// A `ResourceObject` relationship that can be encoded to or decoded from
3535
/// a JSON API "Resource Linkage."
36+
///
3637
/// See https://jsonapi.org/format/#document-resource-object-linkage
38+
///
3739
/// A convenient typealias might make your code much more legible: `One<ResourceObjectDescription>`
38-
public struct ToOneRelationship<Identifiable: JSONAPI.JSONAPIIdentifiable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
40+
///
41+
/// The `IdMetaType` (if not `NoIdMetadata`) will be parsed out of the Resource Identifier Object.
42+
/// (see https://jsonapi.org/format/#document-resource-identifier-objects)
43+
///
44+
/// The `MetaType` (if not `NoMetadata`) will be parsed out of the Relationship Object.
45+
/// (see https://jsonapi.org/format/#document-resource-object-relationships)
46+
public struct ToOneRelationship<Identifiable: JSONAPI.JSONAPIIdentifiable, IdMetaType: JSONAPI.Meta, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
3947

4048
public let id: Identifiable.ID
4149

50+
public let idMeta: IdMetaType
51+
4252
public let meta: MetaType
4353
public let links: LinksType
4454

45-
public init(id: Identifiable.ID, meta: MetaType, links: LinksType) {
55+
public init(id: Identifiable.ID, meta: MetaType, links: LinksType) where IdMetaType == NoIdMetadata {
4656
self.id = id
57+
self.idMeta = .none
58+
self.meta = meta
59+
self.links = links
60+
}
61+
62+
public init(id: (Identifiable.ID, IdMetaType), meta: MetaType, links: LinksType) {
63+
self.id = id.0
64+
self.idMeta = id.1
4765
self.meta = meta
4866
self.links = links
4967
}
5068
}
5169

5270
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
53-
public init(id: Identifiable.ID) {
71+
public init(id: Identifiable.ID) where IdMetaType == NoIdMetadata {
72+
self.init(id: id, meta: .none, links: .none)
73+
}
74+
75+
public init(id: (Identifiable.ID, IdMetaType)) {
5476
self.init(id: id, meta: .none, links: .none)
5577
}
5678
}
5779

5880
extension ToOneRelationship {
59-
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.ID {
81+
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.ID, IdMetaType == NoIdMetadata {
6082
self.init(id: resourceObject.id, meta: meta, links: links)
6183
}
6284
}
6385

64-
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
86+
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks, IdMetaType == NoIdMetadata {
6587
public init<T: ResourceObjectType>(resourceObject: T) where T.Id == Identifiable.ID {
6688
self.init(id: resourceObject.id, meta: .none, links: .none)
6789
}
6890
}
6991

70-
extension ToOneRelationship where Identifiable: OptionalRelatable {
92+
extension ToOneRelationship where Identifiable: OptionalRelatable, IdMetaType == NoIdMetadata {
7193
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.Id == Identifiable.Wrapped.ID {
7294
self.init(id: resourceObject?.id, meta: meta, links: links)
7395
}
7496
}
7597

76-
extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks {
98+
extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks, IdMetaType == NoIdMetadata {
7799
public init<T: ResourceObjectType>(resourceObject: T?) where T.Id == Identifiable.Wrapped.ID {
78100
self.init(id: resourceObject?.id, meta: .none, links: .none)
79101
}
80102
}
81103

82104
/// An ResourceObject relationship that can be encoded to or decoded from
83105
/// a JSON API "Resource Linkage."
106+
///
84107
/// See https://jsonapi.org/format/#document-resource-object-linkage
108+
///
85109
/// A convenient typealias might make your code much more legible: `Many<ResourceObjectDescription>`
86-
public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
110+
///
111+
/// The `IdMetaType` (if not `NoIdMetadata`) will be parsed out of the Resource Identifier Object.
112+
/// (see https://jsonapi.org/format/#document-resource-identifier-objects)
113+
///
114+
/// The `MetaType` (if not `NoMetadata`) will be parsed out of the Relationship Object.
115+
/// (see https://jsonapi.org/format/#document-resource-object-relationships)
116+
public struct ToManyRelationship<Relatable: JSONAPI.Relatable, IdMetaType: JSONAPI.Meta, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
117+
118+
public struct ID: Equatable {
119+
public let id: Relatable.ID
120+
public let meta: IdMetaType
87121

88-
public let ids: [Relatable.ID]
122+
public init(id: Relatable.ID, meta: IdMetaType) {
123+
self.id = id
124+
self.meta = meta
125+
}
126+
127+
internal init(_ idPair: (Relatable.ID, IdMetaType)) {
128+
self.init(id: idPair.0, meta: idPair.1)
129+
}
130+
}
131+
132+
public let metaIds: [ID]
133+
134+
public var ids: [Relatable.ID] {
135+
metaIds.map(\.id)
136+
}
89137

90138
public let meta: MetaType
91139
public let links: LinksType
92140

93-
public init(ids: [Relatable.ID], meta: MetaType, links: LinksType) {
94-
self.ids = ids
141+
142+
public init(ids: [Relatable.ID], meta: MetaType, links: LinksType) where IdMetaType == NoIdMetadata {
143+
self.metaIds = ids.map { .init(id: $0, meta: .none) }
144+
self.meta = meta
145+
self.links = links
146+
}
147+
148+
public init(idsWithMetadata ids: [(Relatable.ID, IdMetaType)], meta: MetaType, links: LinksType) {
149+
self.metaIds = ids.map(ID.init)
95150
self.meta = meta
96151
self.links = links
97152
}
98153

99-
public init<T: JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.ID == Relatable.ID {
100-
ids = pointers.map(\.id)
154+
public init<T: JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoIdMetadata, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.ID == Relatable.ID, IdMetaType == NoIdMetadata {
155+
metaIds = pointers.map { .init(id: $0.id, meta: .none) }
101156
self.meta = meta
102157
self.links = links
103158
}
104159

105-
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.ID {
160+
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.ID, IdMetaType == NoIdMetadata {
106161
self.init(ids: resourceObjects.map(\.id), meta: meta, links: links)
107162
}
108163

109164
private init(meta: MetaType, links: LinksType) {
110-
self.init(ids: [], meta: meta, links: links)
165+
self.init(idsWithMetadata: [], meta: meta, links: links)
111166
}
112167

113168
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
@@ -117,19 +172,23 @@ public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI
117172

118173
extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks {
119174

120-
public init(ids: [Relatable.ID]) {
175+
public init(ids: [Relatable.ID]) where IdMetaType == NoIdMetadata {
121176
self.init(ids: ids, meta: .none, links: .none)
122177
}
123178

124-
public init<T: JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.ID == Relatable.ID {
179+
public init(idsWithMetadata ids: [(Relatable.ID, IdMetaType)]) {
180+
self.init(idsWithMetadata: ids, meta: .none, links: .none)
181+
}
182+
183+
public init<T: JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoIdMetadata, NoMetadata, NoLinks>]) where T.ID == Relatable.ID, IdMetaType == NoIdMetadata {
125184
self.init(pointers: pointers, meta: .none, links: .none)
126185
}
127186

128187
public static var none: ToManyRelationship {
129188
return .none(withMeta: .none, links: .none)
130189
}
131190

132-
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.Id == Relatable.ID {
191+
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.Id == Relatable.ID, IdMetaType == NoIdMetadata {
133192
self.init(resourceObjects: resourceObjects, meta: .none, links: .none)
134193
}
135194
}
@@ -164,6 +223,7 @@ private enum ResourceLinkageCodingKeys: String, CodingKey {
164223
private enum ResourceIdentifierCodingKeys: String, CodingKey {
165224
case id = "id"
166225
case entityType = "type"
226+
case metadata = "meta"
167227
}
168228

169229
extension MetaRelationship: Codable {
@@ -228,6 +288,16 @@ extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
228288
)
229289
)
230290
}
291+
guard let noIdMeta = NoIdMetadata() as? IdMetaType else {
292+
throw DecodingError.valueNotFound(
293+
Self.self,
294+
DecodingError.Context(
295+
codingPath: decoder.codingPath,
296+
debugDescription: "Expected non-null relationship data with metadata inside."
297+
)
298+
)
299+
}
300+
idMeta = noIdMeta
231301
id = val
232302
return
233303
}
@@ -256,6 +326,15 @@ extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
256326
)
257327
}
258328

329+
let idMeta: IdMetaType
330+
let maybeNoIdMeta: IdMetaType? = NoIdMetadata() as? IdMetaType
331+
if let noIdMeta = maybeNoIdMeta {
332+
idMeta = noIdMeta
333+
} else {
334+
idMeta = try identifier.decode(IdMetaType.self, forKey: .metadata)
335+
}
336+
self.idMeta = idMeta
337+
259338
id = Identifiable.ID(rawValue: try identifier.decode(Identifiable.ID.RawType.self, forKey: .id))
260339
}
261340

@@ -282,6 +361,9 @@ extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
282361
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
283362

284363
try identifier.encode(id.rawValue, forKey: .id)
364+
if IdMetaType.self != NoMetadata.self {
365+
try identifier.encode(idMeta, forKey: .metadata)
366+
}
285367
try identifier.encode(Identifiable.jsonType, forKey: .entityType)
286368
}
287369
}
@@ -311,10 +393,10 @@ extension ToManyRelationship: Codable {
311393
throw error
312394
}
313395
throw JSONAPICodingError.quantityMismatch(expected: .many,
314-
path: context.codingPath)
396+
path: context.codingPath)
315397
}
316398

317-
var newIds = [Relatable.ID]()
399+
var newIds = [ID]()
318400
while !identifiers.isAtEnd {
319401
let identifier = try identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
320402

@@ -324,9 +406,19 @@ extension ToManyRelationship: Codable {
324406
throw JSONAPICodingError.typeMismatch(expected: Relatable.jsonType, found: type, path: decoder.codingPath)
325407
}
326408

327-
newIds.append(Relatable.ID(rawValue: try identifier.decode(Relatable.ID.RawType.self, forKey: .id)))
409+
let id = try identifier.decode(Relatable.ID.RawType.self, forKey: .id)
410+
411+
let idMeta: IdMetaType
412+
let maybeNoIdMeta: IdMetaType? = NoIdMetadata() as? IdMetaType
413+
if let noIdMeta = maybeNoIdMeta {
414+
idMeta = noIdMeta
415+
} else {
416+
idMeta = try identifier.decode(IdMetaType.self, forKey: .metadata)
417+
}
418+
419+
newIds.append(.init(id: Relatable.ID(rawValue: id), meta: idMeta) )
328420
}
329-
ids = newIds
421+
metaIds = newIds
330422
}
331423

332424
public func encode(to encoder: Encoder) throws {
@@ -342,10 +434,13 @@ extension ToManyRelationship: Codable {
342434

343435
var identifiers = container.nestedUnkeyedContainer(forKey: .data)
344436

345-
for id in ids {
437+
for id in metaIds {
346438
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
347439

348-
try identifier.encode(id.rawValue, forKey: .id)
440+
try identifier.encode(id.id.rawValue, forKey: .id)
441+
if IdMetaType.self != NoMetadata.self {
442+
try identifier.encode(id.meta, forKey: .metadata)
443+
}
349444
try identifier.encode(Relatable.jsonType, forKey: .entityType)
350445
}
351446
}

Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,12 @@ public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
203203
/// A `ResourceObject.Pointer` is a `ToOneRelationship` with no metadata or links.
204204
/// This is just a convenient way to reference a `ResourceObject` so that
205205
/// other ResourceObjects' Relationships can be built up from it.
206-
typealias Pointer = ToOneRelationship<ResourceObject, NoMetadata, NoLinks>
206+
typealias Pointer = ToOneRelationship<ResourceObject, NoIdMetadata, NoMetadata, NoLinks>
207207

208208
/// `ResourceObject.Pointers` is a `ToManyRelationship` with no metadata or links.
209209
/// This is just a convenient way to reference a bunch of ResourceObjects so
210210
/// that other ResourceObjects' Relationships can be built up from them.
211-
typealias Pointers = ToManyRelationship<ResourceObject, NoMetadata, NoLinks>
211+
typealias Pointers = ToManyRelationship<ResourceObject, NoIdMetadata, NoMetadata, NoLinks>
212212

213213
/// Get a pointer to this resource object that can be used as a
214214
/// relationship to another resource object.
@@ -218,7 +218,7 @@ public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
218218

219219
/// Get a pointer (i.e. `ToOneRelationship`) to this resource
220220
/// object with the given metadata and links attached.
221-
func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<ResourceObject, MType, LType> {
221+
func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<ResourceObject, NoIdMetadata, MType, LType> {
222222
return ToOneRelationship(resourceObject: self, meta: meta, links: links)
223223
}
224224
}
@@ -297,14 +297,14 @@ public extension ResourceObjectProxy {
297297
/// Access to an Id of a `ToOneRelationship`.
298298
/// This allows you to write `resourceObject ~> \.other` instead
299299
/// of `resourceObject.relationships.other.id`.
300-
static func ~><OtherEntity: JSONAPIIdentifiable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.ID {
300+
static func ~><OtherEntity: JSONAPIIdentifiable, IdMType: JSONAPI.Meta, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, IdMType, MType, LType>>) -> OtherEntity.ID {
301301
return entity.relationships[keyPath: path].id
302302
}
303303

304304
/// Access to an Id of an optional `ToOneRelationship`.
305305
/// This allows you to write `resourceObject ~> \.other` instead
306306
/// of `resourceObject.relationships.other?.id`.
307-
static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.ID {
307+
static func ~><OtherEntity: OptionalRelatable, IdMType: JSONAPI.Meta, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, IdMType, MType, LType>?>) -> OtherEntity.ID {
308308
// Implementation Note: This signature applies to `ToOneRelationship<E?, _, _>?`
309309
// whereas the one below applies to `ToOneRelationship<E, _, _>?`
310310
return entity.relationships[keyPath: path]?.id
@@ -313,7 +313,7 @@ public extension ResourceObjectProxy {
313313
/// Access to an Id of an optional `ToOneRelationship`.
314314
/// This allows you to write `resourceObject ~> \.other` instead
315315
/// of `resourceObject.relationships.other?.id`.
316-
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.ID? {
316+
static func ~><OtherEntity: Relatable, IdMType: JSONAPI.Meta, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, IdMType, MType, LType>?>) -> OtherEntity.ID? {
317317
// Implementation Note: This signature applies to `ToOneRelationship<E, _, _>?`
318318
// whereas the one above applies to `ToOneRelationship<E?, _, _>?`
319319
return entity.relationships[keyPath: path]?.id
@@ -322,14 +322,14 @@ public extension ResourceObjectProxy {
322322
/// Access to all Ids of a `ToManyRelationship`.
323323
/// This allows you to write `resourceObject ~> \.others` instead
324324
/// of `resourceObject.relationships.others.ids`.
325-
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>>) -> [OtherEntity.ID] {
325+
static func ~><OtherEntity: Relatable, IdMType: JSONAPI.Meta, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, IdMType, MType, LType>>) -> [OtherEntity.ID] {
326326
return entity.relationships[keyPath: path].ids
327327
}
328328

329329
/// Access to all Ids of an optional `ToManyRelationship`.
330330
/// This allows you to write `resourceObject ~> \.others` instead
331331
/// of `resourceObject.relationships.others?.ids`.
332-
static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>?>) -> [OtherEntity.ID]? {
332+
static func ~><OtherEntity: Relatable, IdMType: JSONAPI.Meta, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, IdMType, MType, LType>?>) -> [OtherEntity.ID]? {
333333
return entity.relationships[keyPath: path]?.ids
334334
}
335335
}

0 commit comments

Comments
 (0)