Skip to content

Commit ec273a7

Browse files
authored
Add generation flag to ordered maps (#884)
1 parent efad7f9 commit ec273a7

20 files changed

+1417
-23
lines changed

generator/generator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ var (
9999
includeModelData = flag.Bool("include_model_data", false, "If set to true, a slice of gNMI ModelData messages are included in the generated Go code containing the details of the input schemas from which the code was generated.")
100100
generatePopulateDefault = flag.Bool("generate_populate_defaults", false, "If set to true, a PopulateDefault method will be generated for all GoStructs which recursively populates default values.")
101101
generateValidateFnName = flag.String("validate_fn_name", "Validate", "The Name of the proxy function for the Validate functionality.")
102+
generateOrderedMaps = flag.Bool("generate_ordered_maps", true, "If set to true, ordered map structures satisfying the interface ygot.GoOrderedMap will be generated for `ordered-by user` lists instead of Go built-in maps.")
102103

103104
// Flags used for PathStruct generation only.
104105
schemaStructPath = flag.String("schema_struct_path", "", "The Go import path for the schema structs package. This should be specified if and only if schema structs are not being generated at the same time as path structs.")
@@ -374,6 +375,7 @@ func main() {
374375
IncludeModelData: *includeModelData,
375376
AppendEnumSuffixForSimpleUnionEnums: *appendEnumSuffixForSimpleUnionEnums,
376377
IgnoreShadowSchemaPaths: *ignoreShadowSchemaPaths,
378+
GenerateOrderedListsAsUnorderedMaps: !*generateOrderedMaps,
377379
},
378380
)
379381

gogen/codegen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ type GoOpts struct {
120120
// compression is enabled, that the shadowed paths are to be ignored
121121
// while while unmarshalling.
122122
IgnoreShadowSchemaPaths bool
123+
// GenerateOrderedListsAsUnorderedMaps indicates that lists that are
124+
// marked `ordered-by user` will be represented using built-in Go maps
125+
// instead of an ordered map Go structure.
126+
GenerateOrderedListsAsUnorderedMaps bool
123127
}
124128

125129
// GeneratedCode contains generated code snippets that can be processed by the calling

gogen/codegen_test.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ package gogen
33
import (
44
"bytes"
55
"encoding/json"
6+
"flag"
67
"fmt"
7-
"io/ioutil"
8+
"os"
89
"path/filepath"
910
"testing"
1011

@@ -25,6 +26,8 @@ const (
2526
deflakeRuns int = 10
2627
)
2728

29+
var updateGolden = flag.Bool("update_golden", false, "Update golden files")
30+
2831
// yangTestCase describs a test case for which code generation is performed
2932
// through Goyang's API, it provides the input set of parameters in a way that
3033
// can be reused across tests.
@@ -307,6 +310,25 @@ func TestSimpleStructs(t *testing.T) {
307310
},
308311
},
309312
wantStructsCodeFile: filepath.Join(TestRoot, "testdata/structs/openconfig-withlist-opstate.formatted-txt"),
313+
}, {
314+
name: "OpenConfig schema test - list and associated method (rename, new) - using unordered list",
315+
inFiles: []string{filepath.Join(datapath, "openconfig-withlist.yang")},
316+
inConfig: CodeGenerator{
317+
IROptions: ygen.IROptions{
318+
TransformationOptions: ygen.TransformationOpts{
319+
CompressBehaviour: genutil.PreferIntendedConfig,
320+
ShortenEnumLeafNames: true,
321+
UseDefiningModuleForTypedefEnumNames: true,
322+
EnumerationsUseUnderscores: true,
323+
},
324+
},
325+
GoOptions: GoOpts{
326+
GenerateRenameMethod: true,
327+
GenerateSimpleUnions: true,
328+
GenerateOrderedListsAsUnorderedMaps: true,
329+
},
330+
},
331+
wantStructsCodeFile: filepath.Join(TestRoot, "testdata/structs/openconfig-withlist-unordered.formatted-txt"),
310332
}, {
311333
name: "OpenConfig schema test - multi-keyed list key struct name conflict and associated method (rename, new)",
312334
inFiles: []string{filepath.Join(datapath, "openconfig-multikey-list-name-conflict.yang")},
@@ -1161,9 +1183,9 @@ func TestSimpleStructs(t *testing.T) {
11611183
}
11621184

11631185
if tt.wantSchemaFile != "" {
1164-
wantSchema, rferr := ioutil.ReadFile(tt.wantSchemaFile)
1186+
wantSchema, rferr := os.ReadFile(tt.wantSchemaFile)
11651187
if rferr != nil {
1166-
t.Fatalf("%s: ioutil.ReadFile(%q) error: %v", tt.name, tt.wantSchemaFile, rferr)
1188+
t.Fatalf("%s: os.ReadFile(%q) error: %v", tt.name, tt.wantSchemaFile, rferr)
11671189
}
11681190

11691191
var wantJSON map[string]interface{}
@@ -1177,14 +1199,19 @@ func TestSimpleStructs(t *testing.T) {
11771199
}
11781200
}
11791201

1180-
wantCodeBytes, rferr := ioutil.ReadFile(tt.wantStructsCodeFile)
1202+
wantCodeBytes, rferr := os.ReadFile(tt.wantStructsCodeFile)
11811203
if rferr != nil {
1182-
t.Fatalf("%s: ioutil.ReadFile(%q) error: %v", tt.name, tt.wantStructsCodeFile, rferr)
1204+
t.Fatalf("%s: os.ReadFile(%q) error: %v", tt.name, tt.wantStructsCodeFile, rferr)
11831205
}
11841206

11851207
wantCode := string(wantCodeBytes)
11861208

11871209
if gotCode != wantCode {
1210+
if *updateGolden {
1211+
if err := os.WriteFile(tt.wantStructsCodeFile, []byte(gotCode), 0644); err != nil {
1212+
t.Fatal(err)
1213+
}
1214+
}
11881215
// Use difflib to generate a unified diff between the
11891216
// two code snippets such that this is simpler to debug
11901217
// in the test output.

gogen/gogen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
10451045
// If the field within the struct is a list, then generate code for this list. This
10461046
// includes extracting any new types that are required to represent the key of a
10471047
// list that has multiple keys.
1048-
fieldType, multiKeyListKey, listMethods, orderedMapSpec, listErr := yangListFieldToGoType(field, fieldName, targetStruct, goStructElements)
1048+
fieldType, multiKeyListKey, listMethods, orderedMapSpec, listErr := yangListFieldToGoType(field, fieldName, targetStruct, goStructElements, !goOpts.GenerateOrderedListsAsUnorderedMaps)
10491049
if listErr != nil {
10501050
errs = append(errs, listErr)
10511051
}

gogen/internal/gotypes/ordered_map.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ type RoutingPolicy_PolicyDefinition_Statement struct {
7272
type RoutingPolicy_PolicyDefinition_Statement_OrderedMap struct {
7373
// TODO: Add a mutex here and add race tests after implementing
7474
// ygot.Equal and evaluating the thread-safety of ygot.
75-
//mu sync.RWmutex
75+
// mu sync.RWmutex
7676
// keys contain the key order of the map.
7777
keys []string
7878
// valueMap contains the mapping from the statement key to each of the

gogen/testdata/structs/openconfig-withlist-opstate.formatted-txt

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type UnionUnsupported struct {
7373
type Model struct {
7474
MultiKey map[Model_MultiKey_Key]*Model_MultiKey `path:"b/multi-key" module:"openconfig-withlist/openconfig-withlist"`
7575
SingleKey map[string]*Model_SingleKey `path:"a/single-key" module:"openconfig-withlist/openconfig-withlist"`
76+
SingleKeyOrdered *Model_SingleKeyOrdered_OrderedMap `path:"c/single-key-ordered" module:"openconfig-withlist/openconfig-withlist"`
7677
}
7778

7879
// IsYANGGoStruct ensures that Model implements the yang.GoStruct
@@ -196,6 +197,165 @@ func (t *Model) RenameSingleKey(oldK, newK string) error {
196197
return nil
197198
}
198199

200+
// AppendNewSingleKeyOrdered creates a new entry in the SingleKeyOrdered
201+
// ordered map of the Model struct. The keys of the list are
202+
// populated from the input arguments.
203+
func (s *Model) AppendNewSingleKeyOrdered(Key string) (*Model_SingleKeyOrdered, error) {
204+
if s.SingleKeyOrdered == nil {
205+
s.SingleKeyOrdered = &Model_SingleKeyOrdered_OrderedMap{}
206+
}
207+
return s.SingleKeyOrdered.AppendNew(Key)
208+
}
209+
210+
// AppendSingleKeyOrdered appends the supplied Model_SingleKeyOrdered struct
211+
// to the list SingleKeyOrdered of Model. If the key value(s)
212+
// specified in the supplied Model_SingleKeyOrdered already exist in the list, an
213+
// error is returned.
214+
func (s *Model) AppendSingleKeyOrdered(v *Model_SingleKeyOrdered) error {
215+
if s.SingleKeyOrdered == nil {
216+
s.SingleKeyOrdered = &Model_SingleKeyOrdered_OrderedMap{}
217+
}
218+
return s.SingleKeyOrdered.Append(v)
219+
}
220+
221+
// GetSingleKeyOrdered retrieves the value with the specified key from the
222+
// SingleKeyOrdered map field of Model. If the receiver
223+
// is nil, or the specified key is not present in the list, nil is returned
224+
// such that Get* methods may be safely chained.
225+
func (s *Model) GetSingleKeyOrdered(Key string) *Model_SingleKeyOrdered {
226+
key := Key
227+
return s.SingleKeyOrdered.Get(key)
228+
}
229+
230+
// DeleteSingleKeyOrdered deletes the value with the specified keys from
231+
// the receiver Model. If there is no such element, the
232+
// function is a no-op.
233+
func (s *Model) DeleteSingleKeyOrdered(Key string) bool {
234+
key := Key
235+
return s.SingleKeyOrdered.Delete(key)
236+
}
237+
238+
// Model_SingleKeyOrdered_OrderedMap is an ordered map that represents the "ordered-by user"
239+
// list elements at /openconfig-withlist/model/c/single-key-ordered.
240+
type Model_SingleKeyOrdered_OrderedMap struct {
241+
keys []string
242+
valueMap map[string]*Model_SingleKeyOrdered
243+
}
244+
245+
// IsYANGOrderedList ensures that Model_SingleKeyOrdered_OrderedMap implements the
246+
// ygot.GoOrderedMap interface.
247+
func (*Model_SingleKeyOrdered_OrderedMap) IsYANGOrderedList() {}
248+
249+
// init initializes any uninitialized values.
250+
func (o *Model_SingleKeyOrdered_OrderedMap) init() {
251+
if o == nil {
252+
return
253+
}
254+
if o.valueMap == nil {
255+
o.valueMap = map[string]*Model_SingleKeyOrdered{}
256+
}
257+
}
258+
259+
// Keys returns a copy of the list's keys.
260+
func (o *Model_SingleKeyOrdered_OrderedMap) Keys() []string {
261+
if o == nil {
262+
return nil
263+
}
264+
return append([]string{}, o.keys...)
265+
}
266+
267+
// Values returns the current set of the list's values in order.
268+
func (o *Model_SingleKeyOrdered_OrderedMap) Values() []*Model_SingleKeyOrdered {
269+
if o == nil {
270+
return nil
271+
}
272+
var values []*Model_SingleKeyOrdered
273+
for _, key := range o.keys {
274+
values = append(values, o.valueMap[key])
275+
}
276+
return values
277+
}
278+
279+
// Len returns a size of Model_SingleKeyOrdered_OrderedMap
280+
func (o *Model_SingleKeyOrdered_OrderedMap) Len() int {
281+
if o == nil {
282+
return 0
283+
}
284+
return len(o.keys)
285+
}
286+
287+
// Get returns the value corresponding to the key. If the key is not found, nil
288+
// is returned.
289+
func (o *Model_SingleKeyOrdered_OrderedMap) Get(key string) *Model_SingleKeyOrdered {
290+
if o == nil {
291+
return nil
292+
}
293+
val, _ := o.valueMap[key]
294+
return val
295+
}
296+
297+
// Delete deletes an element.
298+
func (o *Model_SingleKeyOrdered_OrderedMap) Delete(key string) bool {
299+
if o == nil {
300+
return false
301+
}
302+
if _, ok := o.valueMap[key]; !ok {
303+
return false
304+
}
305+
for i, k := range o.keys {
306+
if k == key {
307+
o.keys = append(o.keys[:i], o.keys[i+1:]...)
308+
delete(o.valueMap, key)
309+
return true
310+
}
311+
}
312+
return false
313+
}
314+
315+
// Append appends a Model_SingleKeyOrdered, returning an error if the key
316+
// already exists in the ordered list or if the key is unspecified.
317+
func (o *Model_SingleKeyOrdered_OrderedMap) Append(v *Model_SingleKeyOrdered) error {
318+
if o == nil {
319+
return fmt.Errorf("nil ordered map, cannot append Model_SingleKeyOrdered")
320+
}
321+
if v == nil {
322+
return fmt.Errorf("nil Model_SingleKeyOrdered")
323+
}
324+
if v.Key == nil {
325+
return fmt.Errorf("invalid nil key received for Key")
326+
}
327+
328+
key := *v.Key
329+
330+
if _, ok := o.valueMap[key]; ok {
331+
return fmt.Errorf("duplicate key for list Statement %v", key)
332+
}
333+
o.keys = append(o.keys, key)
334+
o.init()
335+
o.valueMap[key] = v
336+
return nil
337+
}
338+
339+
// AppendNew creates and appends a new Model_SingleKeyOrdered, returning the
340+
// newly-initialized v. It returns an error if the v already exists.
341+
func (o *Model_SingleKeyOrdered_OrderedMap) AppendNew(Key string) (*Model_SingleKeyOrdered, error) {
342+
if o == nil {
343+
return nil, fmt.Errorf("nil ordered map, cannot append Model_SingleKeyOrdered")
344+
}
345+
key := Key
346+
347+
if _, ok := o.valueMap[key]; ok {
348+
return nil, fmt.Errorf("duplicate key for list Statement %v", key)
349+
}
350+
o.keys = append(o.keys, key)
351+
newElement := &Model_SingleKeyOrdered{
352+
Key: &Key,
353+
}
354+
o.init()
355+
o.valueMap[key] = newElement
356+
return newElement, nil
357+
}
358+
199359
// ΛBelongingModule returns the name of the module that defines the namespace
200360
// of Model.
201361
func (*Model) ΛBelongingModule() string {
@@ -261,3 +421,30 @@ func (t *Model_SingleKey) ΛListKeyMap() (map[string]interface{}, error) {
261421
func (*Model_SingleKey) ΛBelongingModule() string {
262422
return "openconfig-withlist"
263423
}
424+
425+
// Model_SingleKeyOrdered represents the /openconfig-withlist/model/c/single-key-ordered YANG schema element.
426+
type Model_SingleKeyOrdered struct {
427+
Key *string `path:"state/key|key" module:"openconfig-withlist/openconfig-withlist|openconfig-withlist"`
428+
}
429+
430+
// IsYANGGoStruct ensures that Model_SingleKeyOrdered implements the yang.GoStruct
431+
// interface. This allows functions that need to handle this struct to
432+
// identify it as being generated by ygen.
433+
func (*Model_SingleKeyOrdered) IsYANGGoStruct() {}
434+
435+
// ΛListKeyMap returns the keys of the Model_SingleKeyOrdered struct, which is a YANG list entry.
436+
func (t *Model_SingleKeyOrdered) ΛListKeyMap() (map[string]interface{}, error) {
437+
if t.Key == nil {
438+
return nil, fmt.Errorf("nil value for key Key")
439+
}
440+
441+
return map[string]interface{}{
442+
"key": *t.Key,
443+
}, nil
444+
}
445+
446+
// ΛBelongingModule returns the name of the module that defines the namespace
447+
// of Model_SingleKeyOrdered.
448+
func (*Model_SingleKeyOrdered) ΛBelongingModule() string {
449+
return "openconfig-withlist"
450+
}

0 commit comments

Comments
 (0)