Skip to content

Commit 6259f3a

Browse files
kjahedwenovusrobshakir
authored
Generate leaf setters (#717)
* 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]>
1 parent 828caf0 commit 6259f3a

10 files changed

+790
-0
lines changed

generator/generator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ var (
9393
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.")
9494
generateDelete = flag.Bool("generate_delete", false, "If set to true, delete methods are generated for YANG lists (Go maps) within the Go code.")
9595
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.")
96+
generateLeafSetters = flag.Bool("generate_leaf_setters", false, "If set to true, setters for YANG leaves are generated within the Go code.")
9697
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.")
9798
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.")
9899
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.")
@@ -362,6 +363,7 @@ func main() {
362363
GenerateDeleteMethod: *generateDelete,
363364
GenerateAppendMethod: *generateAppend,
364365
GenerateLeafGetters: *generateLeafGetters,
366+
GenerateLeafSetters: *generateLeafSetters,
365367
GeneratePopulateDefault: *generatePopulateDefault,
366368
ValidateFunctionName: *generateValidateFnName,
367369
GenerateSimpleUnions: *generateSimpleUnions,

gogen/codegen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ type GoOpts struct {
9090
// whether a field has been explicitly set to the zero value (i.e., an integer
9191
// field is set to 0), or whether the field was actually unset.
9292
GenerateLeafGetters bool
93+
// GenerateLeafSetters specifies whether Set* methods should be created for
94+
// leaf fields of a struct.
95+
GenerateLeafSetters bool
9396
// GeneratePopulateDefault specifies whether a PopulateDefaults method
9497
// should be generated for every GoStruct that recursively populates
9598
// default values within the subtree.

gogen/codegen_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func TestSimpleStructs(t *testing.T) {
6464
GoOptions: GoOpts{
6565
GenerateSimpleUnions: true,
6666
GenerateLeafGetters: true,
67+
GenerateLeafSetters: true,
6768
GeneratePopulateDefault: true,
6869
},
6970
},
@@ -197,6 +198,7 @@ func TestSimpleStructs(t *testing.T) {
197198
GoOptions: GoOpts{
198199
GenerateSimpleUnions: true,
199200
GenerateLeafGetters: true,
201+
GenerateLeafSetters: true,
200202
GeneratePopulateDefault: true,
201203
},
202204
},
@@ -652,6 +654,7 @@ func TestSimpleStructs(t *testing.T) {
652654
GoOptions: GoOpts{
653655
GenerateSimpleUnions: true,
654656
GenerateLeafGetters: true,
657+
GenerateLeafSetters: true,
655658
},
656659
},
657660
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "enum-union.formatted-txt"),
@@ -711,6 +714,7 @@ func TestSimpleStructs(t *testing.T) {
711714
},
712715
GoOptions: GoOpts{
713716
GenerateLeafGetters: true,
717+
GenerateLeafSetters: true,
714718
GeneratePopulateDefault: true,
715719
AppendEnumSuffixForSimpleUnionEnums: true,
716720
},
@@ -732,6 +736,7 @@ func TestSimpleStructs(t *testing.T) {
732736
GoOptions: GoOpts{
733737
GenerateSimpleUnions: true,
734738
GenerateLeafGetters: true,
739+
GenerateLeafSetters: true,
735740
},
736741
},
737742
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "enum-module.formatted-txt"),
@@ -855,6 +860,26 @@ func TestSimpleStructs(t *testing.T) {
855860
},
856861
},
857862
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "openconfig-list-enum-key.leaf-getters.formatted-txt"),
863+
}, {
864+
name: "module with leaf setters",
865+
inFiles: []string{filepath.Join(datapath, "", "openconfig-list-enum-key.yang")},
866+
inConfig: CodeGenerator{
867+
IROptions: ygen.IROptions{
868+
TransformationOptions: ygen.TransformationOpts{
869+
GenerateFakeRoot: true,
870+
ShortenEnumLeafNames: true,
871+
UseDefiningModuleForTypedefEnumNames: true,
872+
CompressBehaviour: genutil.PreferIntendedConfig,
873+
EnumerationsUseUnderscores: true,
874+
},
875+
},
876+
GoOptions: GoOpts{
877+
GenerateLeafSetters: true,
878+
GeneratePopulateDefault: true,
879+
GenerateSimpleUnions: true,
880+
},
881+
},
882+
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "openconfig-list-enum-key.leaf-setters.formatted-txt"),
858883
}, {
859884
name: "uncompressed module with two different enums",
860885
inFiles: []string{filepath.Join(datapath, "", "enum-list-uncompressed.yang")},

gogen/gogen.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,20 @@ type generatedLeafGetter struct {
281281
Receiver string
282282
}
283283

284+
// generatedLeafSetter is used to represent the parameters required to generate a
285+
// setter for a leaf within the generated Go code.
286+
type generatedLeafSetter struct {
287+
// Name is the name of the field. It is used as a suffix to Set to generate
288+
// the setter.
289+
Name string
290+
// Type is the type of the field, required by the setter method.
291+
Type string
292+
// IsPtr stores whether the value is a pointer.
293+
IsPtr bool
294+
// Receiver is the name of the receiver for the setter method.
295+
Receiver string
296+
}
297+
284298
// generatedDefaultMethod is used to represent parameters required to generate
285299
// a PopulateDefaults method for a GoStruct that recursively populates default
286300
// values within the subtree.
@@ -781,6 +795,16 @@ func (t *{{ .Receiver }}) Get{{ .Name }}() {{ .Type }} {
781795
}
782796
return {{ if .IsPtr -}} * {{- end -}} t.{{ .Name }}
783797
}
798+
`)
799+
800+
// goLeafSetterTemplate defines a template for a function that, for a
801+
// particular leaf, generates a setter method.
802+
goLeafSetterTemplate = mustMakeTemplate("setLeaf", `
803+
// Set{{ .Name }} sets the value of the leaf {{ .Name }} in the {{ .Receiver }}
804+
// struct.
805+
func (t *{{ .Receiver }}) Set{{ .Name }}(v {{ .Type }}) {
806+
t.{{ .Name }} = {{ if .IsPtr -}} & {{- end -}} v
807+
}
784808
`)
785809

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

1350+
// associatedLeafSetters is a slice of structs which define the set of leaf setters
1351+
// to generated for the struct.
1352+
var associatedLeafSetters []*generatedLeafSetter
1353+
13261354
associatedDefaultMethod := generatedDefaultMethod{
13271355
Receiver: targetStruct.Name,
13281356
}
@@ -1495,6 +1523,16 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
14951523
Default: field.LangType.DefaultValue,
14961524
})
14971525

1526+
// If we are generating leaf setters, then append the relevant information
1527+
// to the associatedLeafSetters slice to be generated along with other
1528+
// associated methods.
1529+
associatedLeafSetters = append(associatedLeafSetters, &generatedLeafSetter{
1530+
Name: fieldName,
1531+
Type: fType,
1532+
IsPtr: scalarField,
1533+
Receiver: targetStruct.Name,
1534+
})
1535+
14981536
fieldDef = &goStructField{
14991537
Name: fieldName,
15001538
Type: fType,
@@ -1644,6 +1682,13 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
16441682
errs = append(errs, err)
16451683
}
16461684
}
1685+
1686+
if goOpts.GenerateLeafSetters {
1687+
if err := generateLeafSetters(&methodBuf, associatedLeafSetters); err != nil {
1688+
errs = append(errs, err)
1689+
}
1690+
}
1691+
16471692
if goOpts.GeneratePopulateDefault {
16481693
associatedDefaultMethod.Leaves = associatedLeafGetters
16491694
if err := goDefaultMethodTemplate.Execute(&methodBuf, associatedDefaultMethod); err != nil {
@@ -1834,6 +1879,18 @@ func generateLeafGetters(buf *bytes.Buffer, leaves []*generatedLeafGetter) error
18341879
return errs.Err()
18351880
}
18361881

1882+
// generateLeafSetters generates SetXXX methods for the leaf fields described by
1883+
// the supplied slice of generatedLeafSetter structs.
1884+
func generateLeafSetters(buf *bytes.Buffer, leaves []*generatedLeafSetter) error {
1885+
var errs errlist.List
1886+
for _, l := range leaves {
1887+
if err := goLeafSetterTemplate.Execute(buf, l); err != nil {
1888+
errs.Add(err)
1889+
}
1890+
}
1891+
return errs.Err()
1892+
}
1893+
18371894
// generateGetOrCreateList generates a getter function similar to that created
18381895
// by the generateGetOrCreateStruct function for maps within the generated Go
18391896
// code (which represent YANG lists). It handles both simple and composite key

gogen/gogen_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,6 +1843,156 @@ func (t *Container) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTyp
18431843
func (*Container) ΛBelongingModule() string {
18441844
return "m1"
18451845
}
1846+
`,
1847+
},
1848+
}, {
1849+
name: "container with leaf setters",
1850+
inStructToMap: &ygen.ParsedDirectory{
1851+
Name: "Container",
1852+
Fields: map[string]*ygen.NodeDetails{
1853+
"leafStr": {
1854+
Name: "LeafStr",
1855+
YANGDetails: ygen.YANGNodeDetails{
1856+
Name: "leafStr",
1857+
Defaults: nil,
1858+
RootElementModule: "m1",
1859+
Path: "/m1/foo/bar/leafStr",
1860+
LeafrefTargetPath: "",
1861+
},
1862+
Type: ygen.LeafNode,
1863+
LangType: &ygen.MappedType{
1864+
NativeType: "string",
1865+
UnionTypes: nil,
1866+
IsEnumeratedValue: false,
1867+
ZeroValue: `""`,
1868+
DefaultValue: nil,
1869+
},
1870+
MappedPaths: [][]string{{"bar", "leafStr"}},
1871+
MappedPathModules: [][]string{{"m1", "m1"}},
1872+
ShadowMappedPaths: nil,
1873+
ShadowMappedPathModules: nil,
1874+
},
1875+
"leafUnion": {
1876+
Name: "LeafUnion",
1877+
YANGDetails: ygen.YANGNodeDetails{
1878+
Name: "leafUnion",
1879+
Defaults: nil,
1880+
RootElementModule: "m1",
1881+
Path: "/m1/foo/bar/leafUnion",
1882+
LeafrefTargetPath: "",
1883+
},
1884+
Type: ygen.LeafNode,
1885+
LangType: &ygen.MappedType{
1886+
NativeType: "Container_U1_Union",
1887+
UnionTypes: map[string]ygen.MappedUnionSubtype{
1888+
"string": {
1889+
Index: 0,
1890+
},
1891+
"int8": {
1892+
Index: 1,
1893+
},
1894+
},
1895+
IsEnumeratedValue: false,
1896+
ZeroValue: "nil",
1897+
DefaultValue: nil,
1898+
},
1899+
MappedPaths: [][]string{{"bar", "leafUnion"}},
1900+
MappedPathModules: [][]string{{"m1", "m1"}},
1901+
ShadowMappedPaths: nil,
1902+
ShadowMappedPathModules: nil,
1903+
},
1904+
},
1905+
Path: "/m1/foo",
1906+
BelongingModule: "m1",
1907+
},
1908+
inGoOpts: GoOpts{
1909+
GenerateJSONSchema: true,
1910+
GenerateLeafSetters: true,
1911+
},
1912+
want: wantGoStructOut{
1913+
structs: `
1914+
// Container represents the /m1/foo YANG schema element.
1915+
type Container struct {
1916+
LeafStr *string ` + "`" + `path:"bar/leafStr" module:"m1/m1"` + "`" + `
1917+
LeafUnion Container_U1_Union ` + "`" + `path:"bar/leafUnion" module:"m1/m1"` + "`" + `
1918+
}
1919+
1920+
// IsYANGGoStruct ensures that Container implements the yang.GoStruct
1921+
// interface. This allows functions that need to handle this struct to
1922+
// identify it as being generated by ygen.
1923+
func (*Container) IsYANGGoStruct() {}
1924+
`,
1925+
methods: `
1926+
// SetLeafStr sets the value of the leaf LeafStr in the Container
1927+
// struct.
1928+
func (t *Container) SetLeafStr(v string) {
1929+
t.LeafStr = &v
1930+
}
1931+
1932+
// SetLeafUnion sets the value of the leaf LeafUnion in the Container
1933+
// struct.
1934+
func (t *Container) SetLeafUnion(v Container_U1_Union) {
1935+
t.LeafUnion = v
1936+
}
1937+
1938+
// Validate validates s against the YANG schema corresponding to its type.
1939+
func (t *Container) ΛValidate(opts ...ygot.ValidationOption) error {
1940+
if err := ytypes.Validate(SchemaTree["Container"], t, opts...); err != nil {
1941+
return err
1942+
}
1943+
return nil
1944+
}
1945+
1946+
// ΛEnumTypeMap returns a map, keyed by YANG schema path, of the enumerated types
1947+
// that are included in the generated code.
1948+
func (t *Container) ΛEnumTypeMap() map[string][]reflect.Type { return ΛEnumTypes }
1949+
1950+
// ΛBelongingModule returns the name of the module that defines the namespace
1951+
// of Container.
1952+
func (*Container) ΛBelongingModule() string {
1953+
return "m1"
1954+
}
1955+
`,
1956+
interfaces: `
1957+
// Container_U1_Union is an interface that is implemented by valid types for the union
1958+
// for the leaf /m1/foo/bar/leafUnion within the YANG schema.
1959+
type Container_U1_Union interface {
1960+
Is_Container_U1_Union()
1961+
}
1962+
1963+
// Container_U1_Union_Int8 is used when /m1/foo/bar/leafUnion
1964+
// is to be set to a int8 value.
1965+
type Container_U1_Union_Int8 struct {
1966+
Int8 int8
1967+
}
1968+
1969+
// Is_Container_U1_Union ensures that Container_U1_Union_Int8
1970+
// implements the Container_U1_Union interface.
1971+
func (*Container_U1_Union_Int8) Is_Container_U1_Union() {}
1972+
1973+
// Container_U1_Union_String is used when /m1/foo/bar/leafUnion
1974+
// is to be set to a string value.
1975+
type Container_U1_Union_String struct {
1976+
String string
1977+
}
1978+
1979+
// Is_Container_U1_Union ensures that Container_U1_Union_String
1980+
// implements the Container_U1_Union interface.
1981+
func (*Container_U1_Union_String) Is_Container_U1_Union() {}
1982+
1983+
// To_Container_U1_Union takes an input interface{} and attempts to convert it to a struct
1984+
// which implements the Container_U1_Union union. It returns an error if the interface{} supplied
1985+
// cannot be converted to a type within the union.
1986+
func (t *Container) To_Container_U1_Union(i interface{}) (Container_U1_Union, error) {
1987+
switch v := i.(type) {
1988+
case int8:
1989+
return &Container_U1_Union_Int8{v}, nil
1990+
case string:
1991+
return &Container_U1_Union_String{v}, nil
1992+
default:
1993+
return nil, fmt.Errorf("cannot convert %v to Container_U1_Union, unknown union type, got: %T, want any of [int8, string]", i, i)
1994+
}
1995+
}
18461996
`,
18471997
},
18481998
}}

0 commit comments

Comments
 (0)