Skip to content

Add fields to IR and IROptions used by settting the path origin from the YANG module name or a specified name. #1030

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 28, 2025
13 changes: 13 additions & 0 deletions ygen/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,19 @@ func getOrderedDirDetails(langMapper LangMapper, directory map[string]*Directory
if hasShadowField {
nd.YANGDetails.ShadowSchemaPath = util.SchemaTreePathNoModule(shadowField)
}
// If IROptions.PathOriginName has a value, the value is set to the Origin of the node.
// Else if IROptions.UseModuleNameAsPathOrigin of the node is true,
// YANG module name is set to the Origin of the node.
// Else "" is set to the Origin of the node.
switch {
case opts.PathOriginName != "":
nd.YANGDetails.Origin = opts.PathOriginName
case opts.UseModuleNameAsPathOrigin:
nd.YANGDetails.Origin = nd.YANGDetails.BelongingModule
default:
// TODO: read the origin from the relevant YANG extension.
nd.YANGDetails.Origin = ""
}

switch {
case field.IsLeaf(), field.IsLeafList():
Expand Down
176 changes: 176 additions & 0 deletions ygen/directory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
package ygen

import (
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/openconfig/gnmi/errdiff"
"github.com/openconfig/goyang/pkg/yang"
"github.com/openconfig/ygot/genutil"
"github.com/openconfig/ygot/yangschema"
)

// errToString returns the string representation of err and the empty string if
Expand Down Expand Up @@ -702,3 +705,176 @@ func TestFindMapPaths(t *testing.T) {
})
}
}

type mockLangMapper struct{}

func (*mockLangMapper) FieldName(_ *yang.Entry) (string, error) { return "", nil }

func (*mockLangMapper) DirectoryName(_ *yang.Entry, _ genutil.CompressBehaviour) (string, error) {
return "", nil
}

func (*mockLangMapper) KeyLeafType(_ *yang.Entry, _ IROptions) (*MappedType, error) {
return nil, nil
}

func (*mockLangMapper) LeafType(_ *yang.Entry, _ IROptions) (*MappedType, error) { return nil, nil }

func (*mockLangMapper) PackageName(_ *yang.Entry, _ genutil.CompressBehaviour, _ bool) (string, error) {
return "", nil
}

func (*mockLangMapper) InjectEnumSet(_ map[string]*yang.Entry, _, _, _, _, _, _ bool, _ []string) error {
return nil
}

func (*mockLangMapper) InjectSchemaTree(_ []*yang.Entry) error { return nil }

func (*mockLangMapper) PopulateEnumFlags(EnumeratedYANGType, *yang.YangType) map[string]string {
return nil
}

func (*mockLangMapper) PopulateFieldFlags(NodeDetails, *yang.Entry) map[string]string { return nil }

func (*mockLangMapper) setEnumSet(*enumSet) {}

func (*mockLangMapper) setSchemaTree(*yangschema.Tree) {}

func TestGetOrderedDirDetailsPathOrigin(t *testing.T) {
ms := compileModules(t, map[string]string{
"a-module": `
module a-module {
prefix "m";
namespace "urn:m";

container a-container {
leaf field-a {
type string;
}
}

container b-container {
container config {
leaf field-b {
type string;
}
}
container state {
leaf field-b {
type string;
}
}

container c-container {
leaf field-d {
type string;
}
}
}
}
`,
})

tests := []struct {
name string
inDirectory map[string]*Directory
inSchemaTree *yangschema.Tree
inOpts IROptions
wantPathOriginName map[string]string
wantErr bool
wantErrorSubstrings []string
}{{
name: "PathOriginName is set",
inDirectory: map[string]*Directory{
"/a-module/a-container": {
Name: "AContainer",
Entry: findEntry(t, ms, "a-module", "a-container/field-a"),
Fields: map[string]*yang.Entry{
"field-a": findEntry(t, ms, "a-module", "a-container/field-a"),
},
Path: []string{"", "a-module", "a-container"},
},
},
inSchemaTree: &yangschema.Tree{},
inOpts: IROptions{
PathOriginName: "explicit-origin",
},
wantPathOriginName: map[string]string{
"/a-module/a-container/field-a": "explicit-origin",
},
}, {
name: "UseModuleNameAsPathOrigin is true",
inDirectory: map[string]*Directory{
"/a-module/a-container": {
Name: "AContainer",
Entry: findEntry(t, ms, "a-module", "a-container/field-a"),
Fields: map[string]*yang.Entry{
"field-a": findEntry(t, ms, "a-module", "a-container/field-a"),
},
Path: []string{"", "a-module", "a-container"},
},
},
inSchemaTree: &yangschema.Tree{},
inOpts: IROptions{
UseModuleNameAsPathOrigin: true,
},
wantPathOriginName: map[string]string{
"/a-module/a-container/field-a": "a-module",
},
}, {
name: "PathOriginName and UseModuleNameAsPathOrigin are not set",
inDirectory: map[string]*Directory{
"/a-module/a-container": {
Name: "AContainer",
Entry: findEntry(t, ms, "a-module", "a-container/field-a"),
Fields: map[string]*yang.Entry{
"field-a": findEntry(t, ms, "a-module", "a-container/field-a"),
},
Path: []string{"", "a-module", "a-container"},
},
},
inSchemaTree: &yangschema.Tree{},
inOpts: IROptions{},
wantPathOriginName: map[string]string{
"/a-module/a-container/field-a": "",
},
}}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
langMapper := &mockLangMapper{}
got, err := getOrderedDirDetails(langMapper, tt.inDirectory, tt.inSchemaTree, tt.inOpts)

if tt.wantErr {
if err == nil {
t.Fatalf("getOrderedDirDetails() got no error, want error containing: %v", tt.wantErrorSubstrings)
}
for _, want := range tt.wantErrorSubstrings {
if !strings.Contains(err.Error(), want) {
t.Errorf("getOrderedDirDetails() got error: %v, want error containing: %q", err, want)
}
}
return
}

if err != nil {
t.Fatalf("getOrderedDirDetails() unexpected error: %v", err)
}

if diff := cmp.Diff(tt.wantPathOriginName, getPathOriginNames(got)); diff != "" {
t.Errorf("getOrderedDirDetails() PathOriginName mismatch (-want +got):\n%s", diff)
}
})
}
}

func getPathOriginNames(dirs map[string]*ParsedDirectory) map[string]string {
origins := make(map[string]string)
for path, dir := range dirs {
for _, field := range dir.Fields {
origins[path] = field.YANGDetails.Origin
break // The PathOriginName of the first field in each ParsedDirectory is verified.
}
}
return origins
}
7 changes: 7 additions & 0 deletions ygen/genir.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ type IROptions struct {
// to true.
// NOTE: This flag will be removed by v1 release.
AppendEnumSuffixForSimpleUnionEnums bool

// UseModuleNameAsPathOrigin specifies whether the YANG module name is
// set to the origin for generated gNMI paths when producing the IR.
UseModuleNameAsPathOrigin bool

// PathOriginName specifies the orign name for generated gNMI paths when producing the IR.
PathOriginName string
}

// GenerateIR creates the ygen intermediate representation for a set of
Expand Down
2 changes: 2 additions & 0 deletions ygen/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,8 @@ type YANGNodeDetails struct {
// statement in YANG:
// https://datatracker.ietf.org/doc/html/rfc7950#section-7.21.1
ConfigFalse bool
// Origin specifies the origin name for the generated gNMI path.
Origin string
}

// EnumeratedValueType is used to indicate the source YANG type
Expand Down
Loading