• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pace / bricks / 12827718001

17 Jan 2025 10:57AM UTC coverage: 56.909% (-0.3%) from 57.237%
12827718001

Pull #384

github

monstermunchkin
Satisfy linters
Pull Request #384: Extend linting

478 of 946 new or added lines in 109 files covered. (50.53%)

133 existing lines in 53 files now uncovered.

5667 of 9958 relevant lines covered (56.91%)

21.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.89
/http/jsonapi/response.go
1
// This file is originating from https://github.com/google/jsonapi/
2
// To this file the license conditions of LICENSE apply.
3

4
package jsonapi
5

6
import (
7
        "encoding/json"
8
        "errors"
9
        "fmt"
10
        "io"
11
        "reflect"
12
        "strconv"
13
        "strings"
14
        "time"
15

16
        "github.com/shopspring/decimal"
17
)
18

19
var (
20
        // ErrBadJSONAPIStructTag is returned when the Struct field's JSON API
21
        // annotation is invalid.
22
        ErrBadJSONAPIStructTag = errors.New("bad jsonapi struct tag format")
23
        // ErrBadJSONAPIID is returned when the Struct JSON API annotated "id" field
24
        // was not a valid numeric type.
25
        ErrBadJSONAPIID = errors.New(
26
                "id should be either string, int(8,16,32,64) or uint(8,16,32,64)")
27
        // ErrExpectedSlice is returned when a variable or argument was expected to
28
        // be a slice of *Structs; MarshalMany will return this error when its
29
        // interface{} argument is invalid.
30
        ErrExpectedSlice = errors.New("models should be a slice of struct pointers")
31
        // ErrUnexpectedType is returned when marshalling an interface; the interface
32
        // had to be a pointer or a slice; otherwise this error is returned.
33
        ErrUnexpectedType = errors.New("models should be a struct pointer or slice of struct pointers")
34
)
35

36
// MarshalPayload writes a jsonapi response for one or many records. The
37
// related records are sideloaded into the "included" array. If this method is
38
// given a struct pointer as an argument it will serialize in the form
39
// "data": {...}. If this method is given a slice of pointers, this method will
40
// serialize in the form "data": [...]
41
//
42
// One Example: you could pass it, w, your http.ResponseWriter, and, models, a
43
// ptr to a Blog to be written to the response body:
44
//
45
//         func ShowBlog(w http.ResponseWriter, r *http.Request) {
46
//                 blog := &Blog{}
47
//
48
//                 w.Header().Set("Content-Type", jsonapi.MediaType)
49
//                 w.WriteHeader(http.StatusOK)
50
//
51
//                 if err := jsonapi.MarshalPayload(w, blog); err != nil {
52
//                         http.Error(w, err.Error(), http.StatusInternalServerError)
53
//                 }
54
//         }
55
//
56
// Many Example: you could pass it, w, your http.ResponseWriter, and, models, a
57
// slice of Blog struct instance pointers to be written to the response body:
58
//
59
//                 func ListBlogs(w http.ResponseWriter, r *http.Request) {
60
//            blogs := []*Blog{}
61
//
62
//                         w.Header().Set("Content-Type", jsonapi.MediaType)
63
//                         w.WriteHeader(http.StatusOK)
64
//
65
//                         if err := jsonapi.MarshalPayload(w, blogs); err != nil {
66
//                                 http.Error(w, err.Error(), http.StatusInternalServerError)
67
//                         }
68
//                 }
69
func MarshalPayload(w io.Writer, models interface{}) error {
37✔
70
        payload, err := Marshal(models)
37✔
71
        if err != nil {
42✔
72
                return err
5✔
73
        }
5✔
74

75
        return json.NewEncoder(w).Encode(payload)
32✔
76
}
77

78
// Marshal does the same as MarshalPayload except it just returns the payload
79
// and doesn't write out results. Useful if you use your own JSON rendering
80
// library.
81
func Marshal(models interface{}) (Payloader, error) {
39✔
82
        switch vals := reflect.ValueOf(models); vals.Kind() {
39✔
83
        case reflect.Slice:
7✔
84
                m, err := convertToSliceInterface(&models)
7✔
85
                if err != nil {
7✔
86
                        return nil, err
×
87
                }
×
88

89
                payload, err := marshalMany(m)
7✔
90
                if err != nil {
7✔
91
                        return nil, err
×
92
                }
×
93

94
                if linkableModels, isLinkable := models.(Linkable); isLinkable {
7✔
95
                        jl := linkableModels.JSONAPILinks()
×
96
                        if er := jl.validate(); er != nil {
×
97
                                return nil, er
×
98
                        }
×
99

UNCOV
100
                        payload.Links = linkableModels.JSONAPILinks()
×
101
                }
102

103
                if metableModels, ok := models.(Metable); ok {
7✔
104
                        payload.Meta = metableModels.JSONAPIMeta()
×
105
                }
×
106

107
                return payload, nil
7✔
108
        case reflect.Ptr:
29✔
109
                // Check that the pointer was to a struct
29✔
110
                if reflect.Indirect(vals).Kind() != reflect.Struct {
29✔
111
                        return nil, ErrUnexpectedType
×
112
                }
×
113

114
                return marshalOne(models)
29✔
115
        default:
3✔
116
                return nil, ErrUnexpectedType
3✔
117
        }
118
}
119

120
// MarshalPayloadWithoutIncluded writes a jsonapi response with one or many
121
// records, without the related records sideloaded into "included" array.
122
// If you want to serialize the relations into the "included" array see
123
// MarshalPayload.
124
//
125
// models interface{} should be either a struct pointer or a slice of struct
126
// pointers.
127
func MarshalPayloadWithoutIncluded(w io.Writer, model interface{}) error {
2✔
128
        payload, err := Marshal(model)
2✔
129
        if err != nil {
2✔
130
                return err
×
131
        }
×
132

133
        payload.clearIncluded()
2✔
134

2✔
135
        return json.NewEncoder(w).Encode(payload)
2✔
136
}
137

138
// marshalOne does the same as MarshalOnePayload except it just returns the
139
// payload and doesn't write out results. Useful is you use your JSON rendering
140
// library.
141
func marshalOne(model interface{}) (*OnePayload, error) {
29✔
142
        included := make(map[string]*Node)
29✔
143

29✔
144
        rootNode, err := visitModelNode(model, &included, true)
29✔
145
        if err != nil {
31✔
146
                return nil, err
2✔
147
        }
2✔
148

149
        payload := &OnePayload{Data: rootNode}
27✔
150

27✔
151
        payload.Included = nodeMapValues(&included)
27✔
152

27✔
153
        return payload, nil
27✔
154
}
155

156
// marshalMany does the same as MarshalManyPayload except it just returns the
157
// payload and doesn't write out results. Useful is you use your JSON rendering
158
// library.
159
func marshalMany(models []interface{}) (*ManyPayload, error) {
7✔
160
        payload := &ManyPayload{
7✔
161
                Data: []*Node{},
7✔
162
        }
7✔
163
        included := map[string]*Node{}
7✔
164

7✔
165
        for _, model := range models {
22✔
166
                node, err := visitModelNode(model, &included, true)
15✔
167
                if err != nil {
15✔
168
                        return nil, err
×
169
                }
×
170

171
                payload.Data = append(payload.Data, node)
15✔
172
        }
173

174
        payload.Included = nodeMapValues(&included)
7✔
175

7✔
176
        return payload, nil
7✔
177
}
178

179
// MarshalOnePayloadEmbedded - This method not meant to for use in
180
// implementation code, although feel free.  The purpose of this
181
// method is for use in tests.  In most cases, your request
182
// payloads for create will be embedded rather than sideloaded for
183
// related records. This method will serialize a single struct
184
// pointer into an embedded json response. In other words, there
185
// will be no, "included", array in the json all relationships will
186
// be serailized inline in the data.
187
//
188
// However, in tests, you may want to construct payloads to post
189
// to create methods that are embedded to most closely resemble
190
// the payloads that will be produced by the client. This is what
191
// this method is intended for.
192
//
193
// model interface{} should be a pointer to a struct.
194
func MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error {
2✔
195
        rootNode, err := visitModelNode(model, nil, false)
2✔
196
        if err != nil {
2✔
197
                return err
×
198
        }
×
199

200
        payload := &OnePayload{Data: rootNode}
2✔
201

2✔
202
        return json.NewEncoder(w).Encode(payload)
2✔
203
}
204

205
func visitModelNode(model interface{}, included *map[string]*Node,
206
        sideload bool,
207
) (*Node, error) {
169✔
208
        node := new(Node)
169✔
209

169✔
210
        var er error
169✔
211

169✔
212
        value := reflect.ValueOf(model)
169✔
213
        if value.IsNil() {
171✔
214
                return nil, nil
2✔
215
        }
2✔
216

217
        modelValue := value.Elem()
167✔
218
        modelType := value.Type().Elem()
167✔
219

167✔
220
        for i := 0; i < modelValue.NumField(); i++ {
1,110✔
221
                structField := modelValue.Type().Field(i)
943✔
222

943✔
223
                tag := structField.Tag.Get(annotationJSONAPI)
943✔
224
                if tag == "" {
988✔
225
                        continue
45✔
226
                }
227

228
                fieldValue := modelValue.Field(i)
898✔
229
                fieldType := modelType.Field(i)
898✔
230

898✔
231
                args := strings.Split(tag, annotationSeperator)
898✔
232

898✔
233
                if len(args) < 1 {
898✔
234
                        er = ErrBadJSONAPIStructTag
×
235
                        break
×
236
                }
237

238
                annotation := args[0]
898✔
239

898✔
240
                if (annotation == annotationClientID && len(args) != 1) ||
898✔
241
                        (annotation != annotationClientID && len(args) < 2) {
898✔
242
                        er = ErrBadJSONAPIStructTag
×
243
                        break
×
244
                }
245

246
                if annotation == annotationPrimary {
1,054✔
247
                        v := fieldValue
156✔
248

156✔
249
                        // Deal with PTRS
156✔
250
                        var kind reflect.Kind
156✔
251
                        if fieldValue.Kind() == reflect.Ptr {
158✔
252
                                kind = fieldType.Type.Elem().Kind()
2✔
253
                                v = reflect.Indirect(fieldValue)
2✔
254
                        } else {
156✔
255
                                kind = fieldType.Type.Kind()
154✔
256
                        }
154✔
257

258
                        // Handle allowed types
259
                        switch kind {
156✔
260
                        case reflect.String:
2✔
261
                                node.ID, _ = v.Interface().(string)
2✔
262
                        case reflect.Int:
107✔
263
                                val, ok := v.Interface().(int)
107✔
264
                                if !ok {
107✔
NEW
265
                                        return nil, errors.New("could not assert int")
×
NEW
266
                                }
×
267

268
                                node.ID = strconv.FormatInt(int64(val), 10)
107✔
269
                        case reflect.Int8:
×
NEW
270
                                val, ok := v.Interface().(int8)
×
NEW
271
                                if !ok {
×
NEW
272
                                        return nil, errors.New("could not assert int8")
×
NEW
273
                                }
×
274

NEW
275
                                node.ID = strconv.FormatInt(int64(val), 10)
×
276
                        case reflect.Int16:
×
NEW
277
                                val, ok := v.Interface().(int16)
×
NEW
278
                                if !ok {
×
NEW
279
                                        return nil, errors.New("could not assert int16")
×
NEW
280
                                }
×
281

NEW
282
                                node.ID = strconv.FormatInt(int64(val), 10)
×
283
                        case reflect.Int32:
×
NEW
284
                                val, ok := v.Interface().(int32)
×
NEW
285
                                if !ok {
×
NEW
286
                                        return nil, errors.New("could not assert int32")
×
NEW
287
                                }
×
288

NEW
289
                                node.ID = strconv.FormatInt(int64(val), 10)
×
290
                        case reflect.Int64:
×
NEW
291
                                val, ok := v.Interface().(int64)
×
NEW
292
                                if !ok {
×
NEW
293
                                        return nil, errors.New("could not assert int64")
×
NEW
294
                                }
×
295

NEW
296
                                node.ID = strconv.FormatInt(val, 10)
×
297
                        case reflect.Uint:
×
NEW
298
                                val, ok := v.Interface().(uint)
×
NEW
299
                                if !ok {
×
NEW
300
                                        return nil, errors.New("could not assert uint")
×
NEW
301
                                }
×
302

NEW
303
                                node.ID = strconv.FormatUint(uint64(val), 10)
×
304
                        case reflect.Uint8:
×
NEW
305
                                val, ok := v.Interface().(uint8)
×
NEW
306
                                if !ok {
×
NEW
307
                                        return nil, errors.New("could not assert uint8")
×
NEW
308
                                }
×
309

NEW
310
                                node.ID = strconv.FormatUint(uint64(val), 10)
×
311
                        case reflect.Uint16:
×
NEW
312
                                val, ok := v.Interface().(uint16)
×
NEW
313
                                if !ok {
×
NEW
314
                                        return nil, errors.New("could not assert uint16")
×
NEW
315
                                }
×
316

NEW
317
                                node.ID = strconv.FormatUint(uint64(val), 10)
×
318
                        case reflect.Uint32:
×
NEW
319
                                val, ok := v.Interface().(uint32)
×
NEW
320
                                if !ok {
×
NEW
321
                                        return nil, errors.New("could not assert uint32")
×
NEW
322
                                }
×
323

NEW
324
                                node.ID = strconv.FormatUint(uint64(val), 10)
×
325
                        case reflect.Uint64:
46✔
326
                                val, ok := v.Interface().(uint64)
46✔
327
                                if !ok {
46✔
NEW
328
                                        return nil, errors.New("could not assert uint64")
×
NEW
329
                                }
×
330

331
                                node.ID = strconv.FormatUint(val, 10)
46✔
332
                        default:
1✔
333
                                // We had a JSON float (numeric), but our field was not one of the
1✔
334
                                // allowed numeric types
1✔
335
                                er = ErrBadJSONAPIID
1✔
336
                        }
337

338
                        if er != nil {
157✔
339
                                break
1✔
340
                        }
341

342
                        node.Type = args[1]
155✔
343
                } else if annotation == annotationClientID {
879✔
344
                        clientID := fieldValue.String()
137✔
345
                        if clientID != "" {
142✔
346
                                node.ClientID = clientID
5✔
347
                        }
5✔
348
                } else if annotation == annotationAttribute {
1,100✔
349
                        var omitEmpty, iso8601 bool
495✔
350

495✔
351
                        if len(args) > 2 {
594✔
352
                                for _, arg := range args[2:] {
198✔
353
                                        switch arg {
99✔
354
                                        case annotationOmitEmpty:
84✔
355
                                                omitEmpty = true
84✔
356
                                        case annotationISO8601:
15✔
357
                                                iso8601 = true
15✔
358
                                        }
359
                                }
360
                        }
361

362
                        if node.Attributes == nil {
661✔
363
                                node.Attributes = make(map[string]json.RawMessage)
166✔
364
                        }
166✔
365

366
                        var err error
495✔
367

495✔
368
                        if fieldValue.Type() == reflect.TypeOf(decimal.Decimal{}) {
525✔
369
                                d, ok := fieldValue.Interface().(decimal.Decimal)
30✔
370
                                if !ok {
30✔
NEW
371
                                        return nil, fmt.Errorf("could not assert decimal.Decimal")
×
NEW
372
                                }
×
373

374
                                if !decimal.MarshalJSONWithoutQuotes {
30✔
375
                                        return nil, fmt.Errorf("decimal.MarshalJSONWithoutQuotes needs to be turned on to export decimals as numbers")
×
376
                                }
×
377

378
                                node.Attributes[args[1]] = json.RawMessage(d.String())
30✔
379
                        } else if fieldValue.Type() == reflect.TypeOf(new(decimal.Decimal)) {
477✔
380
                                // A decimal pointer may be nil
12✔
381
                                if fieldValue.IsNil() {
20✔
382
                                        if omitEmpty {
11✔
383
                                                continue
3✔
384
                                        }
385

386
                                        node.Attributes[args[1]] = []byte("null")
5✔
387
                                } else {
4✔
388
                                        d, ok := fieldValue.Interface().(*decimal.Decimal)
4✔
389
                                        if !ok {
4✔
NEW
390
                                                return nil, fmt.Errorf("could not assert decimal.Decimal")
×
NEW
391
                                        }
×
392

393
                                        if !decimal.MarshalJSONWithoutQuotes {
4✔
394
                                                return nil, fmt.Errorf("decimal.MarshalJSONWithoutQuotes needs to be turned on to export decimals as numbers")
×
395
                                        }
×
396

397
                                        node.Attributes[args[1]] = json.RawMessage(d.String())
4✔
398
                                }
399
                        } else if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
484✔
400
                                t, ok := fieldValue.Interface().(time.Time)
31✔
401
                                if !ok {
31✔
NEW
402
                                        return nil, fmt.Errorf("could not assert time.Time")
×
NEW
403
                                }
×
404

405
                                if t.IsZero() {
42✔
406
                                        continue
11✔
407
                                }
408

409
                                if iso8601 {
24✔
410
                                        node.Attributes[args[1]], err = json.Marshal(t.UTC().Format(iso8601TimeFormat))
4✔
411
                                } else {
20✔
412
                                        node.Attributes[args[1]], err = json.Marshal(t.Unix())
16✔
413
                                }
16✔
414

415
                                if err != nil {
20✔
416
                                        return nil, err
×
417
                                }
×
418
                        } else if fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
424✔
419
                                // A time pointer may be nil
2✔
420
                                if fieldValue.IsNil() {
3✔
421
                                        if omitEmpty {
1✔
422
                                                continue
×
423
                                        }
424

425
                                        node.Attributes[args[1]] = []byte("null")
1✔
426
                                } else {
1✔
427
                                        tm, ok := fieldValue.Interface().(*time.Time)
1✔
428
                                        if !ok {
1✔
NEW
429
                                                return nil, fmt.Errorf("could not assert time.Time")
×
NEW
430
                                        }
×
431

432
                                        if tm.IsZero() && omitEmpty {
1✔
433
                                                continue
×
434
                                        }
435

436
                                        if iso8601 {
2✔
437
                                                node.Attributes[args[1]], err = json.Marshal(tm.UTC().Format(iso8601TimeFormat))
1✔
438
                                        } else {
1✔
439
                                                node.Attributes[args[1]], err = json.Marshal(tm.Unix())
×
440
                                        }
×
441

442
                                        if err != nil {
1✔
443
                                                return nil, err
×
444
                                        }
×
445
                                }
446
                        } else {
420✔
447
                                // Dealing with a fieldValue that is not a time
420✔
448
                                emptyValue := reflect.Zero(fieldValue.Type())
420✔
449

420✔
450
                                // See if we need to omit this field
420✔
451
                                if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) {
460✔
452
                                        continue
40✔
453
                                }
454

455
                                if strAttr, ok := fieldValue.Interface().(string); ok {
577✔
456
                                        node.Attributes[args[1]], err = json.Marshal(strAttr)
197✔
457
                                } else if fieldValue.Type().Kind() == reflect.Struct {
381✔
458
                                        // We need to pass a pointer value
1✔
459
                                        ptr := reflect.New(fieldValue.Type())
1✔
460
                                        ptr.Elem().Set(fieldValue)
1✔
461

1✔
462
                                        n, err1 := visitModelNode(ptr.Interface(), nil, false)
1✔
463
                                        if err1 != nil {
1✔
464
                                                return nil, err1
×
465
                                        }
×
466

467
                                        node.Attributes[args[1]], err = json.Marshal(n.Attributes)
1✔
468
                                } else if fieldValue.Type().Kind() == reflect.Ptr && fieldValue.Elem().Kind() == reflect.Struct {
186✔
469
                                        n, err1 := visitModelNode(fieldValue.Interface(), nil, false)
4✔
470
                                        if err1 != nil {
4✔
471
                                                return nil, err1
×
472
                                        }
×
473

474
                                        node.Attributes[args[1]], err = json.Marshal(n.Attributes)
4✔
475
                                } else {
178✔
476
                                        node.Attributes[args[1]], err = json.Marshal(fieldValue.Interface())
178✔
477
                                }
178✔
478

479
                                if err != nil {
380✔
480
                                        return nil, err
×
481
                                }
×
482
                        }
483
                } else if annotation == annotationRelation {
220✔
484
                        var omitEmpty bool
110✔
485

110✔
486
                        // add support for 'omitempty' struct tag for marshaling as absent
110✔
487
                        if len(args) > 2 {
114✔
488
                                omitEmpty = args[2] == annotationOmitEmpty
4✔
489
                        }
4✔
490

491
                        isSlice := fieldValue.Type().Kind() == reflect.Slice
110✔
492
                        if omitEmpty &&
110✔
493
                                (isSlice && fieldValue.Len() < 1 ||
110✔
494
                                        (!isSlice && fieldValue.IsNil())) {
113✔
495
                                continue
3✔
496
                        }
497

498
                        if node.Relationships == nil {
161✔
499
                                node.Relationships = make(map[string]interface{})
54✔
500
                        }
54✔
501

502
                        var relLinks *Links
107✔
503
                        if linkableModel, ok := model.(RelationshipLinkable); ok {
213✔
504
                                relLinks = linkableModel.JSONAPIRelationshipLinks(args[1])
106✔
505
                        }
106✔
506

507
                        var relMeta *Meta
107✔
508
                        if metableModel, ok := model.(RelationshipMetable); ok {
213✔
509
                                relMeta = metableModel.JSONAPIRelationshipMeta(args[1])
106✔
510
                        }
106✔
511

512
                        if isSlice {
160✔
513
                                // to-many relationship
53✔
514
                                relationship, err := visitModelNodeRelationships(
53✔
515
                                        fieldValue,
53✔
516
                                        included,
53✔
517
                                        sideload,
53✔
518
                                )
53✔
519
                                if err != nil {
53✔
520
                                        er = err
×
521
                                        break
×
522
                                }
523

524
                                relationship.Links = relLinks
53✔
525
                                relationship.Meta = relMeta
53✔
526

53✔
527
                                if sideload {
98✔
528
                                        shallowNodes := []*Node{}
45✔
529

45✔
530
                                        for _, n := range relationship.Data {
107✔
531
                                                appendIncluded(included, n)
62✔
532
                                                shallowNodes = append(shallowNodes, toShallowNode(n))
62✔
533
                                        }
62✔
534

535
                                        node.Relationships[args[1]] = &RelationshipManyNode{
45✔
536
                                                Data:  shallowNodes,
45✔
537
                                                Links: relationship.Links,
45✔
538
                                                Meta:  relationship.Meta,
45✔
539
                                        }
45✔
540
                                } else {
8✔
541
                                        node.Relationships[args[1]] = relationship
8✔
542
                                }
8✔
543
                        } else {
54✔
544
                                // to-one relationships
54✔
545
                                // Handle null relationship case
54✔
546
                                if fieldValue.IsNil() {
68✔
547
                                        node.Relationships[args[1]] = &RelationshipOneNode{Data: nil}
14✔
548
                                        continue
14✔
549
                                }
550

551
                                relationship, err := visitModelNode(
40✔
552
                                        fieldValue.Interface(),
40✔
553
                                        included,
40✔
554
                                        sideload,
40✔
555
                                )
40✔
556
                                if err != nil {
40✔
557
                                        er = err
×
558
                                        break
×
559
                                }
560

561
                                if sideload {
72✔
562
                                        appendIncluded(included, relationship)
32✔
563

32✔
564
                                        node.Relationships[args[1]] = &RelationshipOneNode{
32✔
565
                                                Data:  toShallowNode(relationship),
32✔
566
                                                Links: relLinks,
32✔
567
                                                Meta:  relMeta,
32✔
568
                                        }
32✔
569
                                } else {
40✔
570
                                        node.Relationships[args[1]] = &RelationshipOneNode{
8✔
571
                                                Data:  relationship,
8✔
572
                                                Links: relLinks,
8✔
573
                                                Meta:  relMeta,
8✔
574
                                        }
8✔
575
                                }
8✔
576
                        }
UNCOV
577
                } else {
×
578
                        er = ErrBadJSONAPIStructTag
×
579
                        break
×
580
                }
581
        }
582

583
        if er != nil {
168✔
584
                return nil, er
1✔
585
        }
1✔
586

587
        if linkableModel, isLinkable := model.(Linkable); isLinkable {
220✔
588
                jl := linkableModel.JSONAPILinks()
54✔
589
                if er := jl.validate(); er != nil {
55✔
590
                        return nil, er
1✔
591
                }
1✔
592

593
                node.Links = linkableModel.JSONAPILinks()
53✔
594
        }
595

596
        if metableModel, ok := model.(Metable); ok {
218✔
597
                node.Meta = metableModel.JSONAPIMeta()
53✔
598
        }
53✔
599

600
        return node, nil
165✔
601
}
602

603
func toShallowNode(node *Node) *Node {
94✔
604
        return &Node{
94✔
605
                ID:   node.ID,
94✔
606
                Type: node.Type,
94✔
607
        }
94✔
608
}
94✔
609

610
func visitModelNodeRelationships(models reflect.Value, included *map[string]*Node,
611
        sideload bool,
612
) (*RelationshipManyNode, error) {
53✔
613
        nodes := []*Node{}
53✔
614

53✔
615
        for i := 0; i < models.Len(); i++ {
131✔
616
                n := models.Index(i).Interface()
78✔
617

78✔
618
                node, err := visitModelNode(n, included, sideload)
78✔
619
                if err != nil {
78✔
620
                        return nil, err
×
621
                }
×
622

623
                nodes = append(nodes, node)
78✔
624
        }
625

626
        return &RelationshipManyNode{Data: nodes}, nil
53✔
627
}
628

629
func appendIncluded(m *map[string]*Node, nodes ...*Node) {
94✔
630
        included := *m
94✔
631

94✔
632
        for _, n := range nodes {
188✔
633
                k := fmt.Sprintf("%s,%s", n.Type, n.ID)
94✔
634

94✔
635
                if _, hasNode := included[k]; hasNode {
155✔
636
                        continue
61✔
637
                }
638

639
                included[k] = n
33✔
640
        }
641
}
642

643
func nodeMapValues(m *map[string]*Node) []*Node {
34✔
644
        mp := *m
34✔
645
        nodes := make([]*Node, len(mp))
34✔
646

34✔
647
        i := 0
34✔
648

34✔
649
        for _, n := range mp {
67✔
650
                nodes[i] = n
33✔
651
                i++
33✔
652
        }
33✔
653

654
        return nodes
34✔
655
}
656

657
func convertToSliceInterface(i *interface{}) ([]interface{}, error) {
7✔
658
        vals := reflect.ValueOf(*i)
7✔
659
        if vals.Kind() != reflect.Slice {
7✔
660
                return nil, ErrExpectedSlice
×
661
        }
×
662

663
        var response []interface{}
7✔
664
        for x := 0; x < vals.Len(); x++ {
22✔
665
                response = append(response, vals.Index(x).Interface())
15✔
666
        }
15✔
667

668
        return response, nil
7✔
669
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc