Skip to content

Commit

Permalink
Remove Resolve function from generated code (#405)
Browse files Browse the repository at this point in the history
* Remove Resolve function from generated code
  • Loading branch information
wenovus authored Jun 23, 2020
1 parent 718c902 commit 0ce45b1
Show file tree
Hide file tree
Showing 27 changed files with 1,491 additions and 1,640 deletions.
2,636 changes: 1,361 additions & 1,275 deletions exampleocpath/ocpath.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion exampleocpath/ocpath_augment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package exampleocpath

func (r *Root) WithName(name string) *Root {
r.customData["name"] = name
r.PutCustomData("name", name)
return r
}
59 changes: 54 additions & 5 deletions ygot/path_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

package ygot

import gpb "github.com/openconfig/gnmi/proto/gnmi"
import (
"fmt"

gpb "github.com/openconfig/gnmi/proto/gnmi"
)

const (
// PathStructInterfaceName is the name for the interface implemented by all
Expand All @@ -23,6 +27,9 @@ const (
// PathBaseTypeName is the type name of the common embedded struct
// containing the path information for a path struct.
PathBaseTypeName = "NodePath"
// FakeRootBaseTypeName is the type name of the fake root struct which
// should be embedded within the fake root path struct.
FakeRootBaseTypeName = "DeviceRootBase"
)

// PathStruct is an interface that is implemented by any generated path struct
Expand All @@ -47,9 +54,46 @@ type NodePath struct {
p PathStruct
}

// ResolvePath is a helper which returns the root PathStruct and absolute path
// of a PathStruct node.
func ResolvePath(n PathStruct) (PathStruct, []*gpb.PathElem, []error) {
// fakeRootPathStruct is an interface that is implemented by the fake root path
// struct type.
type fakeRootPathStruct interface {
PathStruct
Id() string
CustomData() map[string]interface{}
}

func NewDeviceRootBase(id string) *DeviceRootBase {
return &DeviceRootBase{NodePath: &NodePath{}, id: id, customData: map[string]interface{}{}}
}

// DeviceRootBase represents the fakeroot for all YANG schema elements.
type DeviceRootBase struct {
*NodePath
id string
// customData is meant to store root-specific information that may be
// useful to know when processing the resolved path. It is meant to be
// accessible through a user-defined accessor.
customData map[string]interface{}
}

// Id returns the device ID of the DeviceRootBase struct.
func (d *DeviceRootBase) Id() string {
return d.id
}

// CustomData returns the customData field of the DeviceRootBase struct.
func (d *DeviceRootBase) CustomData() map[string]interface{} {
return d.customData
}

// PutCustomData modifies an entry in the customData field of the DeviceRootBase struct.
func (d *DeviceRootBase) PutCustomData(key string, val interface{}) {
d.customData[key] = val
}

// ResolvePath is a helper which returns the resolved *gpb.Path of a PathStruct
// node as well as the root node's customData.
func ResolvePath(n PathStruct) (*gpb.Path, map[string]interface{}, []error) {
var p []*gpb.PathElem
var errs []error
for ; n.parent() != nil; n = n.parent() {
Expand All @@ -63,7 +107,12 @@ func ResolvePath(n PathStruct) (PathStruct, []*gpb.PathElem, []error) {
if errs != nil {
return nil, nil, errs
}
return n, p, nil

root, ok := n.(fakeRootPathStruct)
if !ok {
return nil, nil, append(errs, fmt.Errorf("ygot.ResolvePath(ygot.PathStruct): got unexpected root of (type, value) (%T, %v)", n, n))
}
return &gpb.Path{Target: root.Id(), Elem: p}, root.CustomData(), nil
}

// ResolveRelPath returns the partial []*gpb.PathElem representing the
Expand Down
31 changes: 18 additions & 13 deletions ygot/path_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ import (
"google.golang.org/protobuf/proto"
)

type deviceRoot struct {
*DeviceRootBase
}

func TestResolvePath(t *testing.T) {
root := &NodePath{}
wantId := "FOO"
wantCustomData := map[string]interface{}{"foo": "bar"}
root := deviceRoot{NewDeviceRootBase(wantId)}
root.PutCustomData("foo", "bar")

tests := []struct {
name string
in PathStruct
wantPathStr string
wantRoot PathStruct
wantErr bool
}{{
name: "simple",
Expand All @@ -42,12 +48,10 @@ func TestResolvePath(t *testing.T) {
},
},
wantPathStr: "/parent/child",
wantRoot: root,
}, {
name: "root",
in: root,
wantPathStr: "/",
wantRoot: root,
}, {
name: "list",
in: &NodePath{
Expand All @@ -60,7 +64,6 @@ func TestResolvePath(t *testing.T) {
},
},
wantPathStr: "/parent/values/value[ID=5]",
wantRoot: root,
}, {
name: "list with unconvertible key value",
in: &NodePath{
Expand All @@ -73,31 +76,33 @@ func TestResolvePath(t *testing.T) {
},
},
wantPathStr: "",
wantRoot: nil,
wantErr: true,
}}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wantP, err := StringToStructuredPath(tt.wantPathStr)
wantPath, err := StringToStructuredPath(tt.wantPathStr)
if err != nil {
t.Fatal(err)
}
wantPath := wantP.Elem
wantPath.Target = wantId

gotRoot, gotPath, gotErrs := ResolvePath(tt.in)
gotPath, gotCustomData, gotErrs := ResolvePath(tt.in)
if gotErrs != nil && !tt.wantErr {
t.Fatal(gotErrs)
} else if gotErrs == nil && tt.wantErr {
t.Fatal("expected error but did not receive any")
}

if gotRoot != tt.wantRoot {
t.Errorf("root not expected - got: %v, want: %v", gotRoot, tt.wantRoot)
if gotErrs != nil {
return
}

if diff := cmp.Diff(wantPath, gotPath, cmp.Comparer(proto.Equal)); diff != "" {
t.Errorf("ResolvePath returned diff (-want +got):\n%s", diff)
t.Errorf("ResolvePath returned diff (-want, +got):\n%s", diff)
}

if diff := cmp.Diff(wantCustomData, gotCustomData); diff != "" {
t.Errorf("ResolvePath: customData is not same as expected (-want, +got)\n%s", diff)
}
})
}
Expand Down
3 changes: 0 additions & 3 deletions ypathgen/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func TestWritePathCode(t *testing.T) {
name: "simple",
in: &ypathgen.GeneratedPathCode{
CommonHeader: "path common header\n",
OneOffHeader: "\npath one-off header\n",
Structs: []ypathgen.GoPathStructCodeSnippet{{
PathStructName: "PathStructName",
StructBase: "\nStructDef\n",
Expand All @@ -40,8 +39,6 @@ func TestWritePathCode(t *testing.T) {
},
want: `path common header
path one-off header
StructDef
ChildConstructor
Expand Down
8 changes: 4 additions & 4 deletions ypathgen/path_tests/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const deviceId = "dev"
// verifyPath checks the given path against expected.
func verifyPath(t *testing.T, p ygot.PathStruct, wantPathStr string) {
t.Helper()
gotPath, _, errs := ocp.Resolve(p)
gotPath, _, errs := ygot.ResolvePath(p)
if errs != nil {
t.Fatal(errs)
}
Expand All @@ -53,11 +53,11 @@ func verifyPath(t *testing.T, p ygot.PathStruct, wantPathStr string) {
// be the non-wildcard version of the path struct.
func verifyTypesEqual(t *testing.T, target ygot.PathStruct, wild ygot.PathStruct, equal bool) {
t.Helper()
targetPathProto, _, errs := ocp.Resolve(target)
targetPathProto, _, errs := ygot.ResolvePath(target)
if errs != nil {
t.Fatal(errs)
}
wildPathProto, _, errs := ocp.Resolve(wild)
wildPathProto, _, errs := ygot.ResolvePath(wild)
if errs != nil {
t.Fatal(errs)
}
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestCustomData(t *testing.T) {
p := root.WithName("foo").Interface("eth1").Ethernet().PortSpeed()
verifyPath(t, p, "/interfaces/interface[name=eth1]/ethernet/state/port-speed")

_, customData, _ := ocp.Resolve(p)
_, customData, _ := ygot.ResolvePath(p)
if diff := cmp.Diff(map[string]interface{}{"name": "foo"}, customData); diff != "" {
t.Errorf("resolved customData for root does not match (-want, +got):\n%s", diff)
}
Expand Down
55 changes: 11 additions & 44 deletions ypathgen/pathgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,7 @@ type GoImports struct {
// code are listed below:
// 1. Struct definitions for each container, list, or leaf schema node,
// as well as the fakeroot.
// 2. A Resolve() helper function, which can return the absolute path of
// any struct.
// 3. Next-level methods for the fakeroot and each non-leaf schema node,
// 2. Next-level methods for the fakeroot and each non-leaf schema node,
// which instantiate and return the next-level structs corresponding to
// its child schema nodes.
// With these components, the generated API is able to support absolute path
Expand Down Expand Up @@ -246,15 +244,13 @@ func (cg *GenConfig) GeneratePathCode(yangFiles, includePaths []string) (*Genera
type GeneratedPathCode struct {
Structs []GoPathStructCodeSnippet // Structs is the generated set of structs representing containers or lists in the input YANG models.
CommonHeader string // CommonHeader is the header that should be used for all output Go files.
OneOffHeader string // OneOffHeader defines the header that should be included in only one output Go file - such as package init statements.
}

// String method for GeneratedPathCode, which can be used to write all the
// generated code into a single file.
func (genCode GeneratedPathCode) String() string {
var gotCode strings.Builder
gotCode.WriteString(genCode.CommonHeader)
gotCode.WriteString(genCode.OneOffHeader)
for _, gotStruct := range genCode.Structs {
gotCode.WriteString(gotStruct.String())
}
Expand All @@ -276,7 +272,6 @@ func (genCode GeneratedPathCode) SplitFiles(fileN int) ([]string, error) {
structsPerFile := structN / fileN
var gotCode strings.Builder
gotCode.WriteString(genCode.CommonHeader)
gotCode.WriteString(genCode.OneOffHeader)

for i, gotStruct := range genCode.Structs {
// The last file contains the remainder of the structs.
Expand Down Expand Up @@ -381,51 +376,25 @@ Imported modules were sourced from:
package {{ .PackageName }}
import (
"fmt"
gpb "{{ .GNMIProtoPath }}"
{{ .SchemaStructPkgAlias }} "{{ .SchemaStructPkgPath }}"
"{{ .YgotImportPath }}"
)
`)

// goPathOneOffHeaderTemplate defines the template for package code that should
// be output in only one file.
goPathOneOffHeaderTemplate = mustTemplate("oneoffHeader", `
// Resolve is a helper which returns the resolved *gpb.Path of a PathStruct node.
func Resolve(n ygot.{{ .PathStructInterfaceName }}) (*gpb.Path, map[string]interface{}, []error) {
n, p, errs := ygot.ResolvePath(n)
root, ok := n.(*{{ .FakeRootTypeName }})
if !ok {
errs = append(errs, fmt.Errorf("Resolve(n ygot.{{ .PathStructInterfaceName }}): got unexpected root of (type, value) (%T, %v)", n, n))
}
if errs != nil {
return nil, nil, errs
}
return &gpb.Path{Target: root.id, Elem: p}, root.customData, nil
}
`)

// goPathFakeRootTemplate defines a template for the type definition and
// basic methods of the fakeroot object. The fakeroot object adheres to
// the methods of PathStructInterfaceName in order to allow its path
// struct descendents to use the Resolve() helper function for
// obtaining their absolute paths.
//
// customData is meant to store root-specific information that may be
// useful to know when processing the resolved path. It is meant to be
// accessible through a user-defined accessor.
// the methods of PathStructInterfaceName and FakeRootBaseTypeName in
// order to allow its path struct descendents to use the ygot.Resolve()
// helper function for obtaining their absolute paths.
goPathFakeRootTemplate = mustTemplate("fakeroot", `
// {{ .TypeName }} represents the {{ .YANGPath }} YANG schema element.
type {{ .TypeName }} struct {
*ygot.{{ .PathBaseTypeName }}
id string
customData map[string]interface{}
*ygot.{{ .FakeRootBaseTypeName }}
}
// DeviceRoot returns a new path object from which YANG paths can be constructed.
func DeviceRoot(id string) *{{ .TypeName }} {
return &{{ .TypeName }}{ {{- .PathBaseTypeName }}: &ygot.{{ .PathBaseTypeName }}{}, id: id, customData: map[string]interface{}{}}
return &{{ .TypeName }}{ygot.New{{- .FakeRootBaseTypeName }}(id)}
}
`)

Expand Down Expand Up @@ -580,13 +549,7 @@ func writeHeader(yangFiles, includePaths []string, cg *GenConfig, genCode *Gener
return err
}

var oneoff strings.Builder
if err := goPathOneOffHeaderTemplate.Execute(&oneoff, s); err != nil {
return err
}

genCode.CommonHeader = common.String()
genCode.OneOffHeader = oneoff.String()
return nil
}

Expand All @@ -601,6 +564,9 @@ type goPathStructData struct {
PathBaseTypeName string
// PathStructInterfaceName is the name of the interface which all path structs implement.
PathStructInterfaceName string
// FakeRootBaseTypeName is the type name of the fake root struct which
// should be embedded within the fake root path struct.
FakeRootBaseTypeName string
// WildcardSuffix is the suffix given to the wildcard versions of
// each node that distinguishes each from its non-wildcard counterpart.
WildcardSuffix string
Expand All @@ -614,6 +580,7 @@ func getStructData(directory *ygen.Directory) goPathStructData {
TypeName: directory.Name,
YANGPath: util.SlicePathToString(directory.Path),
PathBaseTypeName: ygot.PathBaseTypeName,
FakeRootBaseTypeName: ygot.FakeRootBaseTypeName,
PathStructInterfaceName: ygot.PathStructInterfaceName,
WildcardSuffix: WildcardSuffix,
}
Expand Down
7 changes: 3 additions & 4 deletions ypathgen/pathgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1410,13 +1410,12 @@ func (n *ContainerWithConfigAny) Leaflist2() *ContainerWithConfig_Leaflist2Any {
StructBase: `
// Root represents the /root YANG schema element.
type Root struct {
*ygot.NodePath
id string
customData map[string]interface{}
*ygot.DeviceRootBase
}
// DeviceRoot returns a new path object from which YANG paths can be constructed.
func DeviceRoot(id string) *Root {
return &Root{NodePath: &ygot.NodePath{}, id: id, customData: map[string]interface{}{}}
return &Root{ygot.NewDeviceRootBase(id)}
}
// Leaf represents the /root-module/leaf YANG schema element.
Expand Down
Loading

0 comments on commit 0ce45b1

Please sign in to comment.