diff --git a/util/reflect.go b/util/reflect.go index e2779df6..bbb24307 100644 --- a/util/reflect.go +++ b/util/reflect.go @@ -21,7 +21,6 @@ import ( "github.com/kylelemons/godebug/pretty" "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/internal/yreflect" log "github.com/golang/glog" @@ -613,7 +612,7 @@ func (node *PathQueryNodeMemo) GetRoot() *PathQueryNodeMemo { // in, out are passed through from the caller to the iteration visitor function // and can be used to pass state in and out. They are not otherwise touched. // It returns a slice of errors encountered while processing the field. -type FieldIteratorFunc func(ni *NodeInfo, in, out interface{}) Errors +type FieldIteratorFunc func(ni *NodeInfo, in, out any) Errors // IterationAction is an enumeration representing different iteration actions. // @@ -632,6 +631,7 @@ const ( // in, out are passed through from the caller to the iteration visitor function // and can be used to pass state in and out. They are not otherwise touched. // It returns what next iteration action to take as well as an error. +// TODO Deprecate the ForEachField and ForEachDataField functions in favor of Walk. type FieldIteratorFunc2 func(ni *NodeInfo, in, out any) (IterationAction, Errors) // ForEachField recursively iterates through the fields of value (which may be @@ -648,175 +648,86 @@ type FieldIteratorFunc2 func(ni *NodeInfo, in, out any) (IterationAction, Errors // - iterFunction is executed on each scalar field. // // It returns a slice of errors encountered while processing the struct. -func ForEachField(schema *yang.Entry, value interface{}, in, out interface{}, iterFunction FieldIteratorFunc) Errors { - if IsValueNil(value) { - return nil - } - return forEachFieldInternal(&NodeInfo{Schema: schema, FieldValue: reflect.ValueOf(value)}, in, out, iterFunction) -} - -// forEachFieldInternal recursively iterates through the fields of value (which -// may be any Go type) and executes iterFunction on each field that is present -// within the supplied schema. Fields that are explicitly noted not to have -// a schema (e.g., annotation fields) are skipped. // -// - in, out are passed through from the caller to the iteration and can be used -// arbitrarily in the iteration function to carry state and results. -func forEachFieldInternal(ni *NodeInfo, in, out interface{}, iterFunction FieldIteratorFunc) Errors { - if IsValueNil(ni) { - return nil - } - - // If the field is an annotation, then we do not process it any further, including - // skipping running the iterFunction. - if IsYgotAnnotation(ni.StructField) { +// See util.Walk if more dynamic control of the traversal is needed. +func ForEachField(schema *yang.Entry, value any, in, out any, iterFunction FieldIteratorFunc) Errors { + if IsValueNil(value) { return nil } - - var errs Errors - errs = AppendErrs(errs, iterFunction(ni, in, out)) - - // Special processing where an "in" input value is provided. - var newPathQueryMemo func() *PathQueryNodeMemo - switch v := in.(type) { - case *PathQueryNodeMemo: // Memoization of path queries requested. - newPathQueryMemo = func() *PathQueryNodeMemo { - return &PathQueryNodeMemo{Parent: v, Memo: PathQueryMemo{}} - } - default: - } - - v := ni.FieldValue - t := v.Type() - - orderedMap, isOrderedMap := v.Interface().(goOrderedMap) - - switch { - case isOrderedMap, IsTypeSlice(t), IsTypeMap(t): - schema := *(ni.Schema) - schema.ListAttr = nil - - var relPath []string - if !schema.IsLeafList() { - // Leaf-list elements share the parent schema with listattr unset. - relPath = []string{schema.Name} - } - - var elemType reflect.Type - switch { - case isOrderedMap: - var err error - elemType, err = yreflect.OrderedMapElementType(orderedMap) - if err != nil { - errs = AppendErr(errs, err) - return errs - } - default: - elemType = t.Elem() + var v Visitor + errCollector := new(DefaultWalkErrors) + if inMemo, ok := in.(*PathQueryNodeMemo); ok { + v = forEachFieldMemoVisitor{ + // Any existing Memo map passed in will be ignored. + parent: inMemo.Parent, + out: out, + iterFunction: iterFunction, + errorCollector: errCollector, } - - nn := *ni - // The schema for each element is the list schema minus the list - // attrs. - nn.Schema = &schema - nn.Parent = ni - nn.PathFromParent = relPath - - visitListElement := func(k, v reflect.Value) { - nn := nn - nn.FieldValue = v - nn.FieldKey = k - switch in.(type) { - case *PathQueryNodeMemo: // Memoization of path queries requested. - errs = AppendErrs(errs, forEachFieldInternal(&nn, newPathQueryMemo(), out, iterFunction)) - default: - errs = AppendErrs(errs, forEachFieldInternal(&nn, in, out, iterFunction)) - } + } else { + v = &forEachFieldVisitor{ + in: in, + out: out, + iterFunction: iterFunction, + errorCollector: errCollector, } + } - switch { - case IsNilOrInvalidValue(v): - // Traverse the type tree only from this point. - visitListElement(reflect.Value{}, reflect.Zero(elemType)) - case IsTypeSlice(t): - for i := 0; i < ni.FieldValue.Len(); i++ { - visitListElement(reflect.Value{}, ni.FieldValue.Index(i)) - } - case isOrderedMap: - var err error - nn.FieldKeys, err = yreflect.OrderedMapKeys(orderedMap) - if err != nil { - errs = AppendErr(errs, err) - return errs - } - if err := yreflect.RangeOrderedMap(orderedMap, func(k, v reflect.Value) bool { - visitListElement(k, v) - return true - }); err != nil { - errs = AppendErr(errs, err) - } - case IsTypeMap(t): - nn.FieldKeys = ni.FieldValue.MapKeys() - for _, key := range ni.FieldValue.MapKeys() { - visitListElement(key, ni.FieldValue.MapIndex(key)) - } - } + // For back-compatibility with ForFieldField, we explicitly interleave the errors from + // both the traversal and the iterFunction into the shared Errors slice. + _ = Walk(v, WalkNodeFromGoStruct(value), DefaultWalkOptions().WithWalkErrors(errCollector).WithSchema(schema)) + // We ignore the returned WalkErrors because we know it's a reference to v.errorCollector. + return errCollector.Errors +} - case IsTypeStructPtr(t): - t = t.Elem() - if !IsNilOrInvalidValue(v) { - v = v.Elem() - } - fallthrough - case IsTypeStruct(t): - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - - // Do not handle annotation fields, since they have no schema. - if IsYgotAnnotation(sf) { - continue - } +// iterFuncToIterFunc2 converts a FieldIteratorFunc to FieldIteratorFunc2. +func iterFuncToIterFunc2(iterFunction FieldIteratorFunc) FieldIteratorFunc2 { + return func(ni *NodeInfo, in, out any) (IterationAction, Errors) { + return ContinueIteration, iterFunction(ni, in, out) + } +} - nn := &NodeInfo{ - Parent: ni, - StructField: sf, - FieldValue: reflect.Zero(sf.Type), - } - if !IsNilOrInvalidValue(v) { - nn.FieldValue = v.Field(i) - } - ps, err := SchemaPaths(nn.StructField) - if err != nil { - return NewErrs(err) - } +type forEachFieldVisitor struct { + in, out any + errorCollector *DefaultWalkErrors + iterFunction FieldIteratorFunc +} - for _, p := range ps { - nn.Schema = FirstChild(ni.Schema, p) - if nn.Schema == nil { - e := fmt.Errorf("forEachFieldInternal could not find child schema with path %v from schema name %s", p, ni.Schema.Name) - DbgPrint(e.Error()) - log.Errorln(e) - continue - } - nn.PathFromParent = p - switch in.(type) { - case *PathQueryNodeMemo: // Memoization of path queries requested. - errs = AppendErrs(errs, forEachFieldInternal(nn, newPathQueryMemo(), out, iterFunction)) - default: - errs = AppendErrs(errs, forEachFieldInternal(nn, in, out, iterFunction)) - } - } - } +func (vf *forEachFieldVisitor) Visit(node WalkNode) Visitor { + if node == nil { + return nil + } + ni := node.NodeInfo() + if err := vf.iterFunction(ni, vf.in, vf.out); err != nil { + vf.errorCollector.Collect(err) } + return vf +} - return errs +type forEachFieldMemoVisitor struct { + parent *PathQueryNodeMemo + out any + errorCollector *DefaultWalkErrors + iterFunction FieldIteratorFunc } -// iterFuncToIterFunc2 converts a FieldIteratorFunc to FieldIteratorFunc2. -func iterFuncToIterFunc2(iterFunction FieldIteratorFunc) FieldIteratorFunc2 { - return func(ni *NodeInfo, in, out interface{}) (IterationAction, Errors) { - return ContinueIteration, iterFunction(ni, in, out) +func (vf forEachFieldMemoVisitor) Visit(node WalkNode) Visitor { + if node == nil { + return nil } + // Each children sibling needs a dedicated (not shared) Memo data structure (map). + in := &PathQueryNodeMemo{ + Parent: vf.parent, + Memo: PathQueryMemo{}, + } + ni := node.NodeInfo() + if err := vf.iterFunction(ni, in, vf.out); err != nil { + vf.errorCollector.Collect(err) + } + // Since we use a value receiver vf instead of a pointer receiver, + // vf is passed as a copy and it's safe to manipulate. + vf.parent = in + return vf } // ForEachDataField iterates the value supplied and calls the iterFunction for @@ -825,13 +736,13 @@ func iterFuncToIterFunc2(iterFunction FieldIteratorFunc) FieldIteratorFunc2 { // without inspection by this function, and can be used by the caller to store // input and output during the iteration through the data tree. // -// Deprecated: Use ForEachDataField2 instead. -func ForEachDataField(value, in, out interface{}, iterFunction FieldIteratorFunc) Errors { +// Deprecated: Use ForEachDataField2 or util.Walk instead. +func ForEachDataField(value, in, out any, iterFunction FieldIteratorFunc) Errors { if IsValueNil(value) { return nil } - return forEachDataFieldInternal(&NodeInfo{FieldValue: reflect.ValueOf(value)}, in, out, iterFuncToIterFunc2(iterFunction)) + return ForEachDataField2(value, in, out, iterFuncToIterFunc2(iterFunction)) } // ForEachDataField2 is an improved ForEachDataField that allows iteration over @@ -843,141 +754,46 @@ func ForEachDataField(value, in, out interface{}, iterFunction FieldIteratorFunc // The in and out arguments are passed to the iterFunction without inspection // by this function, and can be used by the caller to store input and output // during the iteration through the data tree. +// +// See util.Walk if more dynamic control of the traversal is needed. func ForEachDataField2(value, in, out any, iterFunction FieldIteratorFunc2) Errors { if IsValueNil(value) { return nil } - - return forEachDataFieldInternal(&NodeInfo{FieldValue: reflect.ValueOf(value)}, in, out, iterFunction) + errCollector := new(DefaultWalkErrors) + v := &forEachDataField2Visitor{ + in: in, + out: out, + iterFunction2: iterFunction, + errorCollector: errCollector, + } + // For back-compatibility with ForFieldField, we explicitly interleave the errors from + // both the traversal and the iterFunction into the shared Errors slice. + _ = Walk(v, WalkNodeFromGoStruct(value), DefaultWalkOptions().WithWalkErrors(errCollector)) + // We ignore the returned WalkErrors because we know it's a reference to v.errorCollector. + return errCollector.Errors } -func forEachDataFieldInternal(ni *NodeInfo, in, out any, iterFunction FieldIteratorFunc2) Errors { - if IsValueNil(ni) { - return nil - } +type forEachDataField2Visitor struct { + in, out any + errorCollector *DefaultWalkErrors + iterFunction2 FieldIteratorFunc2 +} - if IsNilOrInvalidValue(ni.FieldValue) { - // Skip any fields that are nil within the data tree, since we - // do not need to iterate on them. +func (vf *forEachDataField2Visitor) Visit(node WalkNode) Visitor { + if node == nil { return nil } - - var errs Errors - // Run the iterator function for this field. - iterAction, err := iterFunction(ni, in, out) - errs = AppendErrs(errs, err) - switch iterAction { - case DoNotIterateDescendants: - return errs - case ContinueIteration: - default: - errs = AppendErr(errs, fmt.Errorf("unhandled IterationAction type: %v", iterAction)) - return errs + ni := node.NodeInfo() + action, err := vf.iterFunction2(ni, vf.in, vf.out) + if err != nil { + vf.errorCollector.Collect(err) } - - v := ni.FieldValue - t := v.Type() - - orderedMap, isOrderedMap := v.Interface().(goOrderedMap) - - // Determine whether we need to recurse into the field, or whether it is - // a leaf or leaf-list, which are not recursed into when traversing the - // data tree. - switch { - case isOrderedMap: - // Handle the case of a keyed map, which is a YANG list. - if IsNilOrInvalidValue(v) { - return errs - } - nn := *ni - nn.Parent = ni - var err error - nn.FieldKeys, err = yreflect.OrderedMapKeys(orderedMap) - if err != nil { - errs = AppendErr(errs, err) - return errs - } - if err := yreflect.RangeOrderedMap(orderedMap, func(k, v reflect.Value) bool { - nn := nn - nn.FieldValue = v - nn.FieldKey = k - errs = AppendErrs(errs, forEachDataFieldInternal(&nn, in, out, iterFunction)) - return true - }); err != nil { - errs = AppendErr(errs, err) - } - case IsTypeStructPtr(t): - // A struct pointer in a GoStruct is a pointer to another container within - // the YANG, therefore we dereference the pointer and then recurse. If the - // pointer is nil, then we do not need to do this since the data tree branch - // is unset in the schema. - t = t.Elem() - v = v.Elem() - fallthrough - case IsTypeStruct(t): - // Handle non-pointer structs by recursing into each field of the struct. - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - nn := &NodeInfo{ - Parent: ni, - StructField: sf, - FieldValue: reflect.Zero(sf.Type), - } - - nn.FieldValue = v.Field(i) - ps, err := SchemaPaths(nn.StructField) - if err != nil { - return NewErrs(err) - } - // In the case that the field expands to >1 different data tree path, - // i.e., SchemaPaths above returns more than one path, then we recurse - // for each schema path. This ensures that the iterator - // function runs for all expansions of the data tree as well as the GoStruct - // fields. - for _, p := range ps { - nn.PathFromParent = p - if IsTypeSlice(sf.Type) || IsTypeMap(sf.Type) { - // Since lists can have path compression - where the path contains more - // than one element, ensure that the schema path we received is only two - // elements long. This protects against compression errors where there are - // trailing spaces (e.g., a path tag of config/bar/). - nn.PathFromParent = p[0:1] - } - errs = AppendErrs(errs, forEachDataFieldInternal(nn, in, out, iterFunction)) - } - } - case IsTypeSlice(t): - // Only iterate in the data tree if the slice is of structs, otherwise - // for leaf-lists we only run once. - if !IsTypeStructPtr(t.Elem()) && !IsTypeStruct(t.Elem()) { - return errs - } - - for i := 0; i < ni.FieldValue.Len(); i++ { - nn := *ni - nn.Parent = ni - // The name of the list is the same in each of the entries within the - // list therefore, we do not need to set the path to be different from - // the parent. - nn.PathFromParent = ni.PathFromParent - nn.FieldValue = ni.FieldValue.Index(i) - errs = AppendErrs(errs, forEachDataFieldInternal(&nn, in, out, iterFunction)) - } - case IsTypeMap(t): - // Handle the case of a keyed map, which is a YANG list. - if IsNilOrInvalidValue(v) { - return errs - } - for _, key := range ni.FieldValue.MapKeys() { - nn := *ni - nn.Parent = ni - nn.FieldValue = ni.FieldValue.MapIndex(key) - nn.FieldKey = key - nn.FieldKeys = ni.FieldValue.MapKeys() - errs = AppendErrs(errs, forEachDataFieldInternal(&nn, in, out, iterFunction)) - } + vf.errorCollector.Errors = AppendErrs(vf.errorCollector.Errors, err) + if action == DoNotIterateDescendants { + return nil } - return errs + return vf } // GetNodes returns the nodes in the data tree at the indicated path, relative diff --git a/util/reflect_test.go b/util/reflect_test.go index 9137bb71..d3b4cb8f 100644 --- a/util/reflect_test.go +++ b/util/reflect_test.go @@ -847,6 +847,13 @@ type PathErrorStruct struct { Field *string } +type BadBasicStructMissingPath struct { + Int32Field int32 + StringField string + Int32PtrField *int32 `path:"int32ptr"` + StringPtrField *string `path:"stringptr"` +} + type BasicStruct struct { Int32Field int32 `path:"int32"` StringField string `path:"string"` @@ -993,6 +1000,13 @@ var ( StringPtrField: toStringPtr("forty two ptr"), } + badBasicStruct1 = BadBasicStructMissingPath{ + Int32Field: int32(42), + StringField: "forty two", + Int32PtrField: toInt32Ptr(4242), + StringPtrField: toStringPtr("forty two ptr"), + } + basicStruct2 = BasicStruct{ Int32Field: int32(43), StringField: "forty three", @@ -1214,6 +1228,76 @@ func TestForEachField(t *testing.T) { } } +type testWalkVisitor struct { + out string +} + +var _ Visitor = &testWalkVisitor{} + +func (v *testWalkVisitor) Visit(node WalkNode) Visitor { + if node == nil { + return nil + } + v.out += node.NodeInfo().FieldValue.String() + "\n" + return v +} + +func TestWalk(t *testing.T) { + tests := []struct { + desc string + parentStruct any + options *WalkOptions + wantOut string + wantErr Errors + }{ + { + desc: "nil", + parentStruct: nil, + wantOut: ``, + }, + { + desc: "struct with schema", + options: DefaultWalkOptions().WithSchema(forEachContainerSchema.Dir["basic-struct"]), + parentStruct: &basicStruct1, + wantOut: "<*util.BasicStruct Value>\n\nforty two\n<*int32 Value>\n<*string Value>\n", + }, + { + desc: "struct mismatched schema not an error", + options: DefaultWalkOptions().WithSchema(forEachContainerSchema.Dir["basic-struct"].Dir["int32ptr"]), + parentStruct: &basicStruct1, + wantOut: "<*util.BasicStruct Value>\n", + }, + { + desc: "struct no schema", + parentStruct: &basicStruct1, + wantOut: "<*util.BasicStruct Value>\n\nforty two\n<*int32 Value>\n<*string Value>\n", + }, + { + desc: "fail struct type missing path", + options: DefaultWalkOptions().WithSchema(forEachContainerSchema.Dir["basic-struct"]), + parentStruct: &badBasicStruct1, + wantErr: []error{fmt.Errorf("field Int32Field did not specify a path")}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + node := WalkNodeFromGoStruct(tt.parentStruct) + v := &testWalkVisitor{} + errs := Walk(v, node, tt.options).(*DefaultWalkErrors).Errors + if diff := cmp.Diff(errs.String(), tt.wantErr.String()); diff != "" { + t.Errorf("error (-got, +want):\n%s", diff) + } + if errs != nil { + return + } + if diff := cmp.Diff(v.out, tt.wantOut); diff != "" { + t.Errorf("%s:\n%s", tt.desc, diff) + } + }) + } +} + func TestForEachDataField(t *testing.T) { tests := []struct { desc string diff --git a/util/walk.go b/util/walk.go new file mode 100644 index 00000000..76ae370d --- /dev/null +++ b/util/walk.go @@ -0,0 +1,392 @@ +package util + +import ( + "fmt" + "reflect" + + log "github.com/golang/glog" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/internal/yreflect" +) + +// WalkNode is an abstraction of NodeInfo during the util.Walk. +type WalkNode interface { + NodeInfo() *NodeInfo +} + +// WalkErrors is an abstraction of collecting Errors during the util.Walk. +type WalkErrors interface { + Collect(error) +} + +// Visitor has a visit method that is invoked for each node encountered by Walk. +type Visitor interface { + Visit(node WalkNode) (w Visitor) +} + +// Walk traverses the nodes with a customized visitor. +// +// It traverses a GoStruct in depth-first order: It starts by calling +// v.Visit(node); node must not be nil. If the visitor w returned by +// v.Visit(node) is not nil, Walk is invoked recursively with visitor +// w for each of the non-nil children of node, followed by a call of +// w.Visit(nil). +// +// By default, the traversal only visit existing GoStruct fields. +// If a customized schema is provided via WithSchema WalkOptions, +// then the traversal will visit the schema entry even if the GoStruct does not populate it. +// +// The Visitor should handle its own error reporting and early termination. +// Any error during the traversal that is not part of Visitor will be aggregated into the +// returned WalkErrors. +// If not overwritten, the returned WalkErrors is of DefaultWalkErrors type, which is an alias of Errors. +func Walk(v Visitor, node WalkNode, o *WalkOptions) WalkErrors { + if o == nil { + o = &WalkOptions{} + } + if o.WalkErrors == nil { + o.WalkErrors = &DefaultWalkErrors{} + } + if o.schema == nil { + walkDataFieldInternal(v, node, o) + return o.WalkErrors + } + node = WalkNodeFromNodeInfo(&NodeInfo{ + Schema: o.schema, + FieldValue: node.NodeInfo().FieldValue, + }) + walkFieldInternal(v, node, o) + return o.WalkErrors +} + +var _ WalkNode = (*walkNodeInfo)(nil) + +type walkNodeInfo struct { + ni *NodeInfo +} + +func (w *walkNodeInfo) NodeInfo() *NodeInfo { + return w.ni +} + +// WalkNodeFromGoStruct converts a GoStruct to WalkNode with empty schema. +func WalkNodeFromGoStruct(value any) WalkNode { + return WalkNodeFromNodeInfo(&NodeInfo{ + FieldValue: reflect.ValueOf(value), + }) +} + +// WalkNodeFromNodeInfo converts a NodeInfo to WalkNode. +func WalkNodeFromNodeInfo(ni *NodeInfo) WalkNode { + return &walkNodeInfo{ni: ni} +} + +// WalkOptions are configurations of the Walk function. +type WalkOptions struct { + WalkErrors + schema *yang.Entry +} + +// DefaultWalkOptions initialize a WalkOptions. +func DefaultWalkOptions() *WalkOptions { + return &WalkOptions{} +} + +// WithSchema traverses schema entries even when a node is not populated with data. +func (o *WalkOptions) WithSchema(schema *yang.Entry) *WalkOptions { + o.schema = schema + return o +} + +// WithWalkErrors customizes the WalkErrors returned by the Walk function. +// If unspecified, the DefaultWalkErrors is used. +func (o *WalkOptions) WithWalkErrors(we WalkErrors) *WalkOptions { + o.WalkErrors = we + return o +} + +// DefaultWalkErrors is the default WalkErrors used unless overwritten via WithWalkErrors. +type DefaultWalkErrors struct { + Errors +} + +var _ WalkErrors = (*DefaultWalkErrors)(nil) + +// Collect is used by Walk to aggregate errors during the traversal. +func (e *DefaultWalkErrors) Collect(err error) { + e.Errors = AppendErr(e.Errors, err) +} + +// walkFieldInternal traverses a GoStruct in depth-first order: It starts by calling +// v.Visit(node); node must not be nil. If the visitor w returned by +// v.Visit(node) is not nil, Walk is invoked recursively with visitor +// w for each of the non-nil children of node, followed by a call of +// w.Visit(nil). +// It behaves similar to ForEachField to determine the set of children of a node. +// Precondition: o.errorCollector must be initialized. +func walkFieldInternal(visitor Visitor, node WalkNode, o *WalkOptions) { + ni := node.NodeInfo() + // Ignore nil field. + if IsValueNil(ni) { + return + } + // If the field is an annotation, then we do not process it any further, including + // skipping running the iterFunction. + if IsYgotAnnotation(ni.StructField) { + return + } + // walk the node itself + childVisitor := visitor.Visit(node) + if childVisitor == nil { + return + } + defer childVisitor.Visit(nil) + + v := ni.FieldValue + t := v.Type() + + // walk children + orderedMap, isOrderedMap := v.Interface().(goOrderedMap) + + switch { + case isOrderedMap, IsTypeSlice(t), IsTypeMap(t): + schema := *(ni.Schema) + schema.ListAttr = nil + + var relPath []string + if !schema.IsLeafList() { + // Leaf-list elements share the parent schema with listattr unset. + relPath = []string{schema.Name} + } + + var elemType reflect.Type + switch { + case isOrderedMap: + var err error + elemType, err = yreflect.OrderedMapElementType(orderedMap) + if err != nil { + o.WalkErrors.Collect(err) + return + } + default: + elemType = t.Elem() + } + + nn := *ni + // The schema for each element is the list schema minus the list + // attrs. + nn.Schema = &schema + nn.Parent = ni + nn.PathFromParent = relPath + + visitListElement := func(k, v reflect.Value) { + nn := nn + nn.FieldValue = v + nn.FieldKey = k + walkFieldInternal(childVisitor, WalkNodeFromNodeInfo(&nn), o) + } + + switch { + case IsNilOrInvalidValue(v): + // Traverse the type tree only from this point. + visitListElement(reflect.Value{}, reflect.Zero(elemType)) + case IsTypeSlice(t): + for i := 0; i < ni.FieldValue.Len(); i++ { + visitListElement(reflect.Value{}, ni.FieldValue.Index(i)) + } + case isOrderedMap: + var err error + nn.FieldKeys, err = yreflect.OrderedMapKeys(orderedMap) + if err != nil { + o.WalkErrors.Collect(err) + return + } + if err := yreflect.RangeOrderedMap(orderedMap, func(k, v reflect.Value) bool { + visitListElement(k, v) + return true + }); err != nil { + o.WalkErrors.Collect(err) + } + case IsTypeMap(t): + nn.FieldKeys = ni.FieldValue.MapKeys() + for _, key := range ni.FieldValue.MapKeys() { + visitListElement(key, ni.FieldValue.MapIndex(key)) + } + } + + case IsTypeStructPtr(t): + t = t.Elem() + if !IsNilOrInvalidValue(v) { + v = v.Elem() + } + fallthrough + case IsTypeStruct(t): + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + + // Do not handle annotation fields, since they have no schema. + if IsYgotAnnotation(sf) { + continue + } + + nn := &NodeInfo{ + Parent: ni, + StructField: sf, + } + if !IsNilOrInvalidValue(v) { + nn.FieldValue = v.Field(i) + } else { + nn.FieldValue = reflect.Zero(sf.Type) + } + ps, err := SchemaPaths(nn.StructField) + if err != nil { + o.WalkErrors.Collect(err) + return + } + + for _, p := range ps { + nn.Schema = FirstChild(ni.Schema, p) + if nn.Schema == nil { + e := fmt.Errorf("forEachFieldInternal could not find child schema with path %v from schema name %s", p, ni.Schema.Name) + DbgPrint(e.Error()) + // TODO(wenovus) Consider making this into an error. + log.Errorln(e) + continue + } + nn.PathFromParent = p + walkFieldInternal(childVisitor, WalkNodeFromNodeInfo(nn), o) + } + } + } +} + +// walkDataFieldInternal traverses a GoStruct in depth-first order: It starts by calling +// v.Visit(node); node must not be nil. If the visitor w returned by +// v.Visit(node) is not nil, Walk is invoked recursively with visitor +// w for each of the non-nil children of node, followed by a call of +// w.Visit(nil). +// It behaves similar to ForEachDataField2 to determine the set of children of a node. +// Precondition: o.errorCollector must be initialized. +func walkDataFieldInternal(visitor Visitor, node WalkNode, o *WalkOptions) { + ni := node.NodeInfo() + if IsValueNil(ni) { + return + } + + if IsNilOrInvalidValue(ni.FieldValue) { + // Skip any fields that are nil within the data tree, since we + // do not need to iterate on them. + return + } + + // walk the node itself + childVisitor := visitor.Visit(node) + if childVisitor == nil { + return + } + defer childVisitor.Visit(nil) + + v := ni.FieldValue + t := v.Type() + + orderedMap, isOrderedMap := v.Interface().(goOrderedMap) + + // Determine whether we need to recurse into the field, or whether it is + // a leaf or leaf-list, which are not recursed into when traversing the + // data tree. + switch { + case isOrderedMap: + // Handle the case of a keyed map, which is a YANG list. + if IsNilOrInvalidValue(v) { + return + } + nn := *ni + nn.Parent = ni + var err error + nn.FieldKeys, err = yreflect.OrderedMapKeys(orderedMap) + if err != nil { + o.WalkErrors.Collect(err) + return + } + if err := yreflect.RangeOrderedMap(orderedMap, func(k, v reflect.Value) bool { + nn := nn + nn.FieldValue = v + nn.FieldKey = k + walkDataFieldInternal(childVisitor, WalkNodeFromNodeInfo(&nn), o) + return true + }); err != nil { + o.WalkErrors.Collect(err) + } + case IsTypeStructPtr(t): + // A struct pointer in a GoStruct is a pointer to another container within + // the YANG, therefore we dereference the pointer and then recurse. If the + // pointer is nil, then we do not need to do this since the data tree branch + // is unset in the schema. + t = t.Elem() + v = v.Elem() + fallthrough + case IsTypeStruct(t): + // Handle non-pointer structs by recursing into each field of the struct. + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + nn := &NodeInfo{ + Parent: ni, + StructField: sf, + FieldValue: reflect.Zero(sf.Type), + } + + nn.FieldValue = v.Field(i) + ps, err := SchemaPaths(nn.StructField) + if err != nil { + o.WalkErrors.Collect(err) + return + } + // In the case that the field expands to >1 different data tree path, + // i.e., SchemaPaths above returns more than one path, then we recurse + // for each schema path. This ensures that the iterator + // function runs for all expansions of the data tree as well as the GoStruct + // fields. + for _, p := range ps { + nn.PathFromParent = p + if IsTypeSlice(sf.Type) || IsTypeMap(sf.Type) { + // Since lists can have path compression - where the path contains more + // than one element, ensure that the schema path we received is only two + // elements long. This protects against compression errors where there are + // trailing spaces (e.g., a path tag of config/bar/). + nn.PathFromParent = p[0:1] + } + walkDataFieldInternal(childVisitor, WalkNodeFromNodeInfo(nn), o) + } + } + case IsTypeSlice(t): + // Only iterate in the data tree if the slice is of structs, otherwise + // for leaf-lists we only run once. + if !IsTypeStructPtr(t.Elem()) && !IsTypeStruct(t.Elem()) { + return + } + + for i := 0; i < ni.FieldValue.Len(); i++ { + nn := *ni + nn.Parent = ni + // The name of the list is the same in each of the entries within the + // list therefore, we do not need to set the path to be different from + // the parent. + nn.PathFromParent = ni.PathFromParent + nn.FieldValue = ni.FieldValue.Index(i) + walkDataFieldInternal(childVisitor, WalkNodeFromNodeInfo(&nn), o) + } + case IsTypeMap(t): + // Handle the case of a keyed map, which is a YANG list. + if IsNilOrInvalidValue(v) { + return + } + for _, key := range ni.FieldValue.MapKeys() { + nn := *ni + nn.Parent = ni + nn.FieldValue = ni.FieldValue.MapIndex(key) + nn.FieldKey = key + nn.FieldKeys = ni.FieldValue.MapKeys() + walkDataFieldInternal(childVisitor, WalkNodeFromNodeInfo(&nn), o) + } + } +}