@@ -33,107 +33,173 @@ 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
55+ public init ( id: ( Identifiable . ID , IdMetaType ) , meta: MetaType , links: LinksType ) {
56+ self . id = id. 0
57+ self . idMeta = id. 1
58+ self . meta = meta
59+ self . links = links
60+ }
61+ }
62+
63+ extension ToOneRelationship where IdMetaType == NoIdMetadata {
4564 public init ( id: Identifiable . ID , meta: MetaType , links: LinksType ) {
4665 self . id = id
66+ self . idMeta = . none
4767 self . meta = meta
4868 self . links = links
4969 }
5070}
5171
5272extension ToOneRelationship where MetaType == NoMetadata , LinksType == NoLinks {
73+ public init ( id: ( Identifiable . ID , IdMetaType ) ) {
74+ self . init ( id: id, meta: . none, links: . none)
75+ }
76+ }
77+
78+ extension ToOneRelationship where IdMetaType == NoIdMetadata , MetaType == NoMetadata , LinksType == NoLinks {
5379 public init ( id: Identifiable . ID ) {
5480 self . init ( id: id, meta: . none, links: . none)
5581 }
5682}
5783
5884extension ToOneRelationship {
59- public init < T: ResourceObjectType > ( resourceObject: T , meta: MetaType , links: LinksType ) where T. Id == Identifiable . ID {
85+ public init < T: ResourceObjectType > ( resourceObject: T , meta: MetaType , links: LinksType ) where T. Id == Identifiable . ID , IdMetaType == NoIdMetadata {
6086 self . init ( id: resourceObject. id, meta: meta, links: links)
6187 }
6288}
6389
64- extension ToOneRelationship where MetaType == NoMetadata , LinksType == NoLinks {
90+ extension ToOneRelationship where MetaType == NoMetadata , LinksType == NoLinks , IdMetaType == NoIdMetadata {
6591 public init < T: ResourceObjectType > ( resourceObject: T ) where T. Id == Identifiable . ID {
6692 self . init ( id: resourceObject. id, meta: . none, links: . none)
6793 }
6894}
6995
70- extension ToOneRelationship where Identifiable: OptionalRelatable {
96+ extension ToOneRelationship where Identifiable: OptionalRelatable , IdMetaType == NoIdMetadata {
7197 public init < T: ResourceObjectType > ( resourceObject: T ? , meta: MetaType , links: LinksType ) where T. Id == Identifiable . Wrapped . ID {
7298 self . init ( id: resourceObject? . id, meta: meta, links: links)
7399 }
74100}
75101
76- extension ToOneRelationship where Identifiable: OptionalRelatable , MetaType == NoMetadata , LinksType == NoLinks {
102+ extension ToOneRelationship where Identifiable: OptionalRelatable , MetaType == NoMetadata , LinksType == NoLinks , IdMetaType == NoIdMetadata {
77103 public init < T: ResourceObjectType > ( resourceObject: T ? ) where T. Id == Identifiable . Wrapped . ID {
78104 self . init ( id: resourceObject? . id, meta: . none, links: . none)
79105 }
80106}
81107
82108/// An ResourceObject relationship that can be encoded to or decoded from
83109/// a JSON API "Resource Linkage."
110+ ///
84111/// See https://jsonapi.org/format/#document-resource-object-linkage
112+ ///
85113/// 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 {
114+ ///
115+ /// The `IdMetaType` (if not `NoIdMetadata`) will be parsed out of the Resource Identifier Object.
116+ /// (see https://jsonapi.org/format/#document-resource-identifier-objects)
117+ ///
118+ /// The `MetaType` (if not `NoMetadata`) will be parsed out of the Relationship Object.
119+ /// (see https://jsonapi.org/format/#document-resource-object-relationships)
120+ public struct ToManyRelationship < Relatable: JSONAPI . Relatable , IdMetaType: JSONAPI . Meta , MetaType: JSONAPI . Meta , LinksType: JSONAPI . Links > : RelationshipType , Equatable {
121+
122+ public struct ID : Equatable {
123+ public let id : Relatable . ID
124+ public let meta : IdMetaType
125+
126+ public init ( id: Relatable . ID , meta: IdMetaType ) {
127+ self . id = id
128+ self . meta = meta
129+ }
87130
88- public let ids : [ Relatable . ID ]
131+ internal init ( _ idPair: ( Relatable . ID , IdMetaType ) ) {
132+ self . init ( id: idPair. 0 , meta: idPair. 1 )
133+ }
134+ }
135+
136+ public let idsWithMeta : [ ID ]
137+
138+ public var ids : [ Relatable . ID ] {
139+ idsWithMeta. map ( \. id)
140+ }
89141
90142 public let meta : MetaType
91143 public let links : LinksType
92144
93- public init ( ids: [ Relatable . ID ] , meta: MetaType , links: LinksType ) {
94- self . ids = ids
145+ public init ( idsWithMetadata ids: [ ( Relatable . ID , IdMetaType ) ] , meta: MetaType , links: LinksType ) {
146+ self . idsWithMeta = ids. map { ID . init ( $0 ) }
95147 self . meta = meta
96148 self . links = links
97149 }
98150
99- public init < T: JSONAPIIdentifiable > ( pointers: [ ToOneRelationship < T , NoMetadata , NoLinks > ] , meta: MetaType , links: LinksType ) where T. ID == Relatable . ID {
100- ids = pointers. map ( \ . id)
151+ public init < T: JSONAPIIdentifiable > ( pointers: [ ToOneRelationship < T , NoIdMetadata , NoMetadata , NoLinks > ] , meta: MetaType , links: LinksType ) where T. ID == Relatable . ID , IdMetaType == NoIdMetadata {
152+ idsWithMeta = pointers. map { . init ( id : $0 . id, meta : . none ) }
101153 self . meta = meta
102154 self . links = links
103155 }
104156
105- public init < T: ResourceObjectType > ( resourceObjects: [ T ] , meta: MetaType , links: LinksType ) where T. Id == Relatable . ID {
157+ public init < T: ResourceObjectType > ( resourceObjects: [ T ] , meta: MetaType , links: LinksType ) where T. Id == Relatable . ID , IdMetaType == NoIdMetadata {
106158 self . init ( ids: resourceObjects. map ( \. id) , meta: meta, links: links)
107159 }
108160
109161 private init ( meta: MetaType , links: LinksType ) {
110- self . init ( ids : [ ] , meta: meta, links: links)
162+ self . init ( idsWithMetadata : [ ] , meta: meta, links: links)
111163 }
112164
113165 public static func none( withMeta meta: MetaType , links: LinksType ) -> ToManyRelationship {
114166 return ToManyRelationship ( meta: meta, links: links)
115167 }
116168}
117169
170+ extension ToManyRelationship where IdMetaType == NoIdMetadata {
171+ public init ( ids: [ Relatable . ID ] , meta: MetaType , links: LinksType ) {
172+ self . idsWithMeta = ids. map { . init( id: $0, meta: . none) }
173+ self . meta = meta
174+ self . links = links
175+ }
176+ }
177+
118178extension ToManyRelationship where MetaType == NoMetadata , LinksType == NoLinks {
119179
120- public init ( ids: [ Relatable . ID ] ) {
121- self . init ( ids : ids, meta: . none, links: . none)
180+ public init ( idsWithMetadata ids: [ ( Relatable . ID , IdMetaType ) ] ) {
181+ self . init ( idsWithMetadata : ids, meta: . none, links: . none)
122182 }
123183
124- public init < T: JSONAPIIdentifiable > ( pointers: [ ToOneRelationship < T , NoMetadata , NoLinks > ] ) where T. ID == Relatable . ID {
184+ public init < T: JSONAPIIdentifiable > ( pointers: [ ToOneRelationship < T , NoIdMetadata , NoMetadata , NoLinks > ] ) where T. ID == Relatable . ID , IdMetaType == NoIdMetadata {
125185 self . init ( pointers: pointers, meta: . none, links: . none)
126186 }
127187
128188 public static var none : ToManyRelationship {
129189 return . none( withMeta: . none, links: . none)
130190 }
131191
132- public init < T: ResourceObjectType > ( resourceObjects: [ T ] ) where T. Id == Relatable . ID {
192+ public init < T: ResourceObjectType > ( resourceObjects: [ T ] ) where T. Id == Relatable . ID , IdMetaType == NoIdMetadata {
133193 self . init ( resourceObjects: resourceObjects, meta: . none, links: . none)
134194 }
135195}
136196
197+ extension ToManyRelationship where IdMetaType == NoIdMetadata , MetaType == NoMetadata , LinksType == NoLinks {
198+ public init ( ids: [ Relatable . ID ] ) {
199+ self . init ( ids: ids, meta: . none, links: . none)
200+ }
201+ }
202+
137203public protocol JSONAPIIdentifiable : JSONTyped {
138204 associatedtype ID : Equatable
139205}
@@ -164,6 +230,7 @@ private enum ResourceLinkageCodingKeys: String, CodingKey {
164230private enum ResourceIdentifierCodingKeys : String , CodingKey {
165231 case id = " id "
166232 case entityType = " type "
233+ case metadata = " meta "
167234}
168235
169236extension MetaRelationship : Codable {
@@ -196,6 +263,9 @@ extension MetaRelationship: Codable {
196263 }
197264}
198265
266+ fileprivate protocol _Optional { }
267+ extension Optional : _Optional { }
268+
199269extension ToOneRelationship : Codable where Identifiable. ID: OptionalId {
200270 public init ( from decoder: Decoder ) throws {
201271 let container = try decoder. container ( keyedBy: ResourceLinkageCodingKeys . self)
@@ -228,6 +298,23 @@ extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
228298 )
229299 )
230300 }
301+ // if we know we aren't getting any Resource Identifer Object at all
302+ // (which we do inside this block) then we better either be expecting
303+ // no Id Metadata or optional Id Metadata or else we will report an
304+ // error.
305+ if let noIdMeta = NoIdMetadata ( ) as? IdMetaType {
306+ idMeta = noIdMeta
307+ } else if let nilMeta = anyNil as? IdMetaType {
308+ idMeta = nilMeta
309+ } else {
310+ throw DecodingError . valueNotFound (
311+ Self . self,
312+ DecodingError . Context (
313+ codingPath: decoder. codingPath,
314+ debugDescription: " Expected non-null relationship data with metadata inside. "
315+ )
316+ )
317+ }
231318 id = val
232319 return
233320 }
@@ -256,6 +343,15 @@ extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
256343 )
257344 }
258345
346+ let idMeta : IdMetaType
347+ let maybeNoIdMeta : IdMetaType ? = NoIdMetadata ( ) as? IdMetaType
348+ if let noIdMeta = maybeNoIdMeta {
349+ idMeta = noIdMeta
350+ } else {
351+ idMeta = try identifier. decode ( IdMetaType . self, forKey: . metadata)
352+ }
353+ self . idMeta = idMeta
354+
259355 id = Identifiable . ID ( rawValue: try identifier. decode ( Identifiable . ID. RawType. self, forKey: . id) )
260356 }
261357
@@ -282,6 +378,9 @@ extension ToOneRelationship: Codable where Identifiable.ID: OptionalId {
282378 var identifier = container. nestedContainer ( keyedBy: ResourceIdentifierCodingKeys . self, forKey: . data)
283379
284380 try identifier. encode ( id. rawValue, forKey: . id)
381+ if IdMetaType . self != NoMetadata . self {
382+ try identifier. encode ( idMeta, forKey: . metadata)
383+ }
285384 try identifier. encode ( Identifiable . jsonType, forKey: . entityType)
286385 }
287386}
@@ -311,10 +410,10 @@ extension ToManyRelationship: Codable {
311410 throw error
312411 }
313412 throw JSONAPICodingError . quantityMismatch ( expected: . many,
314- path: context. codingPath)
413+ path: context. codingPath)
315414 }
316415
317- var newIds = [ Relatable . ID] ( )
416+ var newIds = [ ID] ( )
318417 while !identifiers. isAtEnd {
319418 let identifier = try identifiers. nestedContainer ( keyedBy: ResourceIdentifierCodingKeys . self)
320419
@@ -324,9 +423,19 @@ extension ToManyRelationship: Codable {
324423 throw JSONAPICodingError . typeMismatch ( expected: Relatable . jsonType, found: type, path: decoder. codingPath)
325424 }
326425
327- newIds. append ( Relatable . ID ( rawValue: try identifier. decode ( Relatable . ID. RawType. self, forKey: . id) ) )
426+ let id = try identifier. decode ( Relatable . ID. RawType. self, forKey: . id)
427+
428+ let idMeta : IdMetaType
429+ let maybeNoIdMeta : IdMetaType ? = NoIdMetadata ( ) as? IdMetaType
430+ if let noIdMeta = maybeNoIdMeta {
431+ idMeta = noIdMeta
432+ } else {
433+ idMeta = try identifier. decode ( IdMetaType . self, forKey: . metadata)
434+ }
435+
436+ newIds. append ( . init( id: Relatable . ID ( rawValue: id) , meta: idMeta) )
328437 }
329- ids = newIds
438+ idsWithMeta = newIds
330439 }
331440
332441 public func encode( to encoder: Encoder ) throws {
@@ -342,10 +451,13 @@ extension ToManyRelationship: Codable {
342451
343452 var identifiers = container. nestedUnkeyedContainer ( forKey: . data)
344453
345- for id in ids {
454+ for id in idsWithMeta {
346455 var identifier = identifiers. nestedContainer ( keyedBy: ResourceIdentifierCodingKeys . self)
347456
348- try identifier. encode ( id. rawValue, forKey: . id)
457+ try identifier. encode ( id. id. rawValue, forKey: . id)
458+ if IdMetaType . self != NoMetadata . self {
459+ try identifier. encode ( id. meta, forKey: . metadata)
460+ }
349461 try identifier. encode ( Relatable . jsonType, forKey: . entityType)
350462 }
351463 }
0 commit comments