Skip to content

Commit

Permalink
Implement ytypes.SetNodeOpt (#230)
Browse files Browse the repository at this point in the history
Add an options framework for SetNode that allows behaviours to be controlled.
In this case, add a specific implementation that allows for the path in the data
tree to be created if it does not already exist.
  • Loading branch information
clubfest authored and robshakir committed Sep 19, 2018
1 parent 426e32f commit f479769
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 4 deletions.
31 changes: 28 additions & 3 deletions ytypes/node.go
Expand Up @@ -351,12 +351,37 @@ func appendElem(p *gpb.Path, e *gpb.PathElem) *gpb.Path {
}

// SetNode sets the value of the node specified by the supplied path from the specified root,
// whose schema must also be supplied. It will ensure that the node's ancestors are initialized.
func SetNode(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}) error {
// whose schema must also be supplied. It takes a set of options which can be used to specify set behaviours, such as
// whether or not to ensure that the node's ancestors are initialized.
func SetNode(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...SetNodeOpt) error {
_, err := retrieveNode(schema, root, path, nil, retrieveNodeArgs{
modifyRoot: true,
modifyRoot: hasInitMissingElements(opts),
val: val,
})

return err
}

// SetNodeOpt defines an interface that can be used to supply arguments to functions using SetNode.
type SetNodeOpt interface {
// IsSetNodeOpt is a marker method that is used to identify an instance of SetNodeOpt.
IsSetNodeOpt()
}

// InitMissingElements signals SetNode to initialize the node's ancestors and to ensure that keys are added
// into keyed lists(maps) if they are missing, before updating the node.
type InitMissingElements struct{}

// IsSetNodeOpt implements the SetNodeOpt interface.
func (*InitMissingElements) IsSetNodeOpt() {}

// hasInitMissingElements determines whether there is an instance of InitMissingElements within the supplied
// SetNodeOpt slice. It is used to determine whether to initialize the node's ancestors before updating the node.
func hasInitMissingElements(opts []SetNodeOpt) bool {
for _, o := range opts {
if _, ok := o.(*InitMissingElements); ok {
return true
}
}
return false
}
21 changes: 20 additions & 1 deletion ytypes/node_test.go
Expand Up @@ -1051,6 +1051,7 @@ func TestSetNodeWithSimpleSchema(t *testing.T) {
inParent interface{}
inPath *gpb.Path
inVal interface{}
inOpts []SetNodeOpt
wantErrSubstring string
want interface{}
}{
Expand All @@ -1070,14 +1071,32 @@ func TestSetNodeWithSimpleSchema(t *testing.T) {
inVal: &ExampleAnnotation{ConfigSource: "devicedemo"},
want: []ygot.Annotation{&ExampleAnnotation{ConfigSource: "devicedemo"}},
},
{
inDesc: "success setting annotation in inner node",
inSchema: simpleSchema,
inParent: &ListElemStruct1{},
inPath: mustPath("/outer/inner/@annotation"),
inVal: &ExampleAnnotation{ConfigSource: "devicedemo"},
inOpts: []SetNodeOpt{&InitMissingElements{}},
want: []ygot.Annotation{&ExampleAnnotation{ConfigSource: "devicedemo"}},
},
{
inDesc: "success setting int32 field in inner node",
inSchema: simpleSchema,
inParent: &ListElemStruct1{},
inPath: mustPath("/outer/inner/int32-leaf-field"),
inVal: ygot.Int32(42),
inOpts: []SetNodeOpt{&InitMissingElements{}},
want: ygot.Int32(42),
},
{
inDesc: "failed to set annotation in uninitialized node without InitMissingElements in SetNodeOpt",
inSchema: simpleSchema,
inParent: &ListElemStruct1{},
inPath: mustPath("/outer/inner/@annotation"),
inVal: &ExampleAnnotation{ConfigSource: "devicedemo"},
wantErrSubstring: "could not find children",
},
{
inDesc: "failed to set value on invalid node",
inSchema: simpleSchema,
Expand All @@ -1097,7 +1116,7 @@ func TestSetNodeWithSimpleSchema(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.inDesc, func(t *testing.T) {
err := SetNode(tt.inSchema, tt.inParent, tt.inPath, tt.inVal)
err := SetNode(tt.inSchema, tt.inParent, tt.inPath, tt.inVal, tt.inOpts...)
if diff := errdiff.Substring(err, tt.wantErrSubstring); diff != "" {
t.Fatalf("got %v\nwant %v", err, tt.wantErrSubstring)
}
Expand Down

0 comments on commit f479769

Please sign in to comment.