32
32
ErrUnknownFieldNumberType = errors .New ("The struct field was not of a known number type" )
33
33
// ErrInvalidType is returned when the given type is incompatible with the expected type.
34
34
ErrInvalidType = errors .New ("Invalid type provided" ) // I wish we used punctuation.
35
-
35
+ // ErrBadJSONAPIJoinStruct is returned when the polyrelation type did not contain
36
+ // an appropriate join type to contain the required jsonapi node.
37
+ ErrBadJSONAPIJoinStruct = errors .New ("Invalid join struct for polymorphic relation field" )
36
38
)
37
39
38
40
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
@@ -142,6 +144,131 @@ func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) {
142
144
return models , nil
143
145
}
144
146
147
+ // jsonapiTypeOfModel returns a jsonapi primary type string
148
+ // given a struct type that has typical jsonapi struct tags
149
+ //
150
+ // Example:
151
+ // For this type, "posts" is returned. An error is returned if
152
+ // no properly-formatted "primary" tag is found for jsonapi
153
+ // annotations
154
+ //
155
+ // type Post struct {
156
+ // ID string `jsonapi:"primary,posts"`
157
+ // }
158
+ func jsonapiTypeOfModel (structModel reflect.Type ) (string , error ) {
159
+ for i := 0 ; i < structModel .NumField (); i ++ {
160
+ fieldType := structModel .Field (i )
161
+ args , err := getStructTags (fieldType )
162
+ if err != nil || len (args ) < 2 {
163
+ continue
164
+ }
165
+
166
+ if args [0 ] == annotationPrimary {
167
+ return args [1 ], nil
168
+ }
169
+ }
170
+
171
+ return "" , errors .New ("no primary annotation found on model" )
172
+ }
173
+
174
+ // structFieldIndex holds a bit of information about a type found at a struct field index
175
+ type structFieldIndex struct {
176
+ Type reflect.Type
177
+ FieldNum int
178
+ }
179
+
180
+ // joinStructMapping reflects on a value that may be a slice
181
+ // of join structs or a join struct. A join struct is a struct
182
+ // comprising of pointers to other jsonapi models, only one of
183
+ // which is populated with a value by the decoder. The join struct is
184
+ // probed and a data structure is generated that maps the
185
+ // underlying model type (its 'primary' type) to the field number
186
+ // within the join struct.
187
+ //
188
+ // This data can then be used to correctly assign each data relationship
189
+ // to the correct join struct field.
190
+ func joinStructMapping (join reflect.Type ) (result map [string ]structFieldIndex , err error ) {
191
+ result = make (map [string ]structFieldIndex )
192
+
193
+ for join .Kind () != reflect .Struct {
194
+ join = join .Elem ()
195
+ }
196
+
197
+ for i := 0 ; i < join .NumField (); i ++ {
198
+ fieldType := join .Field (i )
199
+
200
+ if fieldType .Type .Kind () != reflect .Ptr {
201
+ continue
202
+ }
203
+
204
+ subtype := fieldType .Type .Elem ()
205
+ if t , err := jsonapiTypeOfModel (subtype ); err == nil {
206
+ result [t ] = structFieldIndex {
207
+ Type : subtype ,
208
+ FieldNum : i ,
209
+ }
210
+ }
211
+ }
212
+
213
+ return result , nil
214
+ }
215
+
216
+ func getStructTags (field reflect.StructField ) ([]string , error ) {
217
+ tag := field .Tag .Get ("jsonapi" )
218
+ if tag == "" {
219
+ return []string {}, nil
220
+ }
221
+
222
+ args := strings .Split (tag , "," )
223
+ if len (args ) < 1 {
224
+ return nil , ErrBadJSONAPIStructTag
225
+ }
226
+
227
+ annotation := args [0 ]
228
+
229
+ if (annotation == annotationClientID && len (args ) != 1 ) ||
230
+ (annotation != annotationClientID && len (args ) < 2 ) {
231
+ return nil , ErrBadJSONAPIStructTag
232
+ }
233
+
234
+ return args , nil
235
+ }
236
+
237
+ // unmarshalNodeMaybeJoin populates a model that may or may not be
238
+ // a join struct that corresponds to a polyrelation or relation
239
+ func unmarshalNodeMaybeJoin (m * reflect.Value , data * Node , annotation string , joinMapping map [string ]structFieldIndex , included * map [string ]* Node ) error {
240
+ // This will hold either the value of the join model or the actual
241
+ // model, depending on annotation
242
+ var actualModel = * m
243
+ var joinElem * structFieldIndex = nil
244
+
245
+ if annotation == annotationPolyRelation {
246
+ j , ok := joinMapping [data .Type ]
247
+ if ! ok {
248
+ // There is no valid join field to assign this type of relation.
249
+ return ErrBadJSONAPIJoinStruct
250
+ }
251
+ joinElem = & j
252
+ actualModel = reflect .New (joinElem .Type )
253
+ }
254
+
255
+ if err := unmarshalNode (
256
+ fullNode (data , included ),
257
+ actualModel ,
258
+ included ,
259
+ ); err != nil {
260
+ return err
261
+ }
262
+
263
+ if joinElem != nil {
264
+ // actualModel is a pointer to the model type
265
+ // m is a pointer to a struct that should hold the actualModel at joinElem.FieldNum
266
+ v := m .Elem ()
267
+ v .Field (joinElem .FieldNum ).Set (actualModel )
268
+ }
269
+ return nil
270
+ }
271
+
145
272
func unmarshalNode (data * Node , model reflect.Value , included * map [string ]* Node ) (err error ) {
146
273
defer func () {
147
274
if r := recover (); r != nil {
@@ -155,27 +282,18 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
155
282
var er error
156
283
157
284
for i := 0 ; i < modelValue .NumField (); i ++ {
158
- fieldType := modelType .Field (i )
159
- tag := fieldType .Tag .Get ("jsonapi" )
160
- if tag == "" {
161
- continue
162
- }
163
-
164
285
fieldValue := modelValue .Field (i )
286
+ fieldType := modelType .Field (i )
165
287
166
- args := strings . Split ( tag , "," )
167
- if len ( args ) < 1 {
168
- er = ErrBadJSONAPIStructTag
288
+ args , err := getStructTags ( fieldType )
289
+ if err != nil {
290
+ er = err
169
291
break
170
292
}
171
-
172
- annotation := args [0 ]
173
-
174
- if (annotation == annotationClientID && len (args ) != 1 ) ||
175
- (annotation != annotationClientID && len (args ) < 2 ) {
176
- er = ErrBadJSONAPIStructTag
177
- break
293
+ if len (args ) == 0 {
294
+ continue
178
295
}
296
+ annotation := args [0 ]
179
297
180
298
if annotation == annotationPrimary {
181
299
// Check the JSON API Type
@@ -257,33 +375,48 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
257
375
}
258
376
259
377
assign (fieldValue , value )
260
- } else if annotation == annotationRelation {
378
+ } else if annotation == annotationRelation || annotation == annotationPolyRelation {
261
379
isSlice := fieldValue .Type ().Kind () == reflect .Slice
262
380
381
+ // No relations of the given name were provided
263
382
if data .Relationships == nil || data .Relationships [args [1 ]] == nil {
264
383
continue
265
384
}
266
385
386
+ // If this is a polymorphic relation, each data relationship needs to be assigned
387
+ // to it's appropriate join field and fieldValue should be a join field.
388
+ var joinMapping map [string ]structFieldIndex = nil
389
+ if annotation == annotationPolyRelation {
390
+ joinMapping , err = joinStructMapping (fieldValue .Type ())
391
+ if err != nil {
392
+ er = err
393
+ break
394
+ }
395
+ }
396
+
267
397
if isSlice {
268
398
// to-many relationship
269
399
relationship := new (RelationshipManyNode )
400
+ sliceType := fieldValue .Type ()
270
401
271
402
buf := bytes .NewBuffer (nil )
272
403
273
404
json .NewEncoder (buf ).Encode (data .Relationships [args [1 ]])
274
405
json .NewDecoder (buf ).Decode (relationship )
275
406
276
407
data := relationship .Data
277
- models := reflect .New (fieldValue .Type ()).Elem ()
408
+
409
+ // This will hold either the value of the slice of join models or
410
+ // the slice of models, depending on the annotation
411
+ models := reflect .New (sliceType ).Elem ()
278
412
279
413
for _ , n := range data {
280
- m := reflect .New (fieldValue .Type ().Elem ().Elem ())
414
+ // This will hold either the value of the join model or the actual
415
+ // model, depending on annotation
416
+ m := reflect .New (sliceType .Elem ().Elem ())
281
417
282
- if err := unmarshalNode (
283
- fullNode (n , included ),
284
- m ,
285
- included ,
286
- ); err != nil {
418
+ err = unmarshalNodeMaybeJoin (& m , n , annotation , joinMapping , included )
419
+ if err != nil {
287
420
er = err
288
421
break
289
422
}
@@ -313,20 +446,18 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
313
446
continue
314
447
}
315
448
449
+ // This will hold either the value of the join model or the actual
450
+ // model, depending on annotation
316
451
m := reflect .New (fieldValue .Type ().Elem ())
317
- if err := unmarshalNode (
318
- fullNode (relationship .Data , included ),
319
- m ,
320
- included ,
321
- ); err != nil {
452
+
453
+ err = unmarshalNodeMaybeJoin (& m , relationship .Data , annotation , joinMapping , included )
454
+ if err != nil {
322
455
er = err
323
456
break
324
457
}
325
458
326
459
fieldValue .Set (m )
327
-
328
460
}
329
-
330
461
} else if annotation == annotationLinks {
331
462
if data .Links == nil {
332
463
continue
0 commit comments