diff --git a/pkg/yang/entry.go b/pkg/yang/entry.go index 7dd8bfd..eb06437 100644 --- a/pkg/yang/entry.go +++ b/pkg/yang/entry.go @@ -1098,7 +1098,7 @@ func (e *Entry) Augment(addErrors bool) (processed, skipped int) { // ApplyDeviate walks the deviations within the supplied entry, and applies them to the // schema. -func (e *Entry) ApplyDeviate() []error { +func (e *Entry) ApplyDeviate(deviateOpts ...DeviateOpt) []error { var errs []error appendErr := func(err error) { errs = append(errs, err) } for _, d := range e.Deviations { @@ -1168,7 +1168,9 @@ func (e *Entry) ApplyDeviate() []error { appendErr(fmt.Errorf("%s: node %s does not have a valid parent, but deviate not-supported references one", Source(e.Node), e.Name)) continue } - dp.delete(deviatedNode.Name) + if !hasIgnoreDeviateNotSupported(deviateOpts) { + dp.delete(deviatedNode.Name) + } case DeviationDelete: if devSpec.Config != TSUnset { deviatedNode.Config = TSUnset diff --git a/pkg/yang/entry_test.go b/pkg/yang/entry_test.go index d6a8c59..b2a5ae0 100644 --- a/pkg/yang/entry_test.go +++ b/pkg/yang/entry_test.go @@ -2957,6 +2957,7 @@ func TestDeviation(t *testing.T) { tests := []struct { desc string inFiles map[string]string + inParseOptions Options wants map[string][]deviationTest wantParseErrSubstring string wantProcessErrSubstring string @@ -3161,6 +3162,32 @@ func TestDeviation(t *testing.T) { entry: &Entry{Name: "survivor"}, }}, }, + }, { + desc: "deviation - not supported but ignored by option", + inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-notsupported.yang"))}, + inParseOptions: Options{ + DeviateOptions: DeviateOptions{ + IgnoreDeviateNotSupported: true, + }, + }, + wants: map[string][]deviationTest{ + "deviate": {{ + path: "/target", + entry: &Entry{Name: "target"}, + }, { + path: "/target-list", + entry: &Entry{Name: "target-list"}, + }, { + path: "/a-leaf", + entry: &Entry{Name: "a-leaf"}, + }, { + path: "/a-leaflist", + entry: &Entry{Name: "a-leaflist"}, + }, { + path: "survivor", + entry: &Entry{Name: "survivor"}, + }}, + }, }, { desc: "deviation removing non-existent node", inFiles: map[string]string{ @@ -3527,6 +3554,7 @@ func TestDeviation(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { ms := NewModules() + ms.ParseOptions = tt.inParseOptions for name, mod := range tt.inFiles { if err := ms.Parse(mod, name); err != nil { diff --git a/pkg/yang/modules.go b/pkg/yang/modules.go index 162a440..1d47a4e 100644 --- a/pkg/yang/modules.go +++ b/pkg/yang/modules.go @@ -395,7 +395,7 @@ func (ms *Modules) Process() []error { for _, m := range devmods { e := ToEntry(m) if !dvP[e.Name] { - errs = append(errs, e.ApplyDeviate()...) + errs = append(errs, e.ApplyDeviate(ms.ParseOptions.DeviateOptions)...) dvP[e.Name] = true } } diff --git a/pkg/yang/options.go b/pkg/yang/options.go index 879ab4e..2de2ebd 100644 --- a/pkg/yang/options.go +++ b/pkg/yang/options.go @@ -27,4 +27,33 @@ type Options struct { // generated within the schema to store the logical grouping from which it // is derived. StoreUses bool + // DeviateOptions contains options for how deviations are handled. + DeviateOptions DeviateOptions +} + +// DeviateOptions contains options for how deviations are handled. +type DeviateOptions struct { + // IgnoreDeviateNotSupported indicates to the parser to retain nodes + // that are marked with "deviate not-supported". An example use case is + // where the user wants to interact with different targets that have + // different support for a leaf without having to use a second instance + // of an AST. + IgnoreDeviateNotSupported bool +} + +// IsDeviateOpt ensures that DeviateOptions satisfies the DeviateOpt interface. +func (DeviateOptions) IsDeviateOpt() {} + +// DeviateOpt is an interface that can be used in function arguments. +type DeviateOpt interface { + IsDeviateOpt() +} + +func hasIgnoreDeviateNotSupported(opts []DeviateOpt) bool { + for _, o := range opts { + if opt, ok := o.(DeviateOptions); ok { + return opt.IgnoreDeviateNotSupported + } + } + return false }