Skip to content

Commit

Permalink
Add generation flag to ordered maps (#884)
Browse files Browse the repository at this point in the history
  • Loading branch information
wenovus committed Jun 28, 2023
1 parent efad7f9 commit ec273a7
Show file tree
Hide file tree
Showing 20 changed files with 1,417 additions and 23 deletions.
2 changes: 2 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ var (
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.")
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.")
generateValidateFnName = flag.String("validate_fn_name", "Validate", "The Name of the proxy function for the Validate functionality.")
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.")

// Flags used for PathStruct generation only.
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.")
Expand Down Expand Up @@ -374,6 +375,7 @@ func main() {
IncludeModelData: *includeModelData,
AppendEnumSuffixForSimpleUnionEnums: *appendEnumSuffixForSimpleUnionEnums,
IgnoreShadowSchemaPaths: *ignoreShadowSchemaPaths,
GenerateOrderedListsAsUnorderedMaps: !*generateOrderedMaps,
},
)

Expand Down
4 changes: 4 additions & 0 deletions gogen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ type GoOpts struct {
// compression is enabled, that the shadowed paths are to be ignored
// while while unmarshalling.
IgnoreShadowSchemaPaths bool
// GenerateOrderedListsAsUnorderedMaps indicates that lists that are
// marked `ordered-by user` will be represented using built-in Go maps
// instead of an ordered map Go structure.
GenerateOrderedListsAsUnorderedMaps bool
}

// GeneratedCode contains generated code snippets that can be processed by the calling
Expand Down
37 changes: 32 additions & 5 deletions gogen/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package gogen
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

Expand All @@ -25,6 +26,8 @@ const (
deflakeRuns int = 10
)

var updateGolden = flag.Bool("update_golden", false, "Update golden files")

// yangTestCase describs a test case for which code generation is performed
// through Goyang's API, it provides the input set of parameters in a way that
// can be reused across tests.
Expand Down Expand Up @@ -307,6 +310,25 @@ func TestSimpleStructs(t *testing.T) {
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata/structs/openconfig-withlist-opstate.formatted-txt"),
}, {
name: "OpenConfig schema test - list and associated method (rename, new) - using unordered list",
inFiles: []string{filepath.Join(datapath, "openconfig-withlist.yang")},
inConfig: CodeGenerator{
IROptions: ygen.IROptions{
TransformationOptions: ygen.TransformationOpts{
CompressBehaviour: genutil.PreferIntendedConfig,
ShortenEnumLeafNames: true,
UseDefiningModuleForTypedefEnumNames: true,
EnumerationsUseUnderscores: true,
},
},
GoOptions: GoOpts{
GenerateRenameMethod: true,
GenerateSimpleUnions: true,
GenerateOrderedListsAsUnorderedMaps: true,
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata/structs/openconfig-withlist-unordered.formatted-txt"),
}, {
name: "OpenConfig schema test - multi-keyed list key struct name conflict and associated method (rename, new)",
inFiles: []string{filepath.Join(datapath, "openconfig-multikey-list-name-conflict.yang")},
Expand Down Expand Up @@ -1161,9 +1183,9 @@ func TestSimpleStructs(t *testing.T) {
}

if tt.wantSchemaFile != "" {
wantSchema, rferr := ioutil.ReadFile(tt.wantSchemaFile)
wantSchema, rferr := os.ReadFile(tt.wantSchemaFile)
if rferr != nil {
t.Fatalf("%s: ioutil.ReadFile(%q) error: %v", tt.name, tt.wantSchemaFile, rferr)
t.Fatalf("%s: os.ReadFile(%q) error: %v", tt.name, tt.wantSchemaFile, rferr)
}

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

wantCodeBytes, rferr := ioutil.ReadFile(tt.wantStructsCodeFile)
wantCodeBytes, rferr := os.ReadFile(tt.wantStructsCodeFile)
if rferr != nil {
t.Fatalf("%s: ioutil.ReadFile(%q) error: %v", tt.name, tt.wantStructsCodeFile, rferr)
t.Fatalf("%s: os.ReadFile(%q) error: %v", tt.name, tt.wantStructsCodeFile, rferr)
}

wantCode := string(wantCodeBytes)

if gotCode != wantCode {
if *updateGolden {
if err := os.WriteFile(tt.wantStructsCodeFile, []byte(gotCode), 0644); err != nil {
t.Fatal(err)
}
}
// Use difflib to generate a unified diff between the
// two code snippets such that this is simpler to debug
// in the test output.
Expand Down
2 changes: 1 addition & 1 deletion gogen/gogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
// If the field within the struct is a list, then generate code for this list. This
// includes extracting any new types that are required to represent the key of a
// list that has multiple keys.
fieldType, multiKeyListKey, listMethods, orderedMapSpec, listErr := yangListFieldToGoType(field, fieldName, targetStruct, goStructElements)
fieldType, multiKeyListKey, listMethods, orderedMapSpec, listErr := yangListFieldToGoType(field, fieldName, targetStruct, goStructElements, !goOpts.GenerateOrderedListsAsUnorderedMaps)
if listErr != nil {
errs = append(errs, listErr)
}
Expand Down
2 changes: 1 addition & 1 deletion gogen/internal/gotypes/ordered_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type RoutingPolicy_PolicyDefinition_Statement struct {
type RoutingPolicy_PolicyDefinition_Statement_OrderedMap struct {
// TODO: Add a mutex here and add race tests after implementing
// ygot.Equal and evaluating the thread-safety of ygot.
//mu sync.RWmutex
// mu sync.RWmutex
// keys contain the key order of the map.
keys []string
// valueMap contains the mapping from the statement key to each of the
Expand Down
187 changes: 187 additions & 0 deletions gogen/testdata/structs/openconfig-withlist-opstate.formatted-txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type UnionUnsupported struct {
type Model struct {
MultiKey map[Model_MultiKey_Key]*Model_MultiKey `path:"b/multi-key" module:"openconfig-withlist/openconfig-withlist"`
SingleKey map[string]*Model_SingleKey `path:"a/single-key" module:"openconfig-withlist/openconfig-withlist"`
SingleKeyOrdered *Model_SingleKeyOrdered_OrderedMap `path:"c/single-key-ordered" module:"openconfig-withlist/openconfig-withlist"`
}

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

// AppendNewSingleKeyOrdered creates a new entry in the SingleKeyOrdered
// ordered map of the Model struct. The keys of the list are
// populated from the input arguments.
func (s *Model) AppendNewSingleKeyOrdered(Key string) (*Model_SingleKeyOrdered, error) {
if s.SingleKeyOrdered == nil {
s.SingleKeyOrdered = &Model_SingleKeyOrdered_OrderedMap{}
}
return s.SingleKeyOrdered.AppendNew(Key)
}

// AppendSingleKeyOrdered appends the supplied Model_SingleKeyOrdered struct
// to the list SingleKeyOrdered of Model. If the key value(s)
// specified in the supplied Model_SingleKeyOrdered already exist in the list, an
// error is returned.
func (s *Model) AppendSingleKeyOrdered(v *Model_SingleKeyOrdered) error {
if s.SingleKeyOrdered == nil {
s.SingleKeyOrdered = &Model_SingleKeyOrdered_OrderedMap{}
}
return s.SingleKeyOrdered.Append(v)
}

// GetSingleKeyOrdered retrieves the value with the specified key from the
// SingleKeyOrdered map field of Model. If the receiver
// is nil, or the specified key is not present in the list, nil is returned
// such that Get* methods may be safely chained.
func (s *Model) GetSingleKeyOrdered(Key string) *Model_SingleKeyOrdered {
key := Key
return s.SingleKeyOrdered.Get(key)
}

// DeleteSingleKeyOrdered deletes the value with the specified keys from
// the receiver Model. If there is no such element, the
// function is a no-op.
func (s *Model) DeleteSingleKeyOrdered(Key string) bool {
key := Key
return s.SingleKeyOrdered.Delete(key)
}

// Model_SingleKeyOrdered_OrderedMap is an ordered map that represents the "ordered-by user"
// list elements at /openconfig-withlist/model/c/single-key-ordered.
type Model_SingleKeyOrdered_OrderedMap struct {
keys []string
valueMap map[string]*Model_SingleKeyOrdered
}

// IsYANGOrderedList ensures that Model_SingleKeyOrdered_OrderedMap implements the
// ygot.GoOrderedMap interface.
func (*Model_SingleKeyOrdered_OrderedMap) IsYANGOrderedList() {}

// init initializes any uninitialized values.
func (o *Model_SingleKeyOrdered_OrderedMap) init() {
if o == nil {
return
}
if o.valueMap == nil {
o.valueMap = map[string]*Model_SingleKeyOrdered{}
}
}

// Keys returns a copy of the list's keys.
func (o *Model_SingleKeyOrdered_OrderedMap) Keys() []string {
if o == nil {
return nil
}
return append([]string{}, o.keys...)
}

// Values returns the current set of the list's values in order.
func (o *Model_SingleKeyOrdered_OrderedMap) Values() []*Model_SingleKeyOrdered {
if o == nil {
return nil
}
var values []*Model_SingleKeyOrdered
for _, key := range o.keys {
values = append(values, o.valueMap[key])
}
return values
}

// Len returns a size of Model_SingleKeyOrdered_OrderedMap
func (o *Model_SingleKeyOrdered_OrderedMap) Len() int {
if o == nil {
return 0
}
return len(o.keys)
}

// Get returns the value corresponding to the key. If the key is not found, nil
// is returned.
func (o *Model_SingleKeyOrdered_OrderedMap) Get(key string) *Model_SingleKeyOrdered {
if o == nil {
return nil
}
val, _ := o.valueMap[key]
return val
}

// Delete deletes an element.
func (o *Model_SingleKeyOrdered_OrderedMap) Delete(key string) bool {
if o == nil {
return false
}
if _, ok := o.valueMap[key]; !ok {
return false
}
for i, k := range o.keys {
if k == key {
o.keys = append(o.keys[:i], o.keys[i+1:]...)
delete(o.valueMap, key)
return true
}
}
return false
}

// Append appends a Model_SingleKeyOrdered, returning an error if the key
// already exists in the ordered list or if the key is unspecified.
func (o *Model_SingleKeyOrdered_OrderedMap) Append(v *Model_SingleKeyOrdered) error {
if o == nil {
return fmt.Errorf("nil ordered map, cannot append Model_SingleKeyOrdered")
}
if v == nil {
return fmt.Errorf("nil Model_SingleKeyOrdered")
}
if v.Key == nil {
return fmt.Errorf("invalid nil key received for Key")
}

key := *v.Key

if _, ok := o.valueMap[key]; ok {
return fmt.Errorf("duplicate key for list Statement %v", key)
}
o.keys = append(o.keys, key)
o.init()
o.valueMap[key] = v
return nil
}

// AppendNew creates and appends a new Model_SingleKeyOrdered, returning the
// newly-initialized v. It returns an error if the v already exists.
func (o *Model_SingleKeyOrdered_OrderedMap) AppendNew(Key string) (*Model_SingleKeyOrdered, error) {
if o == nil {
return nil, fmt.Errorf("nil ordered map, cannot append Model_SingleKeyOrdered")
}
key := Key

if _, ok := o.valueMap[key]; ok {
return nil, fmt.Errorf("duplicate key for list Statement %v", key)
}
o.keys = append(o.keys, key)
newElement := &Model_SingleKeyOrdered{
Key: &Key,
}
o.init()
o.valueMap[key] = newElement
return newElement, nil
}

// ΛBelongingModule returns the name of the module that defines the namespace
// of Model.
func (*Model) ΛBelongingModule() string {
Expand Down Expand Up @@ -261,3 +421,30 @@ func (t *Model_SingleKey) ΛListKeyMap() (map[string]interface{}, error) {
func (*Model_SingleKey) ΛBelongingModule() string {
return "openconfig-withlist"
}

// Model_SingleKeyOrdered represents the /openconfig-withlist/model/c/single-key-ordered YANG schema element.
type Model_SingleKeyOrdered struct {
Key *string `path:"state/key|key" module:"openconfig-withlist/openconfig-withlist|openconfig-withlist"`
}

// IsYANGGoStruct ensures that Model_SingleKeyOrdered implements the yang.GoStruct
// interface. This allows functions that need to handle this struct to
// identify it as being generated by ygen.
func (*Model_SingleKeyOrdered) IsYANGGoStruct() {}

// ΛListKeyMap returns the keys of the Model_SingleKeyOrdered struct, which is a YANG list entry.
func (t *Model_SingleKeyOrdered) ΛListKeyMap() (map[string]interface{}, error) {
if t.Key == nil {
return nil, fmt.Errorf("nil value for key Key")
}

return map[string]interface{}{
"key": *t.Key,
}, nil
}

// ΛBelongingModule returns the name of the module that defines the namespace
// of Model_SingleKeyOrdered.
func (*Model_SingleKeyOrdered) ΛBelongingModule() string {
return "openconfig-withlist"
}
Loading

0 comments on commit ec273a7

Please sign in to comment.