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

openconfig / ygot / 8993994311

25 Apr 2024 11:39PM UTC coverage: 88.803% (-0.008%) from 88.811%
8993994311

push

github

web-flow
Add `GetOrCreate<ListName>Map` helper for lists. (#971)

This helps avoid nil checks when there is a need to operate on the map
field directly.

7 of 9 new or added lines in 2 files covered. (77.78%)

2 existing lines in 1 file now uncovered.

13911 of 15665 relevant lines covered (88.8%)

482.6 hits per line

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

96.27
/gogen/unordered_list.go
1
// Copyright 2023 Google Inc.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package gogen
16

17
import (
18
        "bytes"
19
        "fmt"
20
        "sort"
21

22
        "github.com/openconfig/ygot/genutil"
23
        "github.com/openconfig/ygot/ygen"
24
)
25

26
// generatedGoMultiKeyListStruct is used to represent a struct used as a key of a YANG list that has multiple
27
// key elements.
28
type generatedGoMultiKeyListStruct struct {
29
        KeyStructName string          // KeyStructName is the name of the struct being output.
30
        Keys          []goStructField // Keys is a slice of goStructFields that are contained in the key struct.
31
        ParentPath    string          // ParentPath is the path to the list's parent in the YANG schema.
32
        ListName      string          // ListName is the name of the list itself in the YANG schema.
33
}
34

35
// generatedGoListMethod contains the fields required for generating the methods
36
// that are associated with a list entry within a struct representing a YANG entity.
37
type generatedGoListMethod struct {
38
        ListName  string          // ListName is the name of the list for which the method is being generated within its parent struct.
39
        ListType  string          // ListType is the type (struct name) of the element representing the list.
40
        Keys      []goStructField // Keys of the list that is being generated (length = 1 if the list is single keyed).
41
        KeyStruct string          // KeyStruct is the name of the struct used as a key for a multi-keyed list.
42
        Receiver  string          // Receiver is the name of the parent struct of the list, which is the receiver for the generated method.
43
}
44

45
// generatedGoKeyHelper contains the fields required for generating a method
46
// associated with a struct that is within a list in the YANG schema.
47
type generatedGoKeyHelper struct {
48
        // Receiver is the name of the type which acts as a receiver for a generated method.
49
        Receiver string
50
        // Keys specifies the keys of the list, as a map from YANG to Go identifier.
51
        Keys []*yangFieldMap
52
}
53

54
var (
55
        // goListKeyTemplate takes an input generatedGoMultiKeyListStruct, which is used to
56
        // describe the key of a list that has multiple keys, and generates a Go
57
        // struct definition that can be used to represent that key. For example, if a
58
        // YANG container or list contains a list, L:
59
        //
60
        //        container A {
61
        //                list L {
62
        //                        key "key-one key-two";
63
        //                        leaf key-one { type string; }
64
        //                        leaf key-two { type uint32; }
65
        //                }
66
        //        }
67
        //
68
        // A struct is generated to represent the key of list L, of the following form:
69
        //
70
        //        type A_L_Key struct {
71
        //                KeyOne        string
72
        //                KeyTwo        uint32
73
        //        }
74
        //
75
        // This struct is then used as the key of the map representing the list L, in
76
        // the generated struct representing the container A.
77
        goListKeyTemplate = mustMakeTemplate("listkey", `
78
// {{ .KeyStructName }} represents the key for list {{ .ListName }} of element {{ .ParentPath }}.
79
type {{ .KeyStructName }} struct {
80
{{- range $idx, $key := .Keys }}
81
        {{ $key.Name }}        {{ $key.Type }}        `+"`{{ $key.Tags }}`"+`
82
{{- end }}
83
}
84

85
// IsYANGGoKeyStruct ensures that {{ .KeyStructName }} partially implements the
86
// yang.GoKeyStruct interface. This allows functions that need to
87
// handle this key struct to identify it as being generated by gogen.
88
func ({{ .KeyStructName }}) IsYANGGoKeyStruct() {}
89

90
// ΛListKeyMap returns the values of the {{ .KeyStructName }} key struct.
91
func (t {{ .KeyStructName }}) ΛListKeyMap() (map[string]interface{}, error) {
92
        return map[string]interface{}{
93
                {{- range $key := .Keys }}
94
                "{{ $key.YANGName }}": t.{{ $key.Name }},
95
                {{- end }}
96
        }, nil
97
}
98
`)
99

100
        // goNewListMemberTemplate takes an input generatedGoListMethod struct and
101
        // outputs a method, using the specified receiver, that creates a new instance
102
        // of a struct within a keyed YANG list, and populates the map key, and the
103
        // key fields of the list's struct according to the input arguments of the
104
        // function.
105
        goNewListMemberTemplate = mustMakeTemplate("newListEntry", `
106
// New{{ .ListName }} creates a new entry in the {{ .ListName }} list of the
107
// {{ .Receiver}} struct. The keys of the list are populated from the input
108
// arguments.
109
func (t *{{ .Receiver }}) New{{ .ListName }}(
110
  {{- $length := len .Keys -}}
111
  {{- range $i, $key := .Keys -}}
112
        {{ $key.Name }} {{ $key.Type -}}
113
        {{- if ne (inc $i) $length -}}, {{ end -}}
114
  {{- end -}}
115
  ) (*{{ .ListType }}, error){
116

117
        // Initialise the list within the receiver struct if it has not already been
118
        // created.
119
        if t.{{ .ListName }} == nil {
120
                {{- if ne .KeyStruct "" }}
121
                t.{{ .ListName }} = make(map[{{ .KeyStruct }}]*{{ .ListType }})
122
                {{- else }}
123
                        {{- $listName := .ListName -}}
124
                        {{- $listType := .ListType -}}
125
                        {{- range $key := .Keys }}
126
                t.{{ $listName }} = make(map[{{ $key.Type }}]*{{ $listType }})
127
                        {{- end }} {{- end }}
128
        }
129

130
        {{ if ne .KeyStruct "" -}}
131
        key := {{ .KeyStruct }}{
132
                {{- range $key := .Keys }}
133
                {{ $key.Name }}: {{ $key.Name }},
134
                {{- end }}
135
        }
136
        {{- else -}}
137
        {{- range $key := .Keys -}}
138
        key := {{ $key.Name }}
139
        {{- end -}}
140
        {{- end }}
141

142
        // Ensure that this key has not already been used in the
143
        // list. Keyed YANG lists do not allow duplicate keys to
144
        // be created.
145
        if _, ok := t.{{ .ListName }}[key]; ok {
146
                return nil, fmt.Errorf("duplicate key %v for list {{ .ListName }}", key)
147
        }
148

149
        t.{{ .ListName }}[key] = &{{ .ListType }}{
150
                {{- range $key := .Keys }}
151
                {{- if $key.IsScalarField }}
152
                {{ $key.Name }}: &{{ $key.Name }},
153
                {{- else }}
154
                {{ $key.Name }}: {{ $key.Name }},
155
                {{- end -}}
156
                {{- end }}
157
        }
158

159
        return t.{{ .ListName }}[key], nil
160
}
161
`)
162

163
        // goListGetterTemplate defines a template for a function that, for a particular
164
        // list key, gets an existing map value.
165
        goListGetterTemplate = mustMakeTemplate("getList", `
166
// Get{{ .ListName }} retrieves the value with the specified key from
167
// the {{ .ListName }} map field of {{ .Receiver }}. If the receiver is nil, or
168
// the specified key is not present in the list, nil is returned such that Get*
169
// methods may be safely chained.
170
func (t *{{ .Receiver }}) Get{{ .ListName }}(
171
  {{- $length := len .Keys -}}
172
  {{- range $i, $key := .Keys -}}
173
        {{ $key.Name }} {{ $key.Type -}}
174
        {{- if ne (inc $i) $length -}}, {{ end -}}
175
  {{- end -}}
176
  ) (*{{ .ListType }}){
177

178
        if t == nil {
179
                return nil
180
        }
181

182
  {{ if ne .KeyStruct "" -}}
183
        key := {{ .KeyStruct }}{
184
                {{- range $key := .Keys }}
185
                {{ $key.Name }}: {{ $key.Name }},
186
                {{- end }}
187
        }
188
        {{- else -}}
189
        {{- range $key := .Keys -}}
190
        key := {{ $key.Name }}
191
        {{- end -}}
192
        {{- end }}
193

194
  if lm, ok := t.{{ .ListName }}[key]; ok {
195
    return lm
196
  }
197
  return nil
198
}
199
`)
200

201
        // goGetOrCreateListElementTemplate defines a template for a function that, for a
202
        // particular list key, gets an existing map value, or creates it if it doesn't
203
        // exist.
204
        goGetOrCreateListElementTemplate = mustMakeTemplate("getOrCreateListElement", `
205
// GetOrCreate{{ .ListName }} retrieves the value with the specified keys from
206
// the receiver {{ .Receiver }}. If the entry does not exist, then it is created.
207
// It returns the existing or new list member.
208
func (t *{{ .Receiver }}) GetOrCreate{{ .ListName }}(
209
  {{- $length := len .Keys -}}
210
  {{- range $i, $key := .Keys -}}
211
        {{ $key.Name }} {{ $key.Type -}}
212
        {{- if ne (inc $i) $length -}}, {{ end -}}
213
  {{- end -}}
214
  ) (*{{ .ListType }}){
215

216
        {{ if ne .KeyStruct "" -}}
217
        key := {{ .KeyStruct }}{
218
                {{- range $key := .Keys }}
219
                {{ $key.Name }}: {{ $key.Name }},
220
                {{- end }}
221
        }
222
        {{- else -}}
223
        {{- range $key := .Keys -}}
224
        key := {{ $key.Name }}
225
        {{- end -}}
226
        {{- end }}
227

228
        if v, ok := t.{{ .ListName }}[key]; ok {
229
                return v
230
        }
231
        // Panic if we receive an error, since we should have retrieved an existing
232
        // list member. This allows chaining of GetOrCreate methods.
233
        v, err := t.New{{ .ListName }}(
234
                {{- range $i, $key := .Keys -}}
235
                {{ $key.Name }}
236
                {{- if ne (inc $i) $length -}}, {{ end -}}
237
                {{- end -}})
238
        if err != nil {
239
                panic(fmt.Sprintf("GetOrCreate{{ .ListName }} got unexpected error: %v", err))
240
        }
241
        return v
242
}
243
`)
244

245
        // goGetOrCreateListTemplate defines a template for a function that
246
        // returns the current list. It also creates it if it doesn't exist.
247
        goGetOrCreateListTemplate = mustMakeTemplate("getOrCreateList", `
248
// GetOrCreate{{ .ListName }}Map returns the list (map) from {{ .Receiver }}.
249
//
250
// It initializes the field if not already initialized.
251
func (t *{{ .Receiver }}) GetOrCreate{{ .ListName }}Map() {{ if ne .KeyStruct "" -}}
252
                map[{{ .KeyStruct }}]*{{ .ListType }}
253
                {{- else }}
254
                        {{- $listName := .ListName -}}
255
                        {{- $listType := .ListType -}}
256
                        {{- range $key := .Keys -}}
257
                map[{{ $key.Type }}]*{{ $listType }}
258
                        {{- end }}
259
                {{- end }} {
260
        if t.{{ .ListName }} == nil {
261
                {{- if ne .KeyStruct "" }}
262
                t.{{ .ListName }} = make(map[{{ .KeyStruct }}]*{{ .ListType }})
263
                {{- else }}
264
                        {{- $listName := .ListName -}}
265
                        {{- $listType := .ListType -}}
266
                        {{- range $key := .Keys }}
267
                t.{{ $listName }} = make(map[{{ $key.Type }}]*{{ $listType }})
268
                        {{- end }}
269
                {{- end }}
270
        }
271
        return t.{{ .ListName }}
272
}
273
`)
274

275
        // goDeleteListTemplate defines a template for a function that, for a
276
        // particular list key, deletes an existing map value.
277
        goDeleteListTemplate = mustMakeTemplate("deleteList", `
278
// Delete{{ .ListName }} deletes the value with the specified keys from
279
// the receiver {{ .Receiver }}. If there is no such element, the function
280
// is a no-op.
281
func (t *{{ .Receiver }}) Delete{{ .ListName }}(
282
  {{- $length := len .Keys -}}
283
  {{- range $i, $key := .Keys -}}
284
        {{ $key.Name }} {{ $key.Type -}}
285
        {{- if ne (inc $i) $length -}}, {{ end -}}
286
  {{- end -}}
287
  ) {
288
        {{ if ne .KeyStruct "" -}}
289
        key := {{ .KeyStruct }}{
290
                {{- range $key := .Keys }}
291
                {{ $key.Name }}: {{ $key.Name }},
292
                {{- end }}
293
        }
294
        {{- else -}}
295
        {{- range $key := .Keys -}}
296
        key := {{ $key.Name }}
297
        {{- end -}}
298
        {{- end }}
299

300
        delete(t.{{ .ListName }}, key)
301
}
302
`)
303

304
        // goListAppendTemplate defines a template for a function that takes an
305
        // input list member struct, extracts the key value, and appends it to a map.
306
        // In this template, since all list keys are specified to be pointer types
307
        // within values by default, we must invert the "IsScalarField" check to
308
        // ensure that we dereference elements that are pointers in the generated
309
        // code.
310
        goListAppendTemplate = mustMakeTemplate("appendList", `
311
// Append{{ .ListName }} appends the supplied {{ .ListType }} struct to the
312
// list {{ .ListName }} of {{ .Receiver }}. If the key value(s) specified in
313
// the supplied {{ .ListType }} already exist in the list, an error is
314
// returned.
315
func (t *{{ .Receiver }}) Append{{ .ListName }}(v *{{ .ListType }}) error {
316
        {{ if ne .KeyStruct "" -}}
317
        {{- range $key := .Keys }}
318
        {{- if $key.IsScalarField -}}
319
        if v.{{ $key.Name }} == nil {
320
                return fmt.Errorf("invalid nil key for {{ $key.Name }}")
321
        }
322

323
        {{ end -}}
324
        {{- end -}}
325
        key := {{ .KeyStruct }}{
326
                {{- range $key := .Keys }}
327
                {{- if $key.IsScalarField }}
328
                {{ $key.Name }}: *v.{{ $key.Name }},
329
                {{- else }}
330
                {{ $key.Name }}: v.{{ $key.Name }},
331
                {{- end -}} 
332
                {{ end }}
333
        }
334
        {{- else -}}
335
        {{- range $key := .Keys -}}
336
                {{- if $key.IsScalarField -}}
337
        if v.{{ $key.Name }} == nil {
338
                return fmt.Errorf("invalid nil key received for {{ $key.Name }}")
339
        }
340

341
        key := *v.{{ $key.Name }}
342
                {{- else -}}
343
        key := v.{{ $key.Name }}
344
                {{- end -}}
345
        {{- end -}}
346
        {{- end }}
347

348
        // Initialise the list within the receiver struct if it has not already been
349
        // created.
350
        if t.{{ .ListName }} == nil {
351
                {{- if ne .KeyStruct "" }}
352
                t.{{ .ListName }} = make(map[{{ .KeyStruct }}]*{{ .ListType }})
353
                {{- else }}
354
                        {{- $listName := .ListName -}}
355
                        {{- $listType := .ListType -}}
356
                        {{- range $key := .Keys }}
357
                t.{{ $listName }} = make(map[{{ $key.Type }}]*{{ $listType }})
358
                        {{- end }}
359
                {{- end }}
360
        }
361

362
        if _, ok := t.{{ .ListName }}[key]; ok {
363
                return fmt.Errorf("duplicate key for list {{ .ListName }} %v", key)
364
        }
365

366
        t.{{ .ListName }}[key] = v
367
        return nil
368
}
369
`)
370

371
        // goListMemberRenameTemplate provides a template for a function which renames
372
        // an entry within a list. It is used to generate functions for each list within
373
        // a generated Go struct.
374
        goListMemberRenameTemplate = mustMakeTemplate("renameListEntry", `
375
// Rename{{ .ListName }} renames an entry in the list {{ .ListName }} within
376
// the {{ .Receiver }} struct. The entry with key oldK is renamed to newK updating
377
// the key within the value.
378
func (t *{{ .Receiver }}) Rename{{ .ListName }}(
379
        {{- if ne .KeyStruct "" -}}
380
        oldK, newK {{ .KeyStruct -}}
381
  {{- else -}}
382
        {{- range $key := .Keys -}}
383
        oldK, newK {{ $key.Type -}}
384
        {{- end -}}
385
        {{- end -}}
386
) error {
387
        if _, ok := t.{{ .ListName }}[newK]; ok {
388
                return fmt.Errorf("key %v already exists in {{ .ListName }}", newK)
389
        }
390

391
        e, ok := t.{{ .ListName }}[oldK]
392
        if !ok {
393
                return fmt.Errorf("key %v not found in {{ .ListName }}", oldK)
394
        }
395

396
        {{- if ne .KeyStruct "" -}}
397
        {{- range $key := .Keys -}}
398
        {{- if $key.IsScalarField }}
399
        e.{{ $key.Name }} = &newK.{{ $key.Name }}
400
        {{- else }}
401
        e.{{ $key.Name }} = newK.{{ $key.Name }}
402
        {{- end -}}
403
        {{- end -}}
404
        {{ else -}}
405
        {{- $key := index .Keys 0 -}}
406
        {{- if $key.IsScalarField }}
407
        e.{{ $key.Name }} = &newK
408
        {{- else }}
409
        e.{{ $key.Name }} = newK
410
        {{- end -}}
411
        {{- end }}
412

413
        t.{{ .ListName }}[newK] = e
414
        delete(t.{{ .ListName }}, oldK)
415
        return nil
416
}
417
`)
418

419
        // goKeyMapTemplate defines the template for a function that is generated for a YANG
420
        // list type. It returns a map[string]interface{} keyed by the YANG leaf identifier of each
421
        // key leaf, and containing their values within the struct.
422
        goKeyMapTemplate = mustMakeTemplate("keyHelper", `
423
// ΛListKeyMap returns the keys of the {{ .Receiver }} struct, which is a YANG list entry.
424
func (t *{{ .Receiver }}) ΛListKeyMap() (map[string]interface{}, error) {
425
{{- range $key := .Keys -}}{{ if $key.IsPtr }}
426
        if t.{{ $key.GoName }} == nil {
427
                return nil, fmt.Errorf("nil value for key {{ $key.GoName }}")
428
        }
429
        {{- end }}
430
{{ end }}
431
        return map[string]interface{}{
432
                {{- range $key := .Keys }}
433
                "{{ $key.YANGName }}": {{ if $key.IsPtr -}}
434
                *
435
                {{- end -}} t.{{ $key.GoName }},
436
                {{- end }}
437
        }, nil
438
}
439
`)
440
)
441

442
// generateGetOrCreateList generates a getter function similar to that created
443
// by the generateGetOrCreateStruct function for maps within the generated Go
444
// code (which represent YANG lists). It handles both simple and composite key
445
// lists.
446
//
447
// If the list described has a single key, the argument to the function is the
448
// non-pointer key value. If the list has a complex type, it is an instance of
449
// the generated key type for the list.
450
//
451
// The generated function returns the existing value if the key exists in the
452
// list, or creates a new value using the NewXXX method if it does not exist.
453
// The generated function is written to the supplied buffer, using the method
454
// argument to determine the list's characteristics in the template.
455
func generateGetOrCreateList(buf *bytes.Buffer, method *generatedGoListMethod) error {
24✔
456
        if err := goGetOrCreateListTemplate.Execute(buf, method); err != nil {
24✔
NEW
457
                return err
×
NEW
458
        }
×
459
        return goGetOrCreateListElementTemplate.Execute(buf, method)
24✔
460
}
461

462
// generateListGetter generates a getter function for members of the a YANG list
463
// (Go map) field of the input struct. The generated function takes arguments
464
// of the same form as those that are given to the GetOrCreate method generated
465
// by generateGetOrCreateList.
466
func generateListGetter(buf *bytes.Buffer, method *generatedGoListMethod) error {
24✔
467
        return goListGetterTemplate.Execute(buf, method)
24✔
468
}
24✔
469

470
// generateListDelete generates a delete function for members of the a YANG list
471
// (Go map) field of the input struct. The generated function takes arguments
472
// of the same form as those that are given to the GetOrCreate method generated
473
// by generateGetOrCreateList.
474
func generateListDelete(buf *bytes.Buffer, method *generatedGoListMethod) error {
24✔
475
        return goDeleteListTemplate.Execute(buf, method)
24✔
476
}
24✔
477

478
// generateListAppend generates a function which appends a (key, value) to a
479
// Go map (YANG list) within the generated code. The argument of the generated
480
// function is the map's member type - from which the key values are extracted.
481
// The generated function returns an error if the key already exists in the list.
482
//
483
// The generated function is written to the supplied buffer - using the supplied
484
// method argument to determine the list's characteristics in the template.
485
func generateListAppend(buf *bytes.Buffer, method *generatedGoListMethod) error {
24✔
486
        return goListAppendTemplate.Execute(buf, method)
24✔
487
}
24✔
488

489
// generateGetListKey generates a function extracting the keys from a list
490
// defined in the Directory s, and appends it to the supplier buffer. The
491
// nameMap stores maps between the key YANG field identifiers and their Go
492
// identifiers.
493
//
494
// If the input Directory is the following list entry:
495
//
496
//        list foo {
497
//          key "bar baz";
498
//
499
//          leaf bar { type string; }
500
//          leaf baz { type uint8; }
501
//          leaf colour { type string; }
502
//        }
503
//
504
// Which is mapped into the Go struct:
505
//
506
//        type Foo {
507
//          Bar *string `path:"bar"`
508
//          Baz *uint8  `path:"baz"`
509
//          Colour *string `path:"colour"`
510
//        }
511
//
512
// The generated method will;
513
//   - Check pointer keys to ensure they are non-nil.
514
//   - Return a map[string]interface{} keyed by the name of the key in the YANG schema, with the value
515
//     specified in the struct.
516
//
517
// i.e.: for the above struct:
518
//
519
//         func (t *Foo) ΛListKeyMap() (map[string]interface{}, error) {
520
//                if t.Bar == nil {
521
//                   return nil, fmt.Errorf("key value for Bar is nil")
522
//                }
523
//
524
//                if t.Baz == nil {
525
//                   return nil, fmt.Errorf("key value for Baz is nil")
526
//                }
527
//
528
//                return map[string]interface{}{
529
//                  "bar": *t.Bar,
530
//                  "baz": *t.Baz,
531
//                }
532
//         }
533
func generateGetListKey(buf *bytes.Buffer, s *ygen.ParsedDirectory, nameMap map[string]*yangFieldMap) error {
2,323✔
534
        if s.ListKeys == nil {
4,162✔
535
                return nil
1,839✔
536
        }
1,839✔
537

538
        h := generatedGoKeyHelper{
484✔
539
                Receiver: s.Name,
484✔
540
        }
484✔
541

484✔
542
        kn := []string{}
484✔
543
        for k := range s.ListKeys {
1,067✔
544
                kn = append(kn, k)
583✔
545
        }
583✔
546
        sort.Strings(kn)
484✔
547

484✔
548
        for _, k := range kn {
1,067✔
549
                h.Keys = append(h.Keys, nameMap[k])
583✔
550
        }
583✔
551

552
        return goKeyMapTemplate.Execute(buf, h)
484✔
553
}
554

555
// UnorderedMapTypeName returns the map and key type names of an
556
// unordered, keyed map given go-generated IR information, as well as whether
557
// it is a defined type rather than a Go built-in type.
558
//
559
// e.g. for a list to be represented as map[string]*Foo, it returns
560
// "map[string]*Foo", "string", false, nil
561
func UnorderedMapTypeName(listYANGPath, listFieldName, parentName string, goStructElements map[string]*ygen.ParsedDirectory) (string, string, bool, error) {
366✔
562
        // The list itself, since it is a container, has a struct associated with it. Retrieve
366✔
563
        // this from the set of Directory structs for which code (a Go struct) will be
366✔
564
        //  generated such that additional details can be used in the code generation.
366✔
565
        listElem, ok := goStructElements[listYANGPath]
366✔
566
        if !ok {
367✔
567
                return "", "", false, fmt.Errorf("struct for %s did not exist", listYANGPath)
1✔
568
        }
1✔
569

570
        var listType, keyType string
365✔
571
        var isDefinedType bool
365✔
572
        switch len(listElem.ListKeys) {
365✔
573
        case 0:
1✔
574
                return "", "", false, fmt.Errorf("list does not contain any keys: %s:", listElem.Name)
1✔
575
        case 1:
259✔
576
                // This is a single keyed list, so we can represent it as a map with
259✔
577
                // a simple Go type as the key. Note that a leaf-list can never be
259✔
578
                // a key, so we do not need to handle the case whereby we would have to
259✔
579
                // have a slice which keys the list.
259✔
580
                for _, listKey := range listElem.ListKeys {
518✔
581
                        listType = fmt.Sprintf("map[%s]*%s", listKey.LangType.NativeType, listElem.Name)
259✔
582
                        keyType = listKey.LangType.NativeType
259✔
583
                        isDefinedType = ygen.IsYgenDefinedGoType(listKey.LangType)
259✔
584
                }
259✔
585
        default:
105✔
586
                // This is a list with multiple keys, so we need to generate a new structure
105✔
587
                // that represents the list key itself - this struct is described in a
105✔
588
                // generatedGoMultiKeyListStruct struct, which is then expanded by a template to the struct
105✔
589
                // definition.
105✔
590
                listKeyStructName := fmt.Sprintf("%s_Key", listElem.Name)
105✔
591
                names := make(map[string]bool, len(goStructElements))
105✔
592
                for _, d := range goStructElements {
546✔
593
                        names[d.Name] = true
441✔
594
                }
441✔
595
                if names[listKeyStructName] {
118✔
596
                        listKeyStructName = fmt.Sprintf("%s_%s_YANGListKey", parentName, listFieldName)
13✔
597
                        if names[listKeyStructName] {
14✔
598
                                return "", "", false, fmt.Errorf("unexpected generated list key name conflict for %s", listYANGPath)
1✔
599
                        }
1✔
600
                        names[listKeyStructName] = true
12✔
601
                }
602
                listType = fmt.Sprintf("map[%s]*%s", listKeyStructName, listElem.Name)
104✔
603
                keyType = listKeyStructName
104✔
604
                isDefinedType = true
104✔
605
        }
606
        return listType, keyType, isDefinedType, nil
363✔
607
}
608

609
// yangListFieldToGoType takes a yang node description (listField) and returns
610
// a string corresponding to the Go type that should be used to represent it
611
// within its parent struct (the parent argument). If applicable, it also
612
// returns generated code specifications for list-associated Go types. If a
613
// particular specification is not applicable, then nil is returned for that
614
// type.
615
//
616
// In all cases, the type of list field is the struct which is defined to
617
// reference the list, used as the base type. This type is then modified based
618
// on how the list is keyed:
619
//   - If the list is a config false, keyless list - a slice of the list's type is returned.
620
//   - If the list has a single key, a map, keyed by the single key's type is returned.
621
//   - If the list has multiple keys, a new struct is defined which represents the set of
622
//     leaves that make up the key. The type of the list is then a map, keyed by the new struct
623
//     type.
624
//   - If the list has "ordered-by user", then for the single and multiple key
625
//     cases, a struct that represents an ordered map keyed by the same key
626
//     type as the unordered map representation described above.
627
//
628
// In the case that the list has multiple keys, the type generated as the key of the list is returned.
629
// If errors are encountered during the type generation for the list, the error is returned.
630
func yangListFieldToGoType(listField *ygen.NodeDetails, listFieldName string, parent *ygen.ParsedDirectory, goStructElements map[string]*ygen.ParsedDirectory, generateOrderedMaps bool) (string, *generatedGoMultiKeyListStruct, *generatedGoListMethod, *generatedOrderedMapStruct, error) {
363✔
631
        // The list itself, since it is a container, has a struct associated with it. Retrieve
363✔
632
        // this from the set of Directory structs for which code (a Go struct) will be
363✔
633
        //  generated such that additional details can be used in the code generation.
363✔
634
        listElem, ok := goStructElements[listField.YANGDetails.Path]
363✔
635
        if !ok {
365✔
636
                return "", nil, nil, nil, fmt.Errorf("struct for %s did not exist", listField.YANGDetails.Path)
2✔
637
        }
2✔
638

639
        if len(listElem.ListKeys) == 0 {
362✔
640
                // Keyless list therefore represent this as a slice of pointers to
1✔
641
                // the struct that represents the list element itself.
1✔
642
                return fmt.Sprintf("[]*%s", listElem.Name), nil, nil, nil, nil
1✔
643
        }
1✔
644

645
        listType, keyType, _, err := UnorderedMapTypeName(listField.YANGDetails.Path, listFieldName, parent.Name, goStructElements)
360✔
646
        if err != nil {
360✔
647
                return "", nil, nil, nil, err
×
648
        }
×
649
        var multiListKey *generatedGoMultiKeyListStruct
360✔
650
        var listKeys []goStructField
360✔
651

360✔
652
        shortestPath := func(ss [][]string) [][]string {
822✔
653
                var shortest []string
462✔
654
                for _, s := range ss {
1,269✔
655
                        if shortest == nil {
1,269✔
656
                                shortest = s
462✔
657
                                continue
462✔
658
                        }
659
                        if len(s) < len(shortest) {
690✔
660
                                shortest = s
345✔
661
                        }
345✔
662
                }
663
                return [][]string{shortest}
462✔
664
        }
665

666
        usedKeyElemNames := make(map[string]bool)
360✔
667
        for _, keName := range listElem.ListKeyYANGNames {
822✔
668
                keyType, ok := listElem.Fields[keName]
462✔
669
                if !ok {
462✔
670
                        return "", nil, nil, nil, fmt.Errorf("did not find type for key %s", keName)
×
671
                }
×
672

673
                keyField := goStructField{
462✔
674
                        YANGName: keName,
462✔
675
                        Name:     genutil.MakeNameUnique(listElem.ListKeys[keName].Name, usedKeyElemNames),
462✔
676
                        Type:     listElem.ListKeys[keName].LangType.NativeType,
462✔
677
                        // The shortest mapped path for a list key must be the path to the key.
462✔
678
                        Tags: mappedPathTag(shortestPath(keyType.MappedPaths), ""),
462✔
679
                }
462✔
680
                keyField.IsScalarField = IsScalarField(keyType)
462✔
681
                listKeys = append(listKeys, keyField)
462✔
682
        }
683

684
        switch {
360✔
685
        case len(listElem.ListKeys) != 1:
102✔
686
                // This is a list with multiple keys, so we need to generate a new structure
102✔
687
                // that represents the list key itself - this struct is described in a
102✔
688
                // generatedGoMultiKeyListStruct struct, which is then expanded by a template to the struct
102✔
689
                // definition.
102✔
690
                multiListKey = &generatedGoMultiKeyListStruct{
102✔
691
                        KeyStructName: keyType,
102✔
692
                        ParentPath:    parent.Path,
102✔
693
                        ListName:      listFieldName,
102✔
694
                        Keys:          listKeys,
102✔
695
                }
102✔
696
        }
697

698
        var listMethodSpec *generatedGoListMethod
360✔
699
        var orderedMapSpec *generatedOrderedMapStruct
360✔
700

360✔
701
        if listField.YANGDetails.OrderedByUser && generateOrderedMaps {
383✔
702
                structName := OrderedMapTypeName(listElem.Name)
23✔
703
                listType = fmt.Sprintf("*%s", structName)
23✔
704
                // Create spec for generating ordered maps.
23✔
705
                orderedMapSpec = &generatedOrderedMapStruct{
23✔
706
                        StructName:       structName,
23✔
707
                        KeyName:          keyType,
23✔
708
                        ListTypeName:     listElem.Name,
23✔
709
                        ListFieldName:    listFieldName,
23✔
710
                        Keys:             listKeys,
23✔
711
                        ParentStructName: parent.Name,
23✔
712
                        YANGPath:         listField.YANGDetails.Path,
23✔
713
                }
23✔
714
        } else {
360✔
715
                // Generate the specification for the methods that should be generated for this
337✔
716
                // list, such that this can be handed to the relevant templates to generate code.
337✔
717
                listMethodSpec = &generatedGoListMethod{
337✔
718
                        ListName: listFieldName,
337✔
719
                        ListType: listElem.Name,
337✔
720
                        Keys:     listKeys,
337✔
721
                        Receiver: parent.Name,
337✔
722
                }
337✔
723
                if multiListKey != nil {
439✔
724
                        listMethodSpec.KeyStruct = keyType
102✔
725
                }
102✔
726
        }
727

728
        return listType, multiListKey, listMethodSpec, orderedMapSpec, nil
360✔
729
}
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