diff --git a/pkg/yang/entry.go b/pkg/yang/entry.go index fa50af1..2f9d743 100644 --- a/pkg/yang/entry.go +++ b/pkg/yang/entry.go @@ -660,6 +660,22 @@ func ToEntry(n Node) (e *Entry) { // when the group is used in multiple locations and the // grouping has a leafref that references outside the group. e = ToEntry(g).dup() + + switch determineYangVersion(g) { + case YANGVersion10: + if len(s.Augment) > 1 { + return newError(s, "multiple augments not allowed in yang version 1.0: %s", s.Name) + } + } + + // process augments + for _, x := range s.Augment { + a := ToEntry(x) + a.Parent = e + a.Augments = append(a.Augments, e) + e.Find(a.Name).merge(nil, a.Namespace(), a) + } + addExtraKeywordsToLeafEntry(n, e) return e } @@ -1045,6 +1061,33 @@ func ToEntry(n Node) (e *Entry) { return e } +// YANGVersion is the enum that represents the YANG Version. +type YANGVersion string + +const ( + YANGVersion10 YANGVersion = "1.0" + YANGVersion11 YANGVersion = "1.1" +) + +func determineYangVersion(n Node) YANGVersion { + p := n.ParentNode() + if p != nil { + return determineYangVersion(p) + } + var m *Module + var ok bool + if m, ok = n.(*Module); ok { + switch m.YangVersion.asString() { + case string(YANGVersion10): + return YANGVersion10 + case string(YANGVersion11): + return YANGVersion11 + } + } + // default to 1.0 + return YANGVersion10 +} + // addExtraKeywordsToLeafEntry stores the values for unimplemented keywords in leaf entries. func addExtraKeywordsToLeafEntry(n Node, e *Entry) { v := reflect.ValueOf(n).Elem() diff --git a/pkg/yang/entry_test.go b/pkg/yang/entry_test.go index 2000238..fb4ad5c 100644 --- a/pkg/yang/entry_test.go +++ b/pkg/yang/entry_test.go @@ -3871,6 +3871,267 @@ func TestLeafEntry(t *testing.T) { } } +func TestAugmentUses(t *testing.T) { + + tests := []struct { + name string + inModules map[string]string + WantErrors []string + pathExist [][]string + }{ + { + name: "Yang 1.0 fail multiple augment in use", + WantErrors: []string{"multiple augments not allowed in yang version 1.0: grouping-a"}, + inModules: map[string]string{ + "a.yang": ` + module mod-a { + yang-version 1.1; + namespace "urn:mod-a"; + prefix moda; + + include submod-a-one; + include submod-a-two; + } + `, + "b.yang": ` + submodule submod-a-one { + belongs-to mod-a { + prefix moda; + } + yang-version 1.0; + include submod-a-two; + + grouping grouping-a { + list range { + must 'end >= start' { + error-message + "'end' must be greater than or equal to 'start'."; + } + leaf start { + type uint16 { + range "1..10"; + } + } + leaf end { + type uint16 { + range "1..10"; + } + } + key "start end"; + } + container z { + + } + } + + augment "/moda:container-a" { + container augment-a { + uses grouping-a { + augment "range" { + choice timeout-type { + mandatory true; + case period { + leaf period { + type uint16 { + range "2..100"; + } + units second; + } + } + case boolean { + container boolean { + leaf enabled { + type boolean; + } + } + } + } + } + augment "z" { + leaf enableZ { + type boolean; + } + } + } + } + } + } + `, + "c.yang": ` + submodule submod-a-two { + belongs-to mod-a { + prefix moda; + } + yang-version 1.1; + + container container-a { + + container a { + } + + container b { + } + } + } + `, + }, + }, + { + name: "Yang 1.1 pass multiple augment in use", + pathExist: [][]string{ + {"container-a", "a"}, + {"container-a", "b"}, + {"container-a", "augment-a", "range", "start"}, + {"container-a", "augment-a", "z", "enableZ"}, + }, + inModules: map[string]string{ + "a.yang": ` + module mod-a { + yang-version 1.1; + namespace "urn:mod-a"; + prefix moda; + + include submod-a-one; + include submod-a-two; + } + `, + "b.yang": ` + submodule submod-a-one { + belongs-to mod-a { + prefix moda; + } + yang-version 1.1; + include submod-a-two; + + grouping grouping-a { + list range { + must 'end >= start' { + error-message + "'end' must be greater than or equal to 'start'."; + } + leaf start { + type uint16 { + range "1..10"; + } + } + leaf end { + type uint16 { + range "1..10"; + } + } + key "start end"; + } + container z { + + } + } + + augment "/moda:container-a" { + container augment-a { + uses grouping-a { + augment "range" { + choice timeout-type { + mandatory true; + case period { + leaf period { + type uint16 { + range "2..100"; + } + units second; + } + } + case boolean { + container boolean { + leaf enabled { + type boolean; + } + } + } + } + } + augment "z" { + leaf enableZ { + type boolean; + } + } + } + } + } + } + `, + "c.yang": ` + submodule submod-a-two { + belongs-to mod-a { + prefix moda; + } + yang-version 1.1; + + container container-a { + + container a { + } + + container b { + } + } + } + `, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ms := NewModules() + var errs []error + + for n, m := range tt.inModules { + if err := ms.Parse(m, n); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + t.Fatalf("ms.Parse(), got unexpected error parsing input modules: %v", errs) + } + + errs = append(errs, ms.Process()...) + + var errsStr = strings.Builder{} + for _, e := range errs { + errsStr.WriteString(e.Error()) + } + + if len(tt.WantErrors) > 0 { + for _, errStr := range tt.WantErrors { + if !strings.Contains(errsStr.String(), errStr) { + t.Errorf("expected error %s", errStr) + } + } + return + } + + var curElem *Entry + curElem, errs = ms.GetModule("mod-a") + if len(errs) > 0 { + t.Fatalf("GetModule returned errors: %v", errs) + } + + x := curElem + for _, p := range tt.pathExist { + for _, pe := range p { + y, ok := x.Dir[pe] + if !ok { + t.Fatalf("expected module %s to contain path %s, could not find element %s in parent, got children: %v", curElem.Name, strings.Join(p, "/"), pe, x.Dir) + } + x = y + } + x = curElem + } + }) + } +} + func TestLess(t *testing.T) { sErrors := sortedErrors{ {"testfile0", errors.New("test error0")}, diff --git a/pkg/yang/modules.go b/pkg/yang/modules.go index ab543d2..03cdac6 100644 --- a/pkg/yang/modules.go +++ b/pkg/yang/modules.go @@ -371,6 +371,18 @@ func (ms *Modules) Process() []error { } } + // rerun the error checks, after augmentation happened + for _, m := range ms.Modules { + errs = append(errs, ToEntry(m).GetErrors()...) + } + for _, m := range ms.SubModules { + errs = append(errs, ToEntry(m).GetErrors()...) + } + + if len(errs) > 0 { + return errorSort(errs) + } + // Now fix up all the choice statements to add in the missing case // statements. for _, m := range ms.Modules { diff --git a/pkg/yang/yang.go b/pkg/yang/yang.go index efff44a..822426f 100644 --- a/pkg/yang/yang.go +++ b/pkg/yang/yang.go @@ -624,13 +624,13 @@ type Uses struct { Parent Node `yang:"Parent,nomerge" json:"-"` Extensions []*Statement `yang:"Ext" json:"-"` - Augment *Augment `yang:"augment" json:",omitempty"` - Description *Value `yang:"description" json:",omitempty"` - IfFeature []*Value `yang:"if-feature" json:"-"` - Refine []*Refine `yang:"refine" json:"-"` - Reference *Value `yang:"reference" json:"-"` - Status *Value `yang:"status" json:"-"` - When *Value `yang:"when" json:",omitempty"` + Augment []*Augment `yang:"augment" json:",omitempty"` + Description *Value `yang:"description" json:",omitempty"` + IfFeature []*Value `yang:"if-feature" json:"-"` + Refine []*Refine `yang:"refine" json:"-"` + Reference *Value `yang:"reference" json:"-"` + Status *Value `yang:"status" json:"-"` + When *Value `yang:"when" json:",omitempty"` } func (Uses) Kind() string { return "uses" }