Skip to content

Commit

Permalink
Generate leaf setters (#717)
Browse files Browse the repository at this point in the history
* Add support for leaf setters generation

* mend

* added unit tests for leaf setters

* added test case for union leaf setters

* Added additional leaf setters tests

* Update gogen/gogen.go

Co-authored-by: Rob Shakir <[email protected]>

* Update gogen/gogen.go

Co-authored-by: Rob Shakir <[email protected]>

Co-authored-by: Wen Bo Li <[email protected]>
Co-authored-by: Rob Shakir <[email protected]>
  • Loading branch information
3 people authored Sep 30, 2022
1 parent 828caf0 commit 6259f3a
Show file tree
Hide file tree
Showing 10 changed files with 790 additions and 0 deletions.
2 changes: 2 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ var (
generateGetters = flag.Bool("generate_getters", false, "If set to true, getter methdos that retrieve or create an element are generated for YANG container (Go struct pointer) or list (Go map) fields within the generated code.")
generateDelete = flag.Bool("generate_delete", false, "If set to true, delete methods are generated for YANG lists (Go maps) within the Go code.")
generateLeafGetters = flag.Bool("generate_leaf_getters", false, "If set to true, getters for YANG leaves are generated within the Go code. Caution should be exercised when using leaf getters, since values that are explicitly set to the Go default/zero value are not distinguishable from those that are unset when retrieved via the GetXXX method.")
generateLeafSetters = flag.Bool("generate_leaf_setters", false, "If set to true, setters for YANG leaves are generated within the Go code.")
generateSimpleUnions = flag.Bool("generate_simple_unions", false, "If set to true, then generated typedefs will be used to represent union subtypes within Go code instead of wrapper struct types.")
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.")
Expand Down Expand Up @@ -362,6 +363,7 @@ func main() {
GenerateDeleteMethod: *generateDelete,
GenerateAppendMethod: *generateAppend,
GenerateLeafGetters: *generateLeafGetters,
GenerateLeafSetters: *generateLeafSetters,
GeneratePopulateDefault: *generatePopulateDefault,
ValidateFunctionName: *generateValidateFnName,
GenerateSimpleUnions: *generateSimpleUnions,
Expand Down
3 changes: 3 additions & 0 deletions gogen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ type GoOpts struct {
// whether a field has been explicitly set to the zero value (i.e., an integer
// field is set to 0), or whether the field was actually unset.
GenerateLeafGetters bool
// GenerateLeafSetters specifies whether Set* methods should be created for
// leaf fields of a struct.
GenerateLeafSetters bool
// GeneratePopulateDefault specifies whether a PopulateDefaults method
// should be generated for every GoStruct that recursively populates
// default values within the subtree.
Expand Down
25 changes: 25 additions & 0 deletions gogen/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func TestSimpleStructs(t *testing.T) {
GoOptions: GoOpts{
GenerateSimpleUnions: true,
GenerateLeafGetters: true,
GenerateLeafSetters: true,
GeneratePopulateDefault: true,
},
},
Expand Down Expand Up @@ -197,6 +198,7 @@ func TestSimpleStructs(t *testing.T) {
GoOptions: GoOpts{
GenerateSimpleUnions: true,
GenerateLeafGetters: true,
GenerateLeafSetters: true,
GeneratePopulateDefault: true,
},
},
Expand Down Expand Up @@ -652,6 +654,7 @@ func TestSimpleStructs(t *testing.T) {
GoOptions: GoOpts{
GenerateSimpleUnions: true,
GenerateLeafGetters: true,
GenerateLeafSetters: true,
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "enum-union.formatted-txt"),
Expand Down Expand Up @@ -711,6 +714,7 @@ func TestSimpleStructs(t *testing.T) {
},
GoOptions: GoOpts{
GenerateLeafGetters: true,
GenerateLeafSetters: true,
GeneratePopulateDefault: true,
AppendEnumSuffixForSimpleUnionEnums: true,
},
Expand All @@ -732,6 +736,7 @@ func TestSimpleStructs(t *testing.T) {
GoOptions: GoOpts{
GenerateSimpleUnions: true,
GenerateLeafGetters: true,
GenerateLeafSetters: true,
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "enum-module.formatted-txt"),
Expand Down Expand Up @@ -855,6 +860,26 @@ func TestSimpleStructs(t *testing.T) {
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "openconfig-list-enum-key.leaf-getters.formatted-txt"),
}, {
name: "module with leaf setters",
inFiles: []string{filepath.Join(datapath, "", "openconfig-list-enum-key.yang")},
inConfig: CodeGenerator{
IROptions: ygen.IROptions{
TransformationOptions: ygen.TransformationOpts{
GenerateFakeRoot: true,
ShortenEnumLeafNames: true,
UseDefiningModuleForTypedefEnumNames: true,
CompressBehaviour: genutil.PreferIntendedConfig,
EnumerationsUseUnderscores: true,
},
},
GoOptions: GoOpts{
GenerateLeafSetters: true,
GeneratePopulateDefault: true,
GenerateSimpleUnions: true,
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "openconfig-list-enum-key.leaf-setters.formatted-txt"),
}, {
name: "uncompressed module with two different enums",
inFiles: []string{filepath.Join(datapath, "", "enum-list-uncompressed.yang")},
Expand Down
57 changes: 57 additions & 0 deletions gogen/gogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,20 @@ type generatedLeafGetter struct {
Receiver string
}

// generatedLeafSetter is used to represent the parameters required to generate a
// setter for a leaf within the generated Go code.
type generatedLeafSetter struct {
// Name is the name of the field. It is used as a suffix to Set to generate
// the setter.
Name string
// Type is the type of the field, required by the setter method.
Type string
// IsPtr stores whether the value is a pointer.
IsPtr bool
// Receiver is the name of the receiver for the setter method.
Receiver string
}

// generatedDefaultMethod is used to represent parameters required to generate
// a PopulateDefaults method for a GoStruct that recursively populates default
// values within the subtree.
Expand Down Expand Up @@ -781,6 +795,16 @@ func (t *{{ .Receiver }}) Get{{ .Name }}() {{ .Type }} {
}
return {{ if .IsPtr -}} * {{- end -}} t.{{ .Name }}
}
`)

// goLeafSetterTemplate defines a template for a function that, for a
// particular leaf, generates a setter method.
goLeafSetterTemplate = mustMakeTemplate("setLeaf", `
// Set{{ .Name }} sets the value of the leaf {{ .Name }} in the {{ .Receiver }}
// struct.
func (t *{{ .Receiver }}) Set{{ .Name }}(v {{ .Type }}) {
t.{{ .Name }} = {{ if .IsPtr -}} & {{- end -}} v
}
`)

// goDefaultMethodTemplate is a template for generating a PopulateDefaults method
Expand Down Expand Up @@ -1323,6 +1347,10 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
// to generated for the struct.
var associatedLeafGetters []*generatedLeafGetter

// associatedLeafSetters is a slice of structs which define the set of leaf setters
// to generated for the struct.
var associatedLeafSetters []*generatedLeafSetter

associatedDefaultMethod := generatedDefaultMethod{
Receiver: targetStruct.Name,
}
Expand Down Expand Up @@ -1495,6 +1523,16 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
Default: field.LangType.DefaultValue,
})

// If we are generating leaf setters, then append the relevant information
// to the associatedLeafSetters slice to be generated along with other
// associated methods.
associatedLeafSetters = append(associatedLeafSetters, &generatedLeafSetter{
Name: fieldName,
Type: fType,
IsPtr: scalarField,
Receiver: targetStruct.Name,
})

fieldDef = &goStructField{
Name: fieldName,
Type: fType,
Expand Down Expand Up @@ -1644,6 +1682,13 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
errs = append(errs, err)
}
}

if goOpts.GenerateLeafSetters {
if err := generateLeafSetters(&methodBuf, associatedLeafSetters); err != nil {
errs = append(errs, err)
}
}

if goOpts.GeneratePopulateDefault {
associatedDefaultMethod.Leaves = associatedLeafGetters
if err := goDefaultMethodTemplate.Execute(&methodBuf, associatedDefaultMethod); err != nil {
Expand Down Expand Up @@ -1834,6 +1879,18 @@ func generateLeafGetters(buf *bytes.Buffer, leaves []*generatedLeafGetter) error
return errs.Err()
}

// generateLeafSetters generates SetXXX methods for the leaf fields described by
// the supplied slice of generatedLeafSetter structs.
func generateLeafSetters(buf *bytes.Buffer, leaves []*generatedLeafSetter) error {
var errs errlist.List
for _, l := range leaves {
if err := goLeafSetterTemplate.Execute(buf, l); err != nil {
errs.Add(err)
}
}
return errs.Err()
}

// generateGetOrCreateList generates a getter function similar to that created
// by the generateGetOrCreateStruct function for maps within the generated Go
// code (which represent YANG lists). It handles both simple and composite key
Expand Down
150 changes: 150 additions & 0 deletions gogen/gogen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,156 @@ func (t *Container) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTyp
func (*Container) ΛBelongingModule() string {
return "m1"
}
`,
},
}, {
name: "container with leaf setters",
inStructToMap: &ygen.ParsedDirectory{
Name: "Container",
Fields: map[string]*ygen.NodeDetails{
"leafStr": {
Name: "LeafStr",
YANGDetails: ygen.YANGNodeDetails{
Name: "leafStr",
Defaults: nil,
RootElementModule: "m1",
Path: "/m1/foo/bar/leafStr",
LeafrefTargetPath: "",
},
Type: ygen.LeafNode,
LangType: &ygen.MappedType{
NativeType: "string",
UnionTypes: nil,
IsEnumeratedValue: false,
ZeroValue: `""`,
DefaultValue: nil,
},
MappedPaths: [][]string{{"bar", "leafStr"}},
MappedPathModules: [][]string{{"m1", "m1"}},
ShadowMappedPaths: nil,
ShadowMappedPathModules: nil,
},
"leafUnion": {
Name: "LeafUnion",
YANGDetails: ygen.YANGNodeDetails{
Name: "leafUnion",
Defaults: nil,
RootElementModule: "m1",
Path: "/m1/foo/bar/leafUnion",
LeafrefTargetPath: "",
},
Type: ygen.LeafNode,
LangType: &ygen.MappedType{
NativeType: "Container_U1_Union",
UnionTypes: map[string]ygen.MappedUnionSubtype{
"string": {
Index: 0,
},
"int8": {
Index: 1,
},
},
IsEnumeratedValue: false,
ZeroValue: "nil",
DefaultValue: nil,
},
MappedPaths: [][]string{{"bar", "leafUnion"}},
MappedPathModules: [][]string{{"m1", "m1"}},
ShadowMappedPaths: nil,
ShadowMappedPathModules: nil,
},
},
Path: "/m1/foo",
BelongingModule: "m1",
},
inGoOpts: GoOpts{
GenerateJSONSchema: true,
GenerateLeafSetters: true,
},
want: wantGoStructOut{
structs: `
// Container represents the /m1/foo YANG schema element.
type Container struct {
LeafStr *string ` + "`" + `path:"bar/leafStr" module:"m1/m1"` + "`" + `
LeafUnion Container_U1_Union ` + "`" + `path:"bar/leafUnion" module:"m1/m1"` + "`" + `
}
// IsYANGGoStruct ensures that Container implements the yang.GoStruct
// interface. This allows functions that need to handle this struct to
// identify it as being generated by ygen.
func (*Container) IsYANGGoStruct() {}
`,
methods: `
// SetLeafStr sets the value of the leaf LeafStr in the Container
// struct.
func (t *Container) SetLeafStr(v string) {
t.LeafStr = &v
}
// SetLeafUnion sets the value of the leaf LeafUnion in the Container
// struct.
func (t *Container) SetLeafUnion(v Container_U1_Union) {
t.LeafUnion = v
}
// Validate validates s against the YANG schema corresponding to its type.
func (t *Container) ΛValidate(opts ...ygot.ValidationOption) error {
if err := ytypes.Validate(SchemaTree["Container"], t, opts...); err != nil {
return err
}
return nil
}
// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
// that are included in the generated code.
func (t *Container) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
// ΛBelongingModule returns the name of the module that defines the namespace
// of Container.
func (*Container) ΛBelongingModule() string {
return "m1"
}
`,
interfaces: `
// Container_U1_Union is an interface that is implemented by valid types for the union
// for the leaf /m1/foo/bar/leafUnion within the YANG schema.
type Container_U1_Union interface {
Is_Container_U1_Union()
}
// Container_U1_Union_Int8 is used when /m1/foo/bar/leafUnion
// is to be set to a int8 value.
type Container_U1_Union_Int8 struct {
Int8 int8
}
// Is_Container_U1_Union ensures that Container_U1_Union_Int8
// implements the Container_U1_Union interface.
func (*Container_U1_Union_Int8) Is_Container_U1_Union() {}
// Container_U1_Union_String is used when /m1/foo/bar/leafUnion
// is to be set to a string value.
type Container_U1_Union_String struct {
String string
}
// Is_Container_U1_Union ensures that Container_U1_Union_String
// implements the Container_U1_Union interface.
func (*Container_U1_Union_String) Is_Container_U1_Union() {}
// To_Container_U1_Union takes an input interface{} and attempts to convert it to a struct
// which implements the Container_U1_Union union. It returns an error if the interface{} supplied
// cannot be converted to a type within the union.
func (t *Container) To_Container_U1_Union(i interface{}) (Container_U1_Union, error) {
switch v := i.(type) {
case int8:
return &Container_U1_Union_Int8{v}, nil
case string:
return &Container_U1_Union_String{v}, nil
default:
return nil, fmt.Errorf("cannot convert %v to Container_U1_Union, unknown union type, got: %T, want any of [int8, string]", i, i)
}
}
`,
},
}}
Expand Down
Loading

0 comments on commit 6259f3a

Please sign in to comment.