diff --git a/Makefile b/Makefile index ed07159109..fa5ac80cd7 100644 --- a/Makefile +++ b/Makefile @@ -95,16 +95,8 @@ yamlGen: go-patch: go-deps cd $(BUILD_GOPATH)/src/github.com/openconfig/ygot/; git reset --hard HEAD; git checkout 724a6b18a9224343ef04fe49199dfb6020ce132a 2>/dev/null ; true; \ -cp $(TOPDIR)/ygot-modified-files/debug.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/../util/debug.go; \ -cp $(TOPDIR)/ygot-modified-files/node.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/node.go; \ -cp $(TOPDIR)/ygot-modified-files/container.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/container.go; \ -cp $(TOPDIR)/ygot-modified-files/list.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/list.go; \ -cp $(TOPDIR)/ygot-modified-files/leaf.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/leaf.go; \ -cp $(TOPDIR)/ygot-modified-files/util_schema.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/util_schema.go; \ -cp $(TOPDIR)/ygot-modified-files/schema.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/../util/schema.go; \ -cp $(TOPDIR)/ygot-modified-files/unmarshal.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/unmarshal.go; \ -cp $(TOPDIR)/ygot-modified-files/validate.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/validate.go; \ -cp $(TOPDIR)/ygot-modified-files/reflect.go $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ytypes/../util/reflect.go; \ +cd ../; cp $(TOPDIR)/ygot-modified-files/ygot.patch .; \ +patch -p1 < ygot.patch; rm -f ygot.patch; \ $(GO) install -v -gcflags "-N -l" $(BUILD_GOPATH)/src/github.com/openconfig/ygot/ygot install: diff --git a/src/translib/request_binder.go b/src/translib/request_binder.go index 8dc39f105a..aa29b375fc 100644 --- a/src/translib/request_binder.go +++ b/src/translib/request_binder.go @@ -1,7 +1,21 @@ -/* -Copyright 2019 Broadcom. All rights reserved. -The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. -*/ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// package translib diff --git a/src/translib/request_binder_test.go b/src/translib/request_binder_test.go index ab1c9f34a0..cd8940d0e6 100644 --- a/src/translib/request_binder_test.go +++ b/src/translib/request_binder_test.go @@ -1,3 +1,22 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + package translib import ( diff --git a/ygot-modified-files/container.go b/ygot-modified-files/container.go deleted file mode 100644 index c07fc1f5c1..0000000000 --- a/ygot-modified-files/container.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "fmt" - "reflect" - - "github.com/kylelemons/godebug/pretty" - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" - "github.com/openconfig/ygot/ygot" -) - -// Refer to: https://tools.ietf.org/html/rfc6020#section-7.5. - -// validateContainer validates each of the values in the map, keyed by the list -// Key value, against the given list schema. -func validateContainer(schema *yang.Entry, value ygot.GoStruct) util.Errors { - var errors []error - if util.IsValueNil(value) { - return nil - } - // Check that the schema itself is valid. - if err := validateContainerSchema(schema); err != nil { - return util.NewErrs(err) - } - - util.DbgPrint("validateContainer with value %v, type %T, schema name %s", util.ValueStrDebug(value), value, schema.Name) - - extraFields := make(map[string]interface{}) - - switch reflect.TypeOf(value).Kind() { - case reflect.Ptr: - // Field exists in a struct but is unset. - if reflect.ValueOf(value).IsNil() { - return nil - } - structElems := reflect.ValueOf(value).Elem() - structTypes := structElems.Type() - - for i := 0; i < structElems.NumField(); i++ { - fieldType := structElems.Type().Field(i) - fieldName := fieldType.Name - fieldValue := structElems.Field(i).Interface() - - // Skip annotation fields when validating the schema. - if util.IsYgotAnnotation(fieldType) { - continue - } - - cschema, err := childSchema(schema, structTypes.Field(i)) - switch { - case err != nil: - errors = util.AppendErr(errors, fmt.Errorf("%s: %v", fieldName, err)) - continue - case cschema != nil: - // Regular named child. - if errs := Validate(cschema, fieldValue); errs != nil { - errors = util.AppendErrs(errors, util.PrefixErrors(errs, cschema.Path())) - } - case !structElems.Field(i).IsNil(): - // Either an element in choice schema subtree, or bad field. - // If the former, it will be found in the choice check below. - extraFields[fieldName] = nil - } - } - - // Field names in the data tree belonging to Choice have the schema of - // the elements of that choice. Hence, choice schemas must be checked - // separately. - for _, choiceSchema := range schema.Dir { - if choiceSchema.IsChoice() { - selected, errs := validateChoice(choiceSchema, value) - for _, s := range selected { - delete(extraFields, s) - } - if errs != nil { - errors = util.AppendErrs(util.AppendErr(errors, fmt.Errorf("%s/", choiceSchema.Name)), errs) - } - } - } - - default: - errors = util.AppendErr(errors, fmt.Errorf("validateContainer expected struct type for %s (type %T), got %v", schema.Name, value, reflect.TypeOf(value).Kind())) - } - - if len(extraFields) > 0 { - errors = util.AppendErr(errors, fmt.Errorf("fields %v are not found in the container schema %s", stringMapSetToSlice(extraFields), schema.Name)) - } - - return util.UniqueErrors(errors) -} - -// unmarshalContainer unmarshals a JSON tree into a struct. -// schema is the schema of the schema node corresponding to the struct being -// unmarshaled into. -// parent is the parent struct, which must be a struct ptr. -// jsonTree is a JSON data tree which must be a map[string]interface{}. -// opts is the set of options that should be used when unmarshalling the JSON -// into the supplied parent. -func unmarshalContainer(schema *yang.Entry, parent interface{}, jsonTree interface{}, enc Encoding, opts ...UnmarshalOpt) error { - if util.IsValueNil(jsonTree) { - return nil - } - - // Check that the schema itself is valid. - if err := validateContainerSchema(schema); err != nil { - return err - } - - util.DbgPrint("unmarshalContainer jsonTree %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(jsonTree), jsonTree, parent, schema.Name) - - // Since this is a container, the JSON data tree is a map. - jt, ok := jsonTree.(map[string]interface{}) - if !ok { - return fmt.Errorf("unmarshalContainer for schema %s: jsonTree %v: got type %T inside container, expect map[string]interface{}", - schema.Name, util.ValueStr(jsonTree), jsonTree) - } - - pvp := reflect.ValueOf(parent) - if !util.IsValueStructPtr(pvp) { - return fmt.Errorf("unmarshalContainer got parent type %T, expect struct ptr", parent) - } - - return unmarshalStruct(schema, parent, jt, enc, opts...) -} - -// unmarshalStruct unmarshals a JSON tree into a struct. -// schema is the YANG schema of the node corresponding to the struct being -// unmarshalled into. -// parent is the parent struct, which must be a struct ptr. -// jsonTree is a JSON data tree which must be a map[string]interface{}. -func unmarshalStruct(schema *yang.Entry, parent interface{}, jsonTree map[string]interface{}, enc Encoding, opts ...UnmarshalOpt) error { - destv := reflect.ValueOf(parent).Elem() - var allSchemaPaths [][]string - // Range over the parent struct fields. For each field, check if the data - // is present in the JSON tree and if so unmarshal it into the field. - for i := 0; i < destv.NumField(); i++ { - f := destv.Field(i) - ft := destv.Type().Field(i) - - // Skip annotation fields since they do not have a schema. - // TODO(robjs): Implement unmarshalling annotations. - if util.IsYgotAnnotation(ft) { - continue - } - - cschema, err := childSchema(schema, ft) - if err != nil { - return err - } - if cschema == nil { - return fmt.Errorf("unmarshalContainer could not find schema for type %T, field name %s", parent, ft.Name) - } - jsonValue, err := getJSONTreeValForField(schema, cschema, ft, jsonTree) - if err != nil { - return err - } - // Store the data tree path of the current field. These will be used - // at the end to ensure that there are no excess elements in the JSON - // tree not covered by any data path. - sp, err := dataTreePaths(schema, cschema, ft) - if err != nil { - return err - } - - allSchemaPaths = append(allSchemaPaths, sp...) - if jsonValue == nil { - util.DbgPrint("field %s paths %v not present in tree", ft.Name, sp) - continue - } - - util.DbgPrint("populating field %s type %s with paths %v.", ft.Name, ft.Type, sp) - // Only create a new field if it is nil, otherwise update just the - // fields that are in the data tree being passed to unmarshal, and - // preserve all other existing values. - if util.IsNilOrInvalidValue(f) { - makeField(destv, ft) - } - - p := parent - switch { - case util.IsUnkeyedList(cschema): - // For unkeyed list, we must pass in the addr of the slice to be - // able to append to it. - p = f.Addr().Interface() - case cschema.IsContainer() || cschema.IsList(): - // For list and container, the new parent is the field we just - // created. For leaf and leaf-list, the parent is still the - // current container. - p = f.Interface() - } - if err := unmarshalGeneric(cschema, p, jsonValue, enc, opts...); err != nil { - return err - } - } - - // Only check for missing fields if the IgnoreExtraFields option isn't specified. - if !hasIgnoreExtraFields(opts) { - // Go over all JSON fields to make sure that each one is covered - // by a data path in the struct. - if err := checkDataTreeAgainstPaths(jsonTree, allSchemaPaths); err != nil { - return fmt.Errorf("parent container %s (type %T): %s", schema.Name, parent, err) - } - } - - if util.IsDebugLibraryEnabled() { - util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) - } - - return nil -} - -// validateContainerSchema validates the given container type schema. This is a -// sanity check validation rather than a comprehensive validation against the -// RFC. It is assumed that such a validation is done when the schema is parsed -// from source YANG. -func validateContainerSchema(schema *yang.Entry) error { - if schema == nil { - return fmt.Errorf("container schema is nil") - } - if !schema.IsContainer() { - return fmt.Errorf("container schema %s is not a container type", schema.Name) - } - - return nil -} diff --git a/ygot-modified-files/debug.go b/ygot-modified-files/debug.go deleted file mode 100644 index ffcb31eaee..0000000000 --- a/ygot-modified-files/debug.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package util - -import ( - "fmt" - "reflect" - "strings" - - "github.com/kylelemons/godebug/pretty" - "github.com/openconfig/goyang/pkg/yang" -) - -var ( - // debugLibrary controls the debugging output from the library data tree - // traversal. Since this setting causes global variables to be manipulated - // controlling the output of the library, it MUST NOT be used in a setting - // whereby thread-safety is required. - debugLibrary = false - // debugSchema controls the debugging output from the library from schema - // matching code. Generates lots of output, so this should be used - // selectively per test case. - debugSchema = false - // maxCharsPerLine is the maximum number of characters per line from - // DbgPrint and DbgSchema. Additional characters are truncated. - maxCharsPerLine = 1000 - // maxValueStrLen is the maximum number of characters output from ValueStr. - maxValueStrLen = 150 -) - -// DbgPrint prints v if the package global variable debugLibrary is set. -// v has the same format as Printf. A trailing newline is added to the output. -func DbgPrint(v ...interface{}) { - if !debugLibrary { - return - } - out := fmt.Sprintf(v[0].(string), v[1:]...) - if len(out) > maxCharsPerLine { - out = out[:maxCharsPerLine] - } - fmt.Println(globalIndent + out) -} - -func IsDebugLibraryEnabled () bool { - return debugLibrary -} - -func IsDebugSchemaEnabled () bool { - return debugSchema -} - -// DbgSchema prints v if the package global variable debugSchema is set. -// v has the same format as Printf. -func DbgSchema(v ...interface{}) { - if debugSchema { - fmt.Printf(v[0].(string), v[1:]...) - } -} - -// DbgErr DbgPrints err and returns it. -func DbgErr(err error) error { - DbgPrint("ERR: " + err.Error()) - return err -} - -// globalIndent is used to control Indent level. -var globalIndent = "" - -// Indent increases DbgPrint Indent level. -func Indent() { - if !debugLibrary { - return - } - globalIndent += ". " -} - -// Dedent decreases DbgPrint Indent level. -func Dedent() { - if !debugLibrary { - return - } - globalIndent = strings.TrimPrefix(globalIndent, ". ") -} - -// ResetIndent sets the indent level to zero. -func ResetIndent() { - if !debugLibrary { - return - } - globalIndent = "" -} - -// ValueStrDebug returns "" if the package global variable -// debugLibrary is not set. Otherwise, it is the same as ValueStr. -// Use this function instead of ValueStr for debugging purpose, e.g. when the -// output is passed to DbgPrint, because ValueStr calls can be the bottleneck -// for large input. -func ValueStrDebug(value interface{}) string { - if !debugLibrary { - return "" - } - return ValueStr(value) -} - -// ValueStr returns a string representation of value which may be a value, ptr, -// or struct type. -func ValueStr(value interface{}) string { - out := valueStrInternal(value) - if len(out) > maxValueStrLen { - out = out[:maxValueStrLen] + "..." - } - return out -} - -// ValueStrInternal is the internal implementation of ValueStr. -func valueStrInternal(value interface{}) string { - v := reflect.ValueOf(value) - kind := v.Kind() - switch kind { - case reflect.Ptr: - if v.IsNil() || !v.IsValid() { - return "nil" - } - return strings.Replace(ValueStr(v.Elem().Interface()), ")", " ptr)", -1) - case reflect.Slice: - var out string - for i := 0; i < v.Len(); i++ { - if i != 0 { - out += ", " - } - out += ValueStr(v.Index(i).Interface()) - } - return "[ " + out + " ]" - case reflect.Struct: - var out string - for i := 0; i < v.NumField(); i++ { - if i != 0 { - out += ", " - } - if !v.Field(i).CanInterface() { - continue - } - out += ValueStr(v.Field(i).Interface()) - } - return "{ " + out + " }" - } - out := fmt.Sprintf("%v (%v)", value, kind) - if len(out) > maxValueStrLen { - out = out[:maxValueStrLen] + "..." - } - return out -} - -// SchemaTypeStr returns a string representation of the type of element schema -// represents e.g. "container", "choice" etc. -func SchemaTypeStr(schema *yang.Entry) string { - switch { - case schema.IsChoice(): - return "choice" - case schema.IsContainer(): - return "container" - case schema.IsCase(): - return "case" - case schema.IsList(): - return "list" - case schema.IsLeaf(): - return "leaf" - case schema.IsLeafList(): - return "leaf-list" - } - return "other" -} - -// YangTypeToDebugString returns a debug string representation of a YangType. -func YangTypeToDebugString(yt *yang.YangType) string { - if !debugLibrary { - return "" - } - out := fmt.Sprintf("(TypeKind: %s", yang.TypeKindToName[yt.Kind]) - if len(yt.Pattern) != 0 { - out += fmt.Sprintf(", Pattern: %s", strings.Join(yt.Pattern, " or ")) - } - if len(yt.Range) != 0 { - out += fmt.Sprintf(", Range: %s", yt.Range.String()) - } - return out + ")" -} - -// SchemaTreeString returns the schema hierarchy tree as a string with node -// names and types only e.g. -// clock (container) -// timezone (choice) -// timezone-name (case) -// timezone-name (leaf) -// timezone-utc-offset (case) -// timezone-utc-offset (leaf) -func SchemaTreeString(schema *yang.Entry, prefix string) string { - out := prefix + schema.Name + " (" + SchemaTypeStr(schema) + ")" + "\n" - for _, ch := range schema.Dir { - out += SchemaTreeString(ch, prefix+" ") - } - return out -} - -// DataSchemaTreesString outputs a combined data/schema tree string where schema -// is displayed alongside the data tree e.g. -// [device (container)] -// RoutingPolicy [routing-policy (container)] -// DefinedSets [defined-sets (container)] -// PrefixSet [prefix-set (list)] -// prefix1 -// prefix1 -// {255.255.255.0/20 20..24} -// IpPrefix : "255.255.255.0/20" [ip-prefix (leaf)] -// MasklengthRange : "20..24" [masklength-range (leaf)] -// PrefixSetName : "prefix1" [prefix-set-name (leaf)] -func DataSchemaTreesString(schema *yang.Entry, dataTree interface{}) string { - printFieldsIterFunc := func(ni *NodeInfo, in, out interface{}) (errs Errors) { - outs := out.(*string) - prefix := "" - for i := 0; i < len(strings.Split(ni.Schema.Path(), "/")); i++ { - prefix += " " - } - - fStr := fmt.Sprintf("%s%s", prefix, ni.StructField.Name) - schemaStr := fmt.Sprintf("[%s (%s)]", ni.Schema.Name, SchemaTypeStr(ni.Schema)) - switch { - case IsValueScalar(ni.FieldValue): - *outs += fmt.Sprintf(" %s : %s %s\n", fStr, pretty.Sprint(ni.FieldValue.Interface()), schemaStr) - case !IsNilOrInvalidValue(ni.FieldKey): - *outs += fmt.Sprintf("%s%v\n", prefix, ni.FieldKey) - case !IsNilOrInvalidValue(ni.FieldValue): - *outs += fmt.Sprintf("%s %s\n", fStr, schemaStr) - } - return - } - var outStr string - errs := ForEachField(schema, dataTree, nil, &outStr, printFieldsIterFunc) - if errs != nil { - outStr = errs.String() - } - - return outStr -} diff --git a/ygot-modified-files/leaf.go b/ygot-modified-files/leaf.go deleted file mode 100644 index 7440e551e8..0000000000 --- a/ygot-modified-files/leaf.go +++ /dev/null @@ -1,938 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "bytes" - "encoding/base64" - "fmt" - "math/big" - "reflect" - "strconv" - "strings" - - log "github.com/golang/glog" - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" - "github.com/openconfig/ygot/ygot" - - gpb "github.com/openconfig/gnmi/proto/gnmi" -) - -// Refer to: https://tools.ietf.org/html/rfc6020#section-7.6. - -// validateLeaf validates the value of a leaf struct against the given schema. -// This value is expected to be a Go basic type corresponding to the leaf -// schema type. -func validateLeaf(inSchema *yang.Entry, value interface{}) util.Errors { - // TODO(mostrowski): "mandatory" not implemented. - if util.IsValueNil(value) { - return nil - } - - // Check that the schema itself is valid. - if err := validateLeafSchema(inSchema); err != nil { - return util.NewErrs(err) - } - - util.DbgPrint("validateLeaf with value %s (%T), schema name %s (%s)", util.ValueStrDebug(value), value, inSchema.Name, inSchema.Type.Kind) - - schema, err := resolveLeafRef(inSchema) - if err != nil { - return util.NewErrs(err) - } - - var rv interface{} - ykind := schema.Type.Kind - rkind := reflect.ValueOf(value).Kind() - switch rkind { - case reflect.Ptr: - rv = reflect.ValueOf(value).Elem().Interface() - case reflect.Slice: - if ykind != yang.Ybinary { - return util.NewErrs(fmt.Errorf("bad leaf type: expect []byte for binary value %v for schema %s, have type %v", value, schema.Name, ykind)) - } - case reflect.Int64: - if ykind != yang.Yenum && ykind != yang.Yidentityref { - return util.NewErrs(fmt.Errorf("bad leaf type: expect Int64 for enum type for schema %s, have type %v", schema.Name, ykind)) - } - case reflect.Bool: - if ykind != yang.Yempty { - return util.NewErrs(fmt.Errorf("bad leaf type: expect Bool for empty type for schema %s, have type %v", schema.Name, ykind)) - } - rv = value - default: - return util.NewErrs(fmt.Errorf("bad leaf value type %v, expect Ptr or Int64 for schema %s", rkind, schema.Name)) - } - - switch ykind { - case yang.Ybinary: - return util.NewErrs(validateBinary(schema, rv)) - case yang.Ybits: - return nil - // TODO(mostrowski): restore when representation is decided. - //return util.NewErrs(validateBitset(schema, rv)) - case yang.Ybool: - return util.NewErrs(validateBool(schema, rv)) - case yang.Yempty: - return util.NewErrs(validateEmpty(schema, rv)) - case yang.Ystring: - return util.NewErrs(validateString(schema, rv)) - case yang.Ydecimal64: - return util.NewErrs(validateDecimal(schema, rv)) - case yang.Yenum, yang.Yidentityref: - if rkind != reflect.Int64 && !isValueInterfacePtrToEnum(reflect.ValueOf(value)) { - return util.NewErrs(fmt.Errorf("bad leaf value type %v, expect Int64 for schema %s, type %v", rkind, schema.Name, ykind)) - } - return nil - case yang.Yunion: - return validateUnion(schema, value) - } - if isIntegerType(ykind) { - return util.NewErrs(validateInt(schema, rv)) - } - return util.NewErrs(fmt.Errorf("unknown leaf type %v for schema %s", ykind, schema.Name)) -} - -/* - validateUnion validates a union type and returns any validation errors. - Unions have two types of possible representation in the data tree, which - depends on the schema. The first case has alternatives with the same Go type, - but different YANG types (possibly with different constraints): - - Name: "address", - Kind: yang.Yleaf, - Dir: {}, - Type: { - Name: "ip-address", - Kind: yang.Yunion, - Type: [ - { - Name: "ipv4-address", - Kind: yang.Ystring, - Pattern: [...pattern...], - }, - { - Name: "ipv6-address", - Kind: yang.Ystring, - Pattern: [...pattern...], - Type: [], - }] - } - - In this case, the data tree will look like this: - - type System_Ntp_Server struct { - Address *string `path:"address"` - } - - The validation will check against all the schema nodes that match the YANG - type corresponding to the Go type and return an error if none match. - - In the second case, where multiple Go types are present, the data tree has an - additional struct layer. In this case, the struct field is compared against - all YANG schemas that match the Go type of the selected wrapping struct e.g. - - Name: "port", - Kind: yang.Yleafref, - Dir: {}, - Type: { - Name: "port", - Kind: yang.Yunion, - Type: [ - { - Name: "port-string", - Kind: yang.Ystring, - Pattern: [...pattern...], - }, - { - Name: "port-integer", - Kind: yang.Yuint16, - Type: [], - }] - } - - -- Corresponding structs data tree -- - - type System_Ntp_Server struct { - Port Port `path:"port"` - } - - type Port interface { - IsPort() - } - - type Port_String struct { - PortString *string - } - - func (Port_String a) IsPort() {} - - type Port_Integer struct { - PortInteger *uint16 - } - - func (Port_Integer a) IsPort() {} - - In this case, the appropriate schema is uniquely selected based on the struct - path. - - A union may be nested. e.g. (shown as YANG schema for brevity) - - leaf foo { - type union { - type derived_string_type1; - type union { - type derived_string_type2; - type derived_string_type3; - } - } - } - - The data tree will look like this: - - type SomeContainer struct { - Foo *string `path:"foo"` - } - - In this case, the value for Foo would be recursively evaluated against any - of the matching types in any contained unions. - validateUnion supports any combination of nested union types and multiple - choices with the same type that are not represented by a named wrapper struct. -*/ -func validateUnion(schema *yang.Entry, value interface{}) util.Errors { - if util.IsValueNil(value) { - return nil - } - - util.DbgPrint("validateUnion %s", schema.Name) - // Must be a ptr - either a struct ptr or Go value ptr like *string. - // Enum types are also represented as a struct for union where the field - // has the enum type. - if reflect.TypeOf(value).Kind() != reflect.Ptr { - return util.NewErrs(fmt.Errorf("wrong value type for union %s: got: %T, expect ptr", schema.Name, value)) - } - - v := reflect.ValueOf(value).Elem() - - // Unions of enum types are passed as ptr to interface to struct ptr. - // Normalize to a union struct. - if util.IsValueInterface(v) { - v = v.Elem() - if util.IsValuePtr(v) { - v = v.Elem() - } - } - - if v.Type().Kind() == reflect.Struct { - if v.NumField() != 1 { - return util.NewErrs(fmt.Errorf("union %s should only have one field, but has %d", schema.Name, v.NumField())) - } - return validateMatchingSchemas(schema, v.Field(0).Interface()) - } - - return validateMatchingSchemas(schema, value) -} - -// validateMatchingSchemas validates against all schemas within the Type slice -// that match the type of passed in value. It returns nil if value is -// successfully validated against any matching schema, or a list of errors found -// during validation against each matching schema otherwise. -func validateMatchingSchemas(schema *yang.Entry, value interface{}) util.Errors { - var errors []error - ss := findMatchingSchemasInUnion(schema.Type, value) - var kk []yang.TypeKind - for _, s := range ss { - kk = append(kk, s.Type.Kind) - } - util.DbgPrint("validateMatchingSchemas for value %v (%T) for schema %s with types %v", value, value, schema.Name, kk) - if len(ss) == 0 { - return util.NewErrs(fmt.Errorf("no types in schema %s match the type of value %v, which is %T", schema.Name, util.ValueStr(value), value)) - } - for _, s := range ss { - var errs []error - if reflect.ValueOf(value).Kind() == reflect.Ptr { - errs = validateLeaf(s, value) - } else { - // Unions with wrapping structs use non-ptr fields so here we need - // to take the address of value to pass to validateLeaf, which - // expects a ptr field. - errs = validateLeaf(s, &value) - } - if errs == nil { - return nil - } - errors = util.AppendErrs(errors, errs) - } - - return errors -} - -// findMatchingSchemasInUnion returns all schemas in the given union type, -// including those within nested unions, that match the Go type of value. -// value must not be nil. -func findMatchingSchemasInUnion(ytype *yang.YangType, value interface{}) []*yang.Entry { - var matches []*yang.Entry - - util.DbgPrint("findMatchingSchemasInUnion for type %T, kind %s", value, reflect.TypeOf(value).Kind()) - for _, t := range ytype.Type { - if t.Kind == yang.Yunion { - // Recursively check all union types within this union. - matches = append(matches, findMatchingSchemasInUnion(t, value)...) - continue - } - - ybt := yangBuiltinTypeToGoType(t.Kind) - if reflect.ValueOf(value).Kind() == reflect.Ptr { - ybt = ygot.ToPtr(yangBuiltinTypeToGoType(t.Kind)) - } - if ybt == nil { - log.Warningf("no matching Go type for type %v in union value %s", t.Kind, util.ValueStr(value)) - continue - } - if reflect.TypeOf(ybt).Kind() == reflect.TypeOf(value).Kind() { - matches = append(matches, yangTypeToLeafEntry(t)) - } - } - - return matches -} - -// stripPrefix removes the prefix from a YANG path element. For example, removing -// foo from "foo:bar". Such qualified paths are used in YANG modules where remote -// paths are referenced. -func stripPrefix(name string) (string, error) { - ps := strings.Split(name, ":") - switch len(ps) { - case 1: - return name, nil - case 2: - return ps[1], nil - } - return "", fmt.Errorf("path element did not form a valid name (name, prefix:name): %v", name) -} - -// removeXPATHPredicates removes predicates from an XPath string. e.g., -// removeXPATHPredicates(/foo/bar[name="foo"]/config/baz -> /foo/bar/config/baz. -func removeXPATHPredicates(s string) (string, error) { - var b bytes.Buffer - for i := 0; i < len(s); { - ss := s[i:] - si, ei := strings.Index(ss, "["), strings.Index(ss, "]") - switch { - case si == -1 && ei == -1: - // This substring didn't contain a [] pair, therefore write it - // to the buffer. - b.WriteString(ss) - // Move to the last character of the substring. - i += len(ss) - case si == -1 || ei == -1: - // This substring contained a mismatched pair of []s. - return "", fmt.Errorf("Mismatched brackets within substring %s of %s, [ pos: %d, ] pos: %d", ss, s, si, ei) - case si > ei: - // This substring contained a ] before a [. - return "", fmt.Errorf("Incorrect ordering of [] within substring %s of %s, [ pos: %d, ] pos: %d", ss, s, si, ei) - default: - // This substring contained a matched set of []s. - b.WriteString(ss[0:si]) - i += ei + 1 - } - } - - return b.String(), nil -} - -// findLeafRefSchema returns a schema Entry at the path pathStr relative to -// schema if it exists, or an error otherwise. -// pathStr has either: -// - the relative form "../a/b/../b/c", where ".." indicates the parent of the -// node, or -// - the absolute form "/a/b/c", which indicates the absolute path from the -// root of the schema tree. -func findLeafRefSchema(schema *yang.Entry, pathStr string) (*yang.Entry, error) { - if pathStr == "" { - return nil, fmt.Errorf("leafref schema %s has empty path", schema.Name) - } - - refSchema := schema - pathStr, err := removeXPATHPredicates(pathStr) - if err != nil { - return nil, err - } - path := strings.Split(pathStr, "/") - - // For absolute path, reset to root of the schema tree. - if pathStr[0] == '/' { - refSchema = schemaTreeRoot(schema) - path = path[1:] - } - - for i := 0; i < len(path); i++ { - pe, err := stripPrefix(path[i]) - if err != nil { - return nil, fmt.Errorf("leafref schema %s path %s: %v", schema.Name, pathStr, err) - } - - if pe == ".." { - if refSchema.Parent == nil { - return nil, fmt.Errorf("parent of %s is nil for leafref schema %s with path %s", refSchema.Name, schema.Name, pathStr) - } - refSchema = refSchema.Parent - continue - } - if refSchema.Dir[pe] == nil { - return nil, fmt.Errorf("schema node %s is nil for leafref schema %s with path %s", pe, schema.Name, pathStr) - } - refSchema = refSchema.Dir[pe] - } - - return refSchema, nil -} - -// validateLeafSchema validates the given leaf type schema. This is a sanity -// check validation rather than a comprehensive validation against the RFC. -// It is assumed that such a validation is done when the schema is parsed from -// source YANG. -func validateLeafSchema(schema *yang.Entry) error { - if schema == nil { - return fmt.Errorf("leaf schema is nil") - } - if schema.Type == nil { - return fmt.Errorf("leaf schema type is nil for schema %s", schema.Name) - } - if schema.Kind != yang.LeafEntry { - return fmt.Errorf("case schema has wrong type %v for schema %s", schema.Kind, schema.Name) - } - return nil -} - -// YANGEmpty is a derived type which is used to represent the YANG -// empty type. -type YANGEmpty bool - -// unmarshalLeaf unmarshals a scalar value (determined by json.Unmarshal) into -// the parent containing the leaf. -// schema points to the schema for the leaf type. -func unmarshalLeaf(inSchema *yang.Entry, parent interface{}, value interface{}, enc Encoding) error { - if util.IsValueNil(value) { - return nil - } - - var err error - if err := validateLeafSchema(inSchema); err != nil { - return err - } - - util.DbgPrint("unmarshalLeaf value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, inSchema.Name) - - fieldName, _, err := schemaToStructFieldName(inSchema, parent) - if err != nil { - return err - } - - schema, err := resolveLeafRef(inSchema) - if err != nil { - return err - } - - ykind := schema.Type.Kind - - if ykind == yang.Yunion { - return unmarshalUnion(schema, parent, fieldName, value, enc) - } - - if ykind == yang.Ybits { - // TODO(mostrowski) - return nil - } - - v, err := unmarshalScalar(parent, schema, fieldName, value, enc) - if err != nil { - return err - } - if ykind == yang.Ybinary { - // Binary is a slice field which is treated as a scalar. - return util.InsertIntoStruct(parent, fieldName, v) - } - - if ykind == yang.Yempty { - // Empty is a derived type of bool which is treated as a scalar. We - // insert it here to avoid strict type checking against the generated - // code. - return util.UpdateField(parent, fieldName, v) - } - - return util.UpdateField(parent, fieldName, v) -} - -// unmarshalUnion unmarshals a union schema type with the given value into -// parent. -/* -for example, with structs schema: - -type Bgp_Neighbor_RouteReflector struct { - RouteReflectorClient *bool `path:"config/route-reflector-client" module:"openconfig-bgp"` - RouteReflectorClusterId Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union `path:"config/route-reflector-cluster-id" module:"openconfig-bgp"` -} -type Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union interface { - Is_Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union() -} -type Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union_String struct { - String string -} -func (t *Bgp_Neighbor_RouteReflector) To_Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union(i interface{}) (Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union, error) { - -and input JSON: - -{"config/route-reflector-cluster-id": "forty-two"} - -the resulting Bgp_Neighbor_RouteReflector would have field -RouteReflectorClusterId set with the type Bgp_Neighbor_RouteReflector_RouteReflectorClusterId_Union_String, -with field String set to "forty-two". -*/ - -func unmarshalUnion(schema *yang.Entry, parent interface{}, fieldName string, value interface{}, enc Encoding) error { - util.DbgPrint("unmarshalUnion value %v, type %T, into parent type %T field name %s, schema name %s", util.ValueStrDebug(value), value, parent, fieldName, schema.Name) - parentV, parentT := reflect.ValueOf(parent), reflect.TypeOf(parent) - if !util.IsTypeStructPtr(parentT) { - return fmt.Errorf("%T is not a struct ptr in unmarshalUnion", parent) - } - - // Get the value and type of the field to set, which may have slice or - // interface types for leaf-list and union cases. - destUnionFieldV := parentV.Elem().FieldByName(fieldName) - if !destUnionFieldV.IsValid() { - return fmt.Errorf("%s is not a valid field name in %T", fieldName, parent) - } - dft, _ := parentT.Elem().FieldByName(fieldName) - destUnionFieldElemT := dft.Type - - // Possible enum types, as []reflect.Type - ets, err := schemaToEnumTypes(schema, parentT) - if err != nil { - return err - } - // Possible YANG scalar types, as []yang.TypeKind. This discards any - // yang.Type restrictions, since these are expected to be checked during - // verification after unmarshal. - sks, err := getUnionKindsNotEnums(schema) - if err != nil { - return err - } - - util.DbgPrint("possible union types are enums %v or scalars %v", ets, sks) - - // Special case. If all possible union types map to a single go type, the - // GoStruct field is that type rather than a union Interface type. - if !util.IsTypeInterface(destUnionFieldElemT) && !util.IsTypeSliceOfInterface(destUnionFieldElemT) { - // Is not an interface, we must have exactly one type in the union. - if len(sks) != 1 { - return fmt.Errorf("got %v types for union schema %s for type %T, expect just one type", sks, fieldName, parent) - } - yk := sks[0] - goValue, err := unmarshalScalar(parent, yangKindToLeafEntry(yk), fieldName, value, enc) - if err != nil { - return fmt.Errorf("could not unmarshal %v into type %s", value, yk) - } - - if !util.IsTypeSlice(destUnionFieldElemT) { - destUnionFieldV.Set(reflect.ValueOf(ygot.ToPtr(goValue))) - return nil - } - - // Handle the case whereby the single-type union is actually a leaf-list, - // such that the representation in the struct is a slice, rather than a - // scalar. - sl := reflect.MakeSlice(destUnionFieldElemT, 0, 0) - if !destUnionFieldV.IsNil() { - // Ensure that we handle the case where there is an existing slice. - sl = destUnionFieldV - } - destUnionFieldV.Set(reflect.Append(sl, reflect.ValueOf(goValue))) - return nil - - } - - // For each possible union type, try to unmarshal the value. If it can be - // unmarshaled, try to resolve the resulting type into a union struct type. - // Note that values can resolve into more than one struct type depending on - // the value and its range. In this case, no attempt is made to find the - // most restrictive type. - // Try to unmarshal to enum types first, since the case of union of string - // and enum could unmarshal into either. Only string values can be enum - // types. - var valueStr string - var ok bool - switch enc { - case GNMIEncoding: - var sv *gpb.TypedValue_StringVal - if sv, ok = value.(*gpb.TypedValue).GetValue().(*gpb.TypedValue_StringVal); ok { - valueStr = sv.StringVal - } - case JSONEncoding: - valueStr, ok = value.(string) - default: - return fmt.Errorf("unknown encoding %v", enc) - } - - if ok { - for _, et := range ets { - util.DbgPrint("try to unmarshal into enum type %s", et) - ev, err := castToEnumValue(et, valueStr) - if err != nil { - return err - } - if ev != nil { - return setFieldWithTypedValue(parentT, destUnionFieldV, destUnionFieldElemT, ev) - } - util.DbgPrint("could not unmarshal %v into enum type: %s", value, err) - } - } - - for _, sk := range sks { - util.DbgPrint("try to unmarshal into type %s", sk) - sch := yangKindToLeafEntry(sk) - gv, err := unmarshalScalar(parent, sch, fieldName, value, enc) - if err == nil { - return setFieldWithTypedValue(parentT, destUnionFieldV, destUnionFieldElemT, gv) - } - util.DbgPrint("could not unmarshal %v into type %s: %s", value, sk, err) - } - - return fmt.Errorf("could not find suitable union type to unmarshal value %v type %T into parent struct type %T field %s", value, value, parent, fieldName) -} - -// setFieldWithTypedValue sets the field destV that has type ft and the given -// parent type with v, which must be a compatible enum type. -func setFieldWithTypedValue(parentT reflect.Type, destV reflect.Value, destElemT reflect.Type, v interface{}) error { - util.DbgPrint("setFieldWithTypedValue value %v into type %s", util.ValueStrDebug(v), destElemT) - if destElemT.Kind() == reflect.Slice { - // leaf-list case - destElemT = destElemT.Elem() - } - mn := "To_" + destElemT.Name() - mapMethod := reflect.New(parentT).Elem().MethodByName(mn) - if !mapMethod.IsValid() { - return fmt.Errorf("%s does not have a %s function", destElemT.Name(), mn) - } - ec := mapMethod.Call([]reflect.Value{reflect.ValueOf(v)}) - if len(ec) != 2 { - return fmt.Errorf("%s %s function returns %d params", destElemT.Name(), mn, len(ec)) - } - ei := ec[0].Interface() - ee := ec[1].Interface() - if ee != nil { - return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", v, v, ee) - } - - util.DbgPrint("unmarshaling %v into type %s", v, reflect.TypeOf(ei)) - - eiv := reflect.ValueOf(ei) - if destV.Type().Kind() == reflect.Slice { - destV.Set(reflect.Append(destV, eiv)) - } else { - destV.Set(eiv) - } - - return nil -} - -// getUnionKindsNotEnums returns all the YANG kinds under the given schema node, -// dereferencing any refs. -func getUnionKindsNotEnums(schema *yang.Entry) ([]yang.TypeKind, error) { - var uks []yang.TypeKind - m := make(map[yang.TypeKind]interface{}) - uts, err := getUnionTypesNotEnums(schema, schema.Type) - if err != nil { - return nil, err - } - for _, yt := range uts { - m[yt.Kind] = nil - } - for k := range m { - uks = append(uks, k) - } - return uks, nil -} - -// getUnionTypesNotEnums returns all the non-enum YANG types under the given -// schema node, dereferencing any refs. -func getUnionTypesNotEnums(schema *yang.Entry, yt *yang.YangType) ([]*yang.YangType, error) { - var uts []*yang.YangType - switch yt.Kind { - case yang.Yenum, yang.Yidentityref: - // Enum types handled separately. - return nil, nil - case yang.Yleafref: - ns, err := findLeafRefSchema(schema, yt.Path) - if err != nil { - return nil, err - } - return getUnionTypesNotEnums(ns, ns.Type) - case yang.Yunion: - for _, t := range yt.Type { - nt, err := getUnionTypesNotEnums(schema, t) - if err != nil { - return nil, err - } - uts = append(uts, nt...) - if err != nil { - return nil, err - } - } - default: - uts = []*yang.YangType{yt} - } - - return uts, nil -} - -// schemaToEnumTypes returns the actual enum types (rather than the interface -// type) for a given schema, which must be for an enum type. t is the type of -// the containing parent struct. -func schemaToEnumTypes(schema *yang.Entry, t reflect.Type) ([]reflect.Type, error) { - enumTypesMethod := reflect.New(t).Elem().MethodByName("ΛEnumTypeMap") - if !enumTypesMethod.IsValid() { - return nil, fmt.Errorf("type %s does not have a ΛEnumTypesMap function", t) - } - - ec := enumTypesMethod.Call(nil) - if len(ec) == 0 { - return nil, fmt.Errorf("%s ΛEnumTypes function returns empty value", t) - } - ei := ec[0].Interface() - enumTypesMap, ok := ei.(map[string][]reflect.Type) - if !ok { - return nil, fmt.Errorf("%s ΛEnumTypes function returned wrong type %T, want map[string][]reflect.Type", t, ei) - } - - if util.IsDebugLibraryEnabled() { - util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) - } - - return enumTypesMap[absoluteSchemaDataPath(schema)], nil -} - -// unmarshalScalar unmarshals value, which is the Go type from json.Unmarshal, -// to the corresponding value used in gostructs. -// parent is the parent struct containing the field being unmarshaled. -// Required if the unmarshaled type is an enum. -// fieldName is the name of the field being unmarshaled. -// Required if the unmarshaled type is an enum. -func unmarshalScalar(parent interface{}, schema *yang.Entry, fieldName string, value interface{}, enc Encoding) (interface{}, error) { - if util.IsValueNil(value) { - return nil, nil - } - - if err := validateLeafSchema(schema); err != nil { - return nil, err - } - - util.DbgPrint("unmarshalScalar value %v, type %T, into parent type %T field %s", value, value, parent, fieldName) - - switch enc { - case JSONEncoding: - return sanitizeJSON(parent, schema, fieldName, value) - case GNMIEncoding: - tv, ok := value.(*gpb.TypedValue) - if !ok { - return nil, fmt.Errorf("got %T type, want gNMI TypedValue as value type", value) - } - return sanitizeGNMI(parent, schema, fieldName, tv) - } - - return nil, fmt.Errorf("unknown encoding mode; %v", enc) -} - -// sanitizeJSON decodes the JSON encoded value into the type of corresponding -// field in GoStruct. Parent is the parent struct containing the field being -// unmarshaled. schema is *yang.Entry corresponding to the field. fieldName -// is the name of the field being written in GoStruct. value is the JSON -// encoded value. -func sanitizeJSON(parent interface{}, schema *yang.Entry, fieldName string, value interface{}) (interface{}, error) { - ykind := schema.Type.Kind - - if ykind != yang.Yunion && reflect.ValueOf(value).Type() != yangToJSONType(ykind) { - return nil, fmt.Errorf("got %T type for field %s, expect %v", value, schema.Name, yangToJSONType(ykind).Kind()) - } - - switch ykind { - case yang.Ybinary: - v, err := base64.StdEncoding.DecodeString(value.(string)) - if err != nil { - return nil, fmt.Errorf("error in DecodeString for \n%v\n for schema %s: %v", value, schema.Name, err) - } - return []byte(v), nil - - case yang.Yempty: - // If an empty leaf is included in the JSON, then we expect it to have a value of [null]. If it does not - // this is an error. - v, ok := value.([]interface{}) - if !ok || len(v) != 1 || v[0] != nil { - return nil, fmt.Errorf("error parsing %v for schema %s: empty leaves must be [null]", value, schema.Name) - } - return true, nil - - case yang.Ybits: - // TODO(mostrowski) - return nil, nil - - case yang.Ybool: - return value.(bool), nil - - case yang.Ystring: - return value.(string), nil - - case yang.Ydecimal64: - floatV, err := strconv.ParseFloat(value.(string), 64) - if err != nil { - return nil, fmt.Errorf("error parsing %v for schema %s: %v", value, schema.Name, err) - } - - return floatV, nil - - case yang.Yenum, yang.Yidentityref: - return enumStringToValue(parent, fieldName, value.(string)) - - case yang.Yint64: - // TODO(b/64812268): value types are different for internal style JSON. - intV, err := strconv.ParseInt(value.(string), 10, 64) - if err != nil { - return nil, fmt.Errorf("error parsing %v for schema %s: %v", value, schema.Name, err) - } - return intV, nil - - case yang.Yuint64: - uintV, err := strconv.ParseUint(value.(string), 10, 64) - if err != nil { - return nil, fmt.Errorf("error parsing %v for schema %s: %v", value, schema.Name, err) - } - return uintV, nil - - case yang.Yint8, yang.Yint16, yang.Yint32: - pv, err := yangFloatIntToGoType(ykind, value.(float64)) - if err != nil { - return nil, fmt.Errorf("error parsing %v for schema %s: %v", value, schema.Name, err) - } - return pv, nil - - case yang.Yuint8, yang.Yuint16, yang.Yuint32: - pv, err := yangFloatIntToGoType(ykind, value.(float64)) - if err != nil { - return nil, fmt.Errorf("error parsing %v for schema %s: %v", value, schema.Name, err) - } - return pv, nil - - case yang.Yunion: - return value, nil - - } - - return nil, fmt.Errorf("unmarshalScalar: unsupported type %v in schema node %s", ykind, schema.Name) -} - -// sanitizeGNMI decodes the GNMI TypedValue encoded value into the type of -// corresponding field in GoStruct. Parent is the parent struct containing the -// field being unmarshaled. schema is *yang.Entry corresponding to the field. -// fieldName is the name of the field being written in GoStruct. value is the -// JSON encoded value. -func sanitizeGNMI(parent interface{}, schema *yang.Entry, fieldName string, tv *gpb.TypedValue) (interface{}, error) { - ykind := schema.Type.Kind - - if !gNMIToYANGTypeMatches(ykind, tv) { - return nil, fmt.Errorf("failed to unmarshal %v into %v", tv.GetValue(), yang.TypeKindToName[ykind]) - } - - switch ykind { - case yang.Ybool: - return tv.GetBoolVal(), nil - case yang.Ystring: - return tv.GetStringVal(), nil - case yang.Yenum, yang.Yidentityref: - return enumStringToValue(parent, fieldName, tv.GetStringVal()) - case yang.Yint8, yang.Yint16, yang.Yint32, yang.Yint64: - gt := reflect.TypeOf(yangBuiltinTypeToGoType(ykind)) - vs := fmt.Sprintf("%v", tv.GetIntVal()) - rv, err := StringToType(gt, vs) - if err != nil { - return nil, fmt.Errorf("StringToType(%q, %v) failed; %v", vs, gt, err) - } - return rv.Interface(), nil - case yang.Yuint8, yang.Yuint16, yang.Yuint32, yang.Yuint64: - gt := reflect.TypeOf(yangBuiltinTypeToGoType(ykind)) - vs := fmt.Sprintf("%v", tv.GetUintVal()) - rv, err := StringToType(gt, vs) - if err != nil { - return nil, fmt.Errorf("StringToType(%q, %v) failed; %v", vs, gt, err) - } - return rv.Interface(), nil - case yang.Ybinary: - return tv.GetBytesVal(), nil - case yang.Ydecimal64: - switch v := tv.GetValue().(type) { - case *gpb.TypedValue_DecimalVal: - prec := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(v.DecimalVal.Precision)), nil) - // Second return value indicates whether returned float64 value exactly - // represents the division. We don't want to fail unmarshalling as float64 - // is the best type in ygot that can represent a decimal64. So, second - // return value is just ignored. - fv, _ := new(big.Rat).SetFrac(big.NewInt(v.DecimalVal.Digits), prec).Float64() - return fv, nil - case *gpb.TypedValue_FloatVal: - return float64(v.FloatVal), nil - } - } - return nil, fmt.Errorf("%v type isn't expected for GNMIEncoding", yang.TypeKindToName[ykind]) -} - -// gNMIToYANGTypeMatches checks whether the provided yang.TypeKind can be set -// by using the provided gNMI TypedValue. gNMI TypedValue oneof fields can -// carry more than one sizes of the same type per gNMI specification: -// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#223-node-values -func gNMIToYANGTypeMatches(ykind yang.TypeKind, tv *gpb.TypedValue) bool { - var ok bool - switch ykind { - case yang.Ybool: - _, ok = tv.GetValue().(*gpb.TypedValue_BoolVal) - case yang.Ystring, yang.Yenum, yang.Yidentityref: - _, ok = tv.GetValue().(*gpb.TypedValue_StringVal) - case yang.Yint8, yang.Yint16, yang.Yint32, yang.Yint64: - _, ok = tv.GetValue().(*gpb.TypedValue_IntVal) - case yang.Yuint8, yang.Yuint16, yang.Yuint32, yang.Yuint64: - _, ok = tv.GetValue().(*gpb.TypedValue_UintVal) - case yang.Ybinary: - _, ok = tv.GetValue().(*gpb.TypedValue_BytesVal) - case yang.Ydecimal64: - _, ok = tv.GetValue().(*gpb.TypedValue_DecimalVal) - if !ok { - _, ok = tv.GetValue().(*gpb.TypedValue_FloatVal) - } - } - return ok -} - -// isValueInterfacePtrToEnum reports whether v is an interface ptr to enum type. -func isValueInterfacePtrToEnum(v reflect.Value) bool { - if v.Kind() != reflect.Ptr { - return false - } - v = v.Elem() - if v.Kind() != reflect.Interface { - return false - } - v = v.Elem() - - return v.Kind() == reflect.Int64 -} diff --git a/ygot-modified-files/list.go b/ygot-modified-files/list.go deleted file mode 100644 index 62d0460301..0000000000 --- a/ygot-modified-files/list.go +++ /dev/null @@ -1,643 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "fmt" - "reflect" - "strings" - - "github.com/kylelemons/godebug/pretty" - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" -) - -// Refer to: https://tools.ietf.org/html/rfc6020#section-7.8. - -// validateList validates each of the values in the map, keyed by the list Key -// value, against the given list schema. -func validateList(schema *yang.Entry, value interface{}) util.Errors { - var errors []error - if util.IsValueNil(value) { - return nil - } - - // Check that the schema itself is valid. - if err := validateListSchema(schema); err != nil { - return util.NewErrs(err) - } - - util.DbgPrint("validateList with value %v, type %T, schema name %s", value, value, schema.Name) - - kind := reflect.TypeOf(value).Kind() - if kind == reflect.Slice || kind == reflect.Map { - // Check list attributes: size constraints etc. - // Skip this check if not a list type - in this case value may be a list - // element which shares the list schema (excluding ListAttr). - errors = util.AppendErrs(errors, validateListAttr(schema, value)) - } - - switch kind { - case reflect.Slice: - // List without key is a slice in the data tree. - sv := reflect.ValueOf(value) - for i := 0; i < sv.Len(); i++ { - errors = util.AppendErrs(errors, validateStructElems(schema, sv.Index(i).Interface())) - } - case reflect.Map: - // List with key is a map in the data tree, with the key being the value - // of the key field(s) in the elements. - for _, key := range reflect.ValueOf(value).MapKeys() { - cv := reflect.ValueOf(value).MapIndex(key).Interface() - structElems := reflect.ValueOf(cv).Elem() - // Check that keys are present and have correct values. - errors = util.AppendErrs(errors, checkKeys(schema, structElems, key)) - - // Verify each elements's fields. - errors = util.AppendErrs(errors, validateStructElems(schema, cv)) - } - case reflect.Ptr: - // Validate was called on a list element rather than the whole list, or - // on a completely bogus struct. In either case, evaluate just the - // element against the list schema without considering list attributes. - errors = util.AppendErrs(errors, validateStructElems(schema, value)) - - default: - errors = util.AppendErr(errors, fmt.Errorf("validateList expected map/slice type for %s, got %T", schema.Name, value)) - } - - return errors -} - -// checkKeys checks that the map key value for the list equals the value of the -// key field(s) in the elements for the map value. -// entry is the schema for the list. -// structElems is the structure representing the element in the data tree. -// keyElems is the structure representing the map key in the data tree. -// For a list schema that has a struct key, it's expected that: -// 1. The schema contains leaves with the struct field names (checked before -// calling this function). -// 2. Each element in the list has key fields defined by the leaves in 1. -// 3. For each such key field, the field value in the element equals the -// value of the map key of the containing map in the data tree. -func checkKeys(schema *yang.Entry, structElems reflect.Value, keyValue reflect.Value) util.Errors { - keys := strings.Split(schema.Key, " ") - if len(keys) == 1 { - return checkBasicKeyValue(structElems, schema.Key, keyValue) - } - - return checkStructKeyValues(structElems, keyValue) -} - -// checkBasicKeyValue checks if keyValue, which is the value of the map key, -// is equal to the value of the key field with field name keyFieldName in the -// element struct. -func checkBasicKeyValue(structElems reflect.Value, keyFieldSchemaName string, keyValue reflect.Value) util.Errors { - // Find field name corresponding to keyFieldName in the schema. - keyFieldName, err := schemaNameToFieldName(structElems, keyFieldSchemaName) - if err != nil { - return util.NewErrs(err) - } - if util.IsValueNil(keyValue.Interface()) { - return nil - } - - if !structElems.FieldByName(keyFieldName).IsValid() { - return util.NewErrs(fmt.Errorf("missing key field %s in element %v", keyFieldName, structElems)) - } - var elementKeyValue interface{} - if structElems.FieldByName(keyFieldName).Kind() == reflect.Ptr && !structElems.FieldByName(keyFieldName).IsNil() { - elementKeyValue = structElems.FieldByName(keyFieldName).Elem().Interface() - - } else { - elementKeyValue = structElems.FieldByName(keyFieldName).Interface() - } - if elementKeyValue != keyValue.Interface() { - return util.NewErrs(fmt.Errorf("key field %s: element key %v != map key %v", keyFieldName, elementKeyValue, keyValue)) - } - - return nil -} - -// checkStructKeyValues checks that the provided key struct (which is the key -// value of the entry in the data tree map): -// - has all the fields defined in the schema key definition -// - has no fields not defined in the schema key definition -// - has values for each field equal to the corresponding field in the element. -func checkStructKeyValues(structElems reflect.Value, keyStruct reflect.Value) util.Errors { - var errors []error - if keyStruct.Type().Kind() != reflect.Struct { - return util.NewErrs(fmt.Errorf("key value %v is not struct type", keyStruct)) - } - for i := 0; i < keyStruct.NumField(); i++ { - keyName := keyStruct.Type().Field(i).Name - keyValue := keyStruct.Field(i).Interface() - if !structElems.FieldByName(keyName).IsValid() { - errors = util.AppendErr(errors, fmt.Errorf("missing key field %s in %v", keyName, keyStruct)) - continue - } - - elementStructKeyValue := structElems.FieldByName(keyName) - if structElems.FieldByName(keyName).Kind() == reflect.Ptr && !structElems.FieldByName(keyName).IsNil() { - elementStructKeyValue = elementStructKeyValue.Elem() - } - - if elementStructKeyValue.Interface() != keyValue { - errors = util.AppendErr(errors, fmt.Errorf("element key value %v for key field %s has different value from map key %v", - elementStructKeyValue, keyName, keyValue)) - } - } - - return errors -} - -// validateStructElems validates each of the struct fields against the schema. -// TODO(mostrowski): choice directly under list is not handled here. -// Also, there's code duplication with a very similar operation in container. -func validateStructElems(schema *yang.Entry, value interface{}) util.Errors { - var errors []error - structElems := reflect.ValueOf(value).Elem() - structTypes := structElems.Type() - - if structElems.Kind() != reflect.Struct { - return util.NewErrs(fmt.Errorf("expected a struct type for %s: got %s", schema.Name, util.ValueStr(value))) - } - // Verify each elements's fields. - for i := 0; i < structElems.NumField(); i++ { - ft := structElems.Type().Field(i) - - // If this is an annotation field, then skip it since it does not have - // a schema. - if util.IsYgotAnnotation(ft) { - continue - } - - fieldName := ft.Name - fieldValue := structElems.Field(i).Interface() - - cschema, err := childSchema(schema, structTypes.Field(i)) - if err != nil { - errors = util.AppendErr(errors, err) - continue - } - if cschema == nil { - errors = util.AppendErr(errors, fmt.Errorf("child schema not found for struct %s field %s", schema.Name, fieldName)) - } else { - errors = util.AppendErrs(errors, Validate(cschema, fieldValue)) - } - } - - return errors -} - -// validateListSchema validates the given list type schema. This is a sanity -// check validation rather than a comprehensive validation against the RFC. -// It is assumed that such a validation is done when the schema is parsed from -// source YANG. -func validateListSchema(schema *yang.Entry) error { - if schema == nil { - return fmt.Errorf("list schema is nil") - } - if !schema.IsList() { - return fmt.Errorf("schema %s is not list type", schema.Name) - } - if schema.IsList() && schema.Config.Value() { - if len(schema.Key) == 0 { - return fmt.Errorf("list %s with config set must have a key", schema.Name) - } - keys := strings.Split(schema.Key, " ") - keysMissing := make(map[string]bool) - for _, v := range keys { - keysMissing[v] = true - } - for _, v := range schema.Dir { - if _, ok := keysMissing[v.Name]; ok { - delete(keysMissing, v.Name) - } - } - if len(keysMissing) != 0 { - return fmt.Errorf("list %s has keys %v missing from required list of %v", schema.Name, keysMissing, keys) - } - } - - return nil -} - -// schemaNameToFieldName returns the name of the struct field that corresponds -// to the name in the schema Key field, given structElems which is the stuct -// containing the field. It returns error if no field is found for the supplied -// key field name. -func schemaNameToFieldName(structElems reflect.Value, schemaKeyFieldName string) (string, error) { - for i := 0; i < structElems.NumField(); i++ { - ps, err := pathToSchema(structElems.Type().Field(i)) - if err != nil { - return "", err - } - matches, err := nameMatchesPath(schemaKeyFieldName, ps) - if err != nil { - return "", err - } - if matches { - return structElems.Type().Field(i).Name, nil - } - } - - return "", fmt.Errorf("struct %v does not contain a field with tag %s", structElems, schemaKeyFieldName) -} - -// nameMatchesPath returns true if the supplied path matches the given field -// name in the schema. -// For MyStructFieldName, the path is expected to follow the pattern of either -// {"my-struct-field-name"} or {"my-struct-name", "my-struct-field-name"} -func nameMatchesPath(fieldName string, path []string) (bool, error) { - switch len(path) { - case 1: - return fieldName == path[0], nil - case 2: - return fieldName == path[1], nil - } - return false, fmt.Errorf("expected field %s path to have one or two elements, got %v", fieldName, path) -} - -// unmarshalList unmarshals a JSON array into a list parent, which must be a -// map or slice ptr. -// schema is the schema of the schema node corresponding to the struct being -// unmamshaled into -// jsonList is a JSON list -// opts... are a set of ytypes.UnmarshalOptionst that are used to control -// the behaviour of the unmarshal function. -func unmarshalList(schema *yang.Entry, parent interface{}, jsonList interface{}, enc Encoding, opts ...UnmarshalOpt) error { - if util.IsValueNil(jsonList) { - return nil - } - // Check that the schema itself is valid. - if err := validateListSchema(schema); err != nil { - return err - } - - util.DbgPrint("unmarshalList jsonList %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(jsonList), jsonList, parent, schema.Name) - - // Parent must be a map, slice ptr, or struct ptr. - t := reflect.TypeOf(parent) - - if util.IsTypeStructPtr(t) { - // May be trying to unmarshal a single list element rather than the - // whole list. - return unmarshalContainerWithListSchema(schema, parent, jsonList, opts...) - } - - // jsonList represents a JSON array, which is a Go slice. - jl, ok := jsonList.([]interface{}) - if !ok { - return fmt.Errorf("unmarshalList for schema %s: jsonList %v: got type %T, expect []interface{}", - schema.Name, util.ValueStr(jsonList), jsonList) - } - - if !(util.IsTypeMap(t) || util.IsTypeSlicePtr(t)) { - return fmt.Errorf("unmarshalList for %s got parent type %s, expect map, slice ptr or struct ptr", schema.Name, t.Kind()) - } - - listElementType := t.Elem() - if util.IsTypeSlicePtr(t) { - listElementType = t.Elem().Elem() - } - if !util.IsTypeStructPtr(listElementType) { - return fmt.Errorf("unmarshalList for %s parent type %T, has bad field type %v", listElementType, parent, listElementType) - } - - // Iterate over JSON list. Each JSON list element is a map with the field - // name as the key. The JSON values must be unmarshaled and inserted into - // the new struct list element. When all fields of the new element have been - // filled, the constructed object will be added to listFieldName field in - // the parent struct, which can be a map or a slice, for keyed/unkeyed list - // types respectively. - // For a keyed list, the value(s) of the key are derived from the key fields - // in the new list element. - for _, le := range jl { - var err error - jt := le.(map[string]interface{}) - newVal := reflect.New(listElementType.Elem()) - util.DbgPrint("creating a new list element val of type %v", newVal.Type()) - if err := unmarshalStruct(schema, newVal.Interface(), jt, enc, opts...); err != nil { - return err - } - - switch { - case util.IsTypeMap(t): - newKey, err := makeKeyForInsert(schema, parent, newVal) - if err != nil { - return err - } - err = util.InsertIntoMap(parent, newKey.Interface(), newVal.Interface()) - case util.IsTypeSlicePtr(t): - err = util.InsertIntoSlice(parent, newVal.Interface()) - default: - return fmt.Errorf("unexpected type %s inserting in unmarshalList for parent type %T", t, parent) - } - if err != nil { - return err - } - } - if util.IsDebugLibraryEnabled() { - util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) - } - - return nil -} - -// makeValForInsert is used to create a value with the type extracted from -// given map. The returned value is populated according to the supplied "keys" -// map, which is assumed to be the map[string]string keys field from a gNMI -// PathElem protobuf message. Output of this function can be passed to -// makeKeyForInsert to produce a key to use while inserting into map. The -// function returns an error if a key name is not a valid schema tag in the -// supplied schema. Also, function uses the last schema tag if there is more -// than one by assuming it is direct descendant. -// - schema: schema of the map. -// - parent: value of the map. -// - keys: dictionary received as part of Key field of gNMI PathElem. -func makeValForInsert(schema *yang.Entry, parent interface{}, keys map[string]string) (reflect.Value, error) { - rv, rt := reflect.ValueOf(parent), reflect.TypeOf(parent) - if !util.IsValueMap(rv) { - return reflect.ValueOf(nil), fmt.Errorf("%T is not a reflect.Map kind", parent) - } - // key is a non-pointer type - keyT := rt.Key() - // element is pointer type - elmT := rt.Elem() - - if !util.IsTypeStructPtr(elmT) { - return reflect.ValueOf(nil), fmt.Errorf("%v is not a pointer to a struct", elmT) - } - - // Create an instance of map value type. Element is dereferenced as it is a pointer. - val := reflect.New(elmT.Elem()) - // Helper to update the field corresponding to schema key. - setKey := func(schemaKey string, fieldVal string) error { - fn, err := schemaNameToFieldName(val.Elem(), schemaKey) - if err != nil { - return err - } - fv := val.Elem().FieldByName(fn) - ft := fv.Type() - if util.IsValuePtr(fv) { - ft = ft.Elem() - } - sf, ok := val.Elem().Type().FieldByName(fn) - if ok == false { - return fmt.Errorf("Field %s not present in the struct %s", fn, val.Elem()) - } - cschema, err := childSchema(schema, sf) - if err != nil { - return err - } - keyLeafKind := cschema.Type.Kind - if keyLeafKind == yang.Yleafref { - lrfschema, err := resolveLeafRef(cschema) - if err != nil { - return err - } - keyLeafKind = lrfschema.Type.Kind - } - - var nv reflect.Value - if keyLeafKind == yang.Yunion { - sks, err := getUnionKindsNotEnums(cschema) - if err != nil { - return err - } - for _, sk := range sks { - gv, err := StringToType(reflect.TypeOf(yangBuiltinTypeToGoType(sk)), fieldVal) - if err == nil { - mn := "To_" + ft.Name() - mapMethod := val.MethodByName(mn) - if !mapMethod.IsValid() { - return fmt.Errorf("%s does not have a %s function", val, mn) - } - ec := mapMethod.Call([]reflect.Value{gv}) - if len(ec) != 2 { - return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) - } - ei := ec[0].Interface() - ee := ec[1].Interface() - if ee != nil { - return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) - } - nv = reflect.ValueOf(ei) - break - } - } - - if nv.IsValid() == false { - ets, err := schemaToEnumTypes(cschema, elmT) - if err != nil { - return err - } - for _, et := range ets { - ev, err := castToEnumValue(et, fieldVal) - if err != nil { - return err - } - if ev != nil { - mn := "To_" + ft.Name() - mapMethod := val.MethodByName(mn) - if !mapMethod.IsValid() { - return fmt.Errorf("%s does not have a %s function", val, mn) - } - ec := mapMethod.Call([]reflect.Value{reflect.ValueOf(ev)}) - if len(ec) != 2 { - return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) - } - ei := ec[0].Interface() - ee := ec[1].Interface() - if ee != nil { - return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) - } - nv = reflect.ValueOf(ei) - break - } - fmt.Errorf("could not unmarshal %v into enum type: %s\n", fieldVal, err) - } - if nv.IsValid() == false { - return fmt.Errorf("could not create the value type for the field name %s with the value %s", fn, fieldVal) - } - } - } else { - nv, err = StringToType(ft, fieldVal) - if err != nil { - return err - } - } - return util.InsertIntoStruct(val.Interface(), fn, nv.Interface()) - } - - if util.IsTypeStruct(keyT) { - for i := 0; i < keyT.NumField(); i++ { - schKey, err := directDescendantSchema(keyT.Field(i)) - if err != nil { - return reflect.ValueOf(nil), err - } - schVal, ok := keys[schKey] - if !ok { - return reflect.ValueOf(nil), fmt.Errorf("missing %v key in %v", schKey, keys) - } - if err := setKey(schKey, schVal); err != nil { - return reflect.ValueOf(nil), err - } - } - return val, nil - } - v, ok := keys[schema.Key] - if !ok { - return reflect.ValueOf(nil), fmt.Errorf("missing %v key in %v", schema.Key, keys) - } - if err := setKey(schema.Key, v); err != nil { - return reflect.ValueOf(nil), err - } - return val, nil -} - -// makeKeyForInsert returns a key for inserting a struct newVal into the parent, -// which must be a map. -func makeKeyForInsert(schema *yang.Entry, parentMap interface{}, newVal reflect.Value) (reflect.Value, error) { - // Key is always a value type, never a ptr. - listKeyType := reflect.TypeOf(parentMap).Key() - newKey := reflect.New(listKeyType).Elem() - - if util.IsTypeStruct(listKeyType) { - // For struct key type, copy the key fields from the new list entry - // struct newVal into the key struct. - for i := 0; i < newKey.NumField(); i++ { - kfn := listKeyType.Field(i).Name - fv := newVal.Elem().FieldByName(kfn) - if !fv.IsValid() { - return reflect.ValueOf(nil), fmt.Errorf("element struct type %s does not contain key field %s", newVal.Elem().Type(), kfn) - } - nv := fv - if fv.Type().Kind() == reflect.Ptr { - // Ptr values are deferenced in key struct. - nv = nv.Elem() - } - if !nv.IsValid() { - return reflect.ValueOf(nil), fmt.Errorf("%v field doesn't have a valid value", kfn) - } - util.DbgPrint("Setting value of %v (%T) in key struct (%T)", nv.Interface(), nv.Interface(), newKey.Interface()) - newKeyField := newKey.FieldByName(kfn) - if !util.ValuesAreSameType(newKeyField, nv) { - return reflect.ValueOf(nil), fmt.Errorf("multi-key %v is not assignable to %v", nv.Type(), newKeyField.Type()) - } - newKeyField.Set(nv) - } - - return newKey, nil - } - - // Simple key type. Get the value from the new value struct, - // given the key string. - kv, err := getKeyValue(newVal.Elem(), schema.Key) - if err != nil { - return reflect.ValueOf(nil), err - } - util.DbgPrint("key value is %v.", kv) - - rvKey := reflect.ValueOf(kv) - - switch { - case util.IsTypeInterface(listKeyType) && util.IsValueTypeCompatible(listKeyType, newKey), util.ValuesAreSameType(newKey, rvKey): - default: - return reflect.ValueOf(nil), fmt.Errorf("single-key %v is not assignable to %v", rvKey.Type(), newKey.Type()) - } - newKey.Set(rvKey) - - return newKey, nil -} - -// insertAndGetKey creates key and value from the supplied keys map. It inserts -// key and value into the given root which must be a map with the supplied schema. -func insertAndGetKey(schema *yang.Entry, root interface{}, keys map[string]string) (interface{}, error) { - switch { - case schema.Key == "": - return nil, fmt.Errorf("unkeyed list can't be traversed, type %T, keys %v", root, keys) - case !util.IsValueMap(reflect.ValueOf(root)): - return nil, fmt.Errorf("root has type %T, want map", root) - } - - // TODO(yusufsn): When the key is a leafref, its target should be filled out. - if (len(keys) == 0) { - return nil, nil - } - mapVal, err := makeValForInsert(schema, root, keys) - if err != nil { - return nil, fmt.Errorf("failed to create map value for insert, root %T, keys %v: %v", root, keys, err) - } - mapKey, err := makeKeyForInsert(schema, root, mapVal) - if err != nil { - return nil, fmt.Errorf("failed to create map key for insert, root %T, keys %v: %v", root, keys, err) - } - err = util.InsertIntoMap(root, mapKey.Interface(), mapVal.Interface()) - if err != nil { - return nil, fmt.Errorf("failed to insert into map %T, keys %v: %v", root, keys, err) - } - - return mapKey.Interface(), nil -} - -// unmarshalContainerWithListSchema unmarshals a container data tree element -// using a list schema. This can happen because in OC schemas, list elements -// share the list schema so if a user attempts to unmarshal a list element vs. -// the whole list, the supplied schema is the same - the only difference is -// that in the latter case the target is a struct ptr. The supplied opts control -// the behaviour of the unmarshal function. -func unmarshalContainerWithListSchema(schema *yang.Entry, parent interface{}, value interface{}, opts ...UnmarshalOpt) error { - - if !util.IsTypeStructPtr(reflect.TypeOf(parent)) { - return fmt.Errorf("unmarshalContainerWithListSchema value %v, type %T, into parent type %T, schema name %s: parent must be a struct ptr", - value, value, parent, schema.Name) - } - // Create a container equivalent of the list, which is just the list - // with ListAttrs unset. - newSchema := *schema - newSchema.ListAttr = nil - return Unmarshal(&newSchema, parent, value, opts...) -} - -// getKeyValue returns the value from the structVal field whose last path -// element is key. The value is dereferenced if it is a ptr type. This function -// is used to create a key value for a keyed list. -// getKeyValue returns an error if no path in any of the fields of structVal has -// key as the last path element. -func getKeyValue(structVal reflect.Value, key string) (interface{}, error) { - for i := 0; i < structVal.NumField(); i++ { - f := structVal.Type().Field(i) - p, err := pathToSchema(f) - if err != nil { - return nil, err - } - if p[len(p)-1] == key { - fv := structVal.Field(i) - if fv.Type().Kind() == reflect.Ptr { - // The type for the key is the dereferenced type, if the type - // is a ptr. - if !fv.Elem().IsValid() { - return nil, fmt.Errorf("key field %s (%s) has nil value %v", key, fv.Type(), fv) - } - return fv.Elem().Interface(), nil - } - return fv.Interface(), nil - } - } - - return nil, fmt.Errorf("could not find key field %s in struct type %s", key, structVal.Type()) -} diff --git a/ygot-modified-files/node.go b/ygot-modified-files/node.go deleted file mode 100644 index 9e0af202e8..0000000000 --- a/ygot-modified-files/node.go +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "reflect" - - "github.com/golang/protobuf/proto" - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" - "github.com/openconfig/ygot/ygot" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - gpb "github.com/openconfig/gnmi/proto/gnmi" -) - -// Type retrieveNodeArgs contains the set of parameters that changes -// behavior of how retrieveNode works. -type retrieveNodeArgs struct { - // If delete is set to true, retrieve node deletes the node at the - // to supplied path. - delete bool - // If set to true, retrieveNode handles wildcards. e.g. key=* - handleWildcards bool - // If partialKeyMatch is set to true, retrieveNode tolerates missing - // key(s) in the given path. If no key is provided, all the nodes - // in the keyed list are treated as match. If some of the keys are - // provided, it returns the nodes corresponding to provided keys. - partialKeyMatch bool - // If modifyRoot is set to true, retrieveNode traverses the GoStruct - // and initialies nodes or inserting keys into maps if they do not exist. - modifyRoot bool - // If val is set to a non-nil value, leaf/leaflist node corresponding - // to the given path is updated with this value. - val interface{} -} - -// retrieveNode is an internal function that retrieves the node specified by -// the supplied path from the root which must have the schema supplied. -// retrieveNodeArgs change the way retrieveNode works. -// retrieveNode returns the list of matching nodes and their schemas, and error. -// Note that retrieveNode may mutate the tree even if it fails. -func retrieveNode(schema *yang.Entry, root interface{}, path, traversedPath *gpb.Path, args retrieveNodeArgs) ([]*TreeNode, error) { - switch { - case path == nil || len(path.Elem) == 0: - // When args.val is non-nil and the schema isn't nil, further check whether - // the node has a non-leaf schema. Setting a non-leaf schema isn't allowed. - if !util.IsValueNil(args.val) && schema != nil { - if !(schema.IsLeaf() || schema.IsLeafList()) { - return nil, status.Errorf(codes.Unknown, "path %v points to a node with non-leaf schema %v", traversedPath, schema) - } - } - return []*TreeNode{{ - Path: traversedPath, - Schema: schema, - Data: root, - }}, nil - case util.IsValueNil(root): - return nil, status.Errorf(codes.NotFound, "could not find children %v at path %v", path, traversedPath) - case schema == nil: - return nil, status.Errorf(codes.InvalidArgument, "schema is nil for type %T, path %v", root, path) - } - - switch { - // Check if the schema is a container, or the schema is a list and the parent provided is a member of that list. - case schema.IsContainer() || (schema.IsList() && util.IsTypeStructPtr(reflect.TypeOf(root))): - return retrieveNodeContainer(schema, root, path, traversedPath, args) - case schema.IsList(): - return retrieveNodeList(schema, root, path, traversedPath, args) - } - return nil, status.Errorf(codes.InvalidArgument, "can not use a parent that is not a container or list; schema %v root %T, path %v", schema, root, path) -} - -// retrieveNodeContainer is an internal function and operates on GoStruct. It retrieves -// the node by the supplied path from the root which must have the schema supplied. -// It recurses by calling retrieveNode. If modifyRoot is set to true, nodes along the path are initialized -// if they are nil. If val isn't nil, then it is set on the leaf or leaflist node. -// Note that root is modified even if function returns error status. -func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path, traversedPath *gpb.Path, args retrieveNodeArgs) ([]*TreeNode, error) { - rv := reflect.ValueOf(root) - if !util.IsTypeStructPtr(rv.Type()) { - return nil, status.Errorf(codes.InvalidArgument, "got %T, want struct ptr root in retrieveNodeContainer", root) - } - - // dereference reflect value as it points to a pointer. - v := rv.Elem() - - for i := 0; i < v.NumField(); i++ { - fv, ft := v.Field(i), v.Type().Field(i) - - cschema, err := childSchema(schema, ft) - if !util.IsYgotAnnotation(ft) { - switch { - case err != nil: - return nil, status.Errorf(codes.Unknown, "failed to get child schema for %T, field %s: %s", root, ft.Name, err) - case cschema == nil: - return nil, status.Errorf(codes.InvalidArgument, "could not find schema for type %T, field %s", root, ft.Name) - } - } - - schPaths, err := util.SchemaPaths(ft) - if err != nil { - return nil, status.Errorf(codes.Unknown, "failed to get schema paths for %T, field %s: %s", root, ft.Name, err) - } - - for _, p := range schPaths { - if !util.PathMatchesPrefix(path, p) { - continue - } - to := len(p) - if util.IsTypeMap(ft.Type) { - to-- - } - - if args.modifyRoot { - if err := util.InitializeStructField(root, ft.Name); err != nil { - return nil, status.Errorf(codes.Unknown, "failed to initialize struct field %s in %T, child schema %v, path %v", ft.Name, root, cschema, path) - } - } - - // If val in args is set to a non-nil value and the path is exhausted, we - // may be dealing with a leaf or leaf list node. We should set the val - // to the corresponding field in GoStruct. If the field is an annotation, - // the field doesn't have a schema, so it is handled seperately. - if !util.IsValueNil(args.val) && len(path.Elem) == to { - switch { - case util.IsYgotAnnotation(ft): - if err := util.UpdateField(root, ft.Name, args.val); err != nil { - return nil, status.Errorf(codes.Unknown, "failed to update struct field %s in %T with value %v, because of %v", ft.Name, root, args.val, err) - } - case cschema.IsLeaf() || cschema.IsLeafList(): - // With GNMIEncoding, unmarshalGeneric can only unmarshal leaf or leaf list - // nodes. Schema provided must be the schema of the leaf or leaf list node. - // root must be the reference of container leaf/leaf list belongs to. - if err := unmarshalGeneric(cschema, root, args.val, GNMIEncoding); err != nil { - return nil, status.Errorf(codes.Unknown, "failed to update struct field %s in %T with value %v; %v", ft.Name, root, args.val, err) - } - } - } - - np := &gpb.Path{} - if traversedPath != nil { - np = proto.Clone(traversedPath).(*gpb.Path) - } - for i := range p[0:to] { - np.Elem = append(np.Elem, path.GetElem()[i]) - } - return retrieveNode(cschema, fv.Interface(), util.TrimGNMIPathPrefix(path, p[0:to]), np, args) - } - } - - return nil, status.Errorf(codes.InvalidArgument, "no match found in %T, for path %v", root, path) -} - -// retrieveNodeList is an internal function and operates on a map. It returns the nodes matching -// with keys corresponding to the key supplied in path. -// Function returns list of nodes, list of schemas and error. -func retrieveNodeList(schema *yang.Entry, root interface{}, path, traversedPath *gpb.Path, args retrieveNodeArgs) ([]*TreeNode, error) { - rv := reflect.ValueOf(root) - switch { - case schema.Key == "": - return nil, status.Errorf(codes.InvalidArgument, "unkeyed list can't be traversed, type %T, path %v", root, path) - case len(path.GetElem()) == 0: - return nil, status.Errorf(codes.InvalidArgument, "path length is 0, schema %v, root %v", schema, root) - case !util.IsValueMap(rv): - return nil, status.Errorf(codes.InvalidArgument, "root has type %T, expect map", root) - } - - var matches []*TreeNode - - listKeyT := rv.Type().Key() - listElemT := rv.Type().Elem() - for _, k := range rv.MapKeys() { - listElemV := rv.MapIndex(k) - - // Handle lists with a single key. - if !util.IsValueStruct(k) { - // Handle the special case that we have zero keys specified only when we are handling lists - // with partial keys specified. - if len(path.GetElem()[0].GetKey()) == 0 && args.partialKeyMatch || (args.handleWildcards && path.GetElem()[0].GetKey()[schema.Key] == "*") { - keys, err := ygot.PathKeyFromStruct(listElemV) - if err != nil { - return nil, status.Errorf(codes.Unknown, "could not get path keys at %v: %v", traversedPath, err) - } - nodes, err := retrieveNode(schema, listElemV.Interface(), util.PopGNMIPath(path), appendElem(traversedPath, &gpb.PathElem{Name: path.GetElem()[0].Name, Key: keys}), args) - if err != nil { - return nil, err - } - - matches = append(matches, nodes...) - - continue - } - - // Otherwise, check for equality of the key. - pathKey, ok := path.GetElem()[0].GetKey()[schema.Key] - if !ok { - return nil, status.Errorf(codes.NotFound, "schema key %s is not found in gNMI path %v, root %T", schema.Key, path, root) - } - - kv, err := getKeyValue(listElemV.Elem(), schema.Key) - if err != nil { - return nil, status.Errorf(codes.Unknown, "failed to get key value for %v, path %v: %v", listElemV.Interface(), path, err) - } - - keyAsString, err := ygot.KeyValueAsString(kv) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "failed to convert %v to a string, path %v: %v", kv, path, err) - } - if keyAsString == pathKey { - return retrieveNode(schema, listElemV.Interface(), util.PopGNMIPath(path), appendElem(traversedPath, path.GetElem()[0]), args) - } - continue - } - - match := true - for i := 0; i < k.NumField(); i++ { - fieldName := listKeyT.Field(i).Name - fieldValue := k.Field(i) - if !fieldValue.IsValid() { - return nil, status.Errorf(codes.InvalidArgument, "invalid field %s in %T", fieldName, k) - } - - elemFieldT, ok := listElemT.Elem().FieldByName(fieldName) - if !ok { - return nil, status.Errorf(codes.NotFound, "element struct type %v does not contain key field %s", listElemT, fieldName) - } - - schemaKey, err := directDescendantSchema(elemFieldT) - if err != nil { - return nil, status.Errorf(codes.Unknown, "unable to get direct descendant schema name for %v: %v", schemaKey, err) - } - - pathKey, ok := path.GetElem()[0].GetKey()[schemaKey] - // If key isn't found in the path key, treat it as error if partialKeyMatch is set to false. - // Otherwise, continue searching other keys of key struct and count the value as match - // if other keys are also match. - switch { - case !ok && !args.partialKeyMatch: - return nil, status.Errorf(codes.NotFound, "gNMI path %v does not contain a map entry for schema %v, root %T", path, schemaKey, root) - case !ok && args.partialKeyMatch: - // If the key wasn't specified, then skip the comparison of value. - continue - } - keyAsString, err := ygot.KeyValueAsString(fieldValue.Interface()) - if err != nil { - return nil, status.Errorf(codes.Unknown, "failed to convert the field value to string, field %v: %v", fieldName, err) - } - if !(args.handleWildcards && pathKey == "*") && pathKey != keyAsString { - match = false - break - } - } - - if match { - keys, err := ygot.PathKeyFromStruct(listElemV) - if err != nil { - return nil, status.Errorf(codes.Unknown, "could not extract keys from %v: %v", traversedPath, err) - } - nodes, err := retrieveNode(schema, listElemV.Interface(), util.PopGNMIPath(path), appendElem(traversedPath, &gpb.PathElem{Name: path.GetElem()[0].Name, Key: keys}), args) - if err != nil { - return nil, err - } - - if nodes != nil { - matches = append(matches, nodes...) - } - } - } - - if len(matches) == 0 && args.modifyRoot { - key, err := insertAndGetKey(schema, root, path.GetElem()[0].GetKey()) - if err != nil { - return nil, err - } - - if (key == nil) { - return []*TreeNode{{Path: traversedPath,Schema: schema,Data: root,}}, nil - } - - nodes, err := retrieveNode(schema, rv.MapIndex(reflect.ValueOf(key)).Interface(), util.PopGNMIPath(path), appendElem(traversedPath, path.GetElem()[0]), args) - if err != nil { - return nil, err - } - matches = append(matches, nodes...) - } - - return matches, nil -} - -// GetOrCreateNode function retrieves the node specified by the supplied path from the root which must have the -// schema supplied. It strictly matches keys in the path, in other words doesn't treat partial match as match. -// However, if there is no match, a new entry in the map is created. GetOrCreateNode also initializes the nodes -// along the path if they are nil. -// Function returns the value and schema of the node as well as error. -// Note that this function may modify the supplied root even if the function fails. -func GetOrCreateNode(schema *yang.Entry, root interface{}, path *gpb.Path) (interface{}, *yang.Entry, error) { - nodes, err := retrieveNode(schema, root, path, nil, retrieveNodeArgs{modifyRoot: true}) - if err != nil { - return nil, nil, err - } - - // There must be a result as this function initializes nodes along the supplied path. - return nodes[0].Data, nodes[0].Schema, nil -} - -// TreeNode wraps an individual entry within a YANG data tree to return to a caller. -type TreeNode struct { - // Schema is the schema entry for the data tree node, specified as a goyang Entry struct. - Schema *yang.Entry - // Data is the data node found at the path. - Data interface{} - // Path is the path of the data node that is being returned. - Path *gpb.Path -} - -// GetNode retrieves the node specified by the supplied path from the specified root, whose schema must -// also be supplied. It takes a set of options which can be used to specify get behaviours, such as -// allowing partial match. If there are no matches for the path, an error is returned. -func GetNode(schema *yang.Entry, root interface{}, path *gpb.Path, opts ...GetNodeOpt) ([]*TreeNode, error) { - return retrieveNode(schema, root, path, nil, retrieveNodeArgs{ - // We never want to modify the input root, so we specify modifyRoot. - modifyRoot: false, - partialKeyMatch: hasPartialKeyMatch(opts), - handleWildcards: hasHandleWildcards(opts), - }) -} - -// GetNodeOpt defines an interface that can be used to supply arguments to functions using GetNode. -type GetNodeOpt interface { - // IsGetNodeOpt is a marker method that is used to identify an instance of GetNodeOpt. - IsGetNodeOpt() -} - -// GetPartialKeyMatch specifies that a match within GetNode should be allowed to partially match -// keys for list entries. -type GetPartialKeyMatch struct{} - -// IsGetNodeOpt implements the GetNodeOpt interface. -func (*GetPartialKeyMatch) IsGetNodeOpt() {} - -// hasPartialKeyMatch determines whether there is an instance of GetPartialKeyMatch within the supplied -// GetNodeOpt slice. It is used to determine whether partial key matches should be allowed in an operation -// involving a GetNode. -func hasPartialKeyMatch(opts []GetNodeOpt) bool { - for _, o := range opts { - if _, ok := o.(*GetPartialKeyMatch); ok { - return true - } - } - return false -} - -// GetHandleWildcards specifies that a match within GetNode should be allowed to use wildekarts. -type GetHandleWildcards struct{} - -// IsGetNodeOpt implements the GetNodeOpt interface. -func (*GetHandleWildcards) IsGetNodeOpt() {} - -// hasHandleWildcards determines whether there is an instance of GetHandleWildcards within the supplied -// GetNodeOpt slice. -func hasHandleWildcards(opts []GetNodeOpt) bool { - for _, o := range opts { - if _, ok := o.(*GetHandleWildcards); ok { - return true - } - } - return false -} - -// appendElem adds the element e to the path p and returns the resulting -// path. -func appendElem(p *gpb.Path, e *gpb.PathElem) *gpb.Path { - np := &gpb.Path{} - if p != nil { - np = proto.Clone(p).(*gpb.Path) - } - np.Elem = append(np.Elem, e) - return np -} - -// SetNode sets the value of the node specified by the supplied path from the specified root, -// 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 { - nodes, err := retrieveNode(schema, root, path, nil, retrieveNodeArgs{ - modifyRoot: hasInitMissingElements(opts), - val: val, - }) - - if err != nil { - return err - } - - if len(nodes) == 0 { - return status.Errorf(codes.NotFound, "unable to find any nodes for the given path %v", path) - } - - return nil -} - -// 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 -} diff --git a/ygot-modified-files/reflect.go b/ygot-modified-files/reflect.go deleted file mode 100644 index d10ce9b89e..0000000000 --- a/ygot-modified-files/reflect.go +++ /dev/null @@ -1,1061 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package util - -import ( - "errors" - "fmt" - "reflect" - - "github.com/kylelemons/godebug/pretty" - "github.com/openconfig/goyang/pkg/yang" - - log "github.com/golang/glog" - - gpb "github.com/openconfig/gnmi/proto/gnmi" -) - -// CompressedSchemaAnnotation stores the name of the annotation indicating -// whether a set of structs were built with -compress_path. It is appended -// to the yang.Entry struct of the root entity of the structs within the -// SchemaTree. -const CompressedSchemaAnnotation string = "isCompressedSchema" - -// IsTypeStruct reports whether t is a struct type. -func IsTypeStruct(t reflect.Type) bool { - return t.Kind() == reflect.Struct -} - -// IsTypeStructPtr reports whether v is a struct ptr type. -func IsTypeStructPtr(t reflect.Type) bool { - if t == reflect.TypeOf(nil) { - return false - } - return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct -} - -// IsTypeSlice reports whether v is a slice type. -func IsTypeSlice(t reflect.Type) bool { - return t.Kind() == reflect.Slice -} - -// IsTypeSlicePtr reports whether v is a slice ptr type. -func IsTypeSlicePtr(t reflect.Type) bool { - if t == reflect.TypeOf(nil) { - return false - } - return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice -} - -// IsTypeMap reports whether v is a map type. -func IsTypeMap(t reflect.Type) bool { - if t == reflect.TypeOf(nil) { - return false - } - return t.Kind() == reflect.Map -} - -// IsTypeInterface reports whether v is an interface. -func IsTypeInterface(t reflect.Type) bool { - if t == reflect.TypeOf(nil) { - return false - } - return t.Kind() == reflect.Interface -} - -// IsTypeSliceOfInterface reports whether v is a slice of interface. -func IsTypeSliceOfInterface(t reflect.Type) bool { - if t == reflect.TypeOf(nil) { - return false - } - return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface -} - -// IsNilOrInvalidValue reports whether v is nil or reflect.Zero. -func IsNilOrInvalidValue(v reflect.Value) bool { - return !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) || IsValueNil(v.Interface()) -} - -// IsValueNil returns true if either value is nil, or has dynamic type {ptr, -// map, slice} with value nil. -func IsValueNil(value interface{}) bool { - if value == nil { - return true - } - switch reflect.TypeOf(value).Kind() { - case reflect.Slice, reflect.Ptr, reflect.Map: - return reflect.ValueOf(value).IsNil() - } - return false -} - -// IsValueNilOrDefault returns true if either IsValueNil(value) or the default -// value for the type. -func IsValueNilOrDefault(value interface{}) bool { - if IsValueNil(value) { - return true - } - if !IsValueScalar(reflect.ValueOf(value)) { - // Default value is nil for non-scalar types. - return false - } - return value == reflect.New(reflect.TypeOf(value)).Elem().Interface() -} - -// IsValuePtr reports whether v is a ptr type. -func IsValuePtr(v reflect.Value) bool { - return v.Kind() == reflect.Ptr -} - -// IsValueInterface reports whether v is an interface type. -func IsValueInterface(v reflect.Value) bool { - return v.Kind() == reflect.Interface -} - -// IsValueStruct reports whether v is a struct type. -func IsValueStruct(v reflect.Value) bool { - return v.Kind() == reflect.Struct -} - -// IsValueStructPtr reports whether v is a struct ptr type. -func IsValueStructPtr(v reflect.Value) bool { - return v.Kind() == reflect.Ptr && IsValueStruct(v.Elem()) -} - -// IsValueMap reports whether v is a map type. -func IsValueMap(v reflect.Value) bool { - return v.Kind() == reflect.Map -} - -// IsValueSlice reports whether v is a slice type. -func IsValueSlice(v reflect.Value) bool { - return v.Kind() == reflect.Slice -} - -// IsValueScalar reports whether v is a scalar type. -func IsValueScalar(v reflect.Value) bool { - if IsNilOrInvalidValue(v) { - return false - } - if IsValuePtr(v) { - if v.IsNil() { - return false - } - v = v.Elem() - } - return !IsValueStruct(v) && !IsValueMap(v) && !IsValueSlice(v) -} - -// ValuesAreSameType returns true if v1 and v2 has the same reflect.Type, -// otherwise it returns false. -func ValuesAreSameType(v1 reflect.Value, v2 reflect.Value) bool { - return v1.Type() == v2.Type() -} - -// IsValueInterfaceToStructPtr reports whether v is an interface that contains a -// pointer to a struct. -func IsValueInterfaceToStructPtr(v reflect.Value) bool { - return IsValueInterface(v) && IsValueStructPtr(v.Elem()) -} - -// IsStructValueWithNFields returns true if the reflect.Value representing a -// struct v has n fields. -func IsStructValueWithNFields(v reflect.Value, n int) bool { - return IsValueStruct(v) && v.NumField() == n -} - -// InsertIntoSlice inserts value into parent which must be a slice ptr. -func InsertIntoSlice(parentSlice interface{}, value interface{}) error { - DbgPrint("InsertIntoSlice into parent type %T with value %v, type %T", parentSlice, ValueStrDebug(value), value) - - pv := reflect.ValueOf(parentSlice) - t := reflect.TypeOf(parentSlice) - v := reflect.ValueOf(value) - - if !IsTypeSlicePtr(t) { - return fmt.Errorf("InsertIntoSlice parent type is %s, must be slice ptr", t) - } - - pv.Elem().Set(reflect.Append(pv.Elem(), v)) - DbgPrint("new list: %v\n", pv.Elem().Interface()) - - return nil -} - -// InsertIntoMap inserts value with key into parent which must be a map. -func InsertIntoMap(parentMap interface{}, key interface{}, value interface{}) error { - if debugLibrary { - DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", - parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) - } - - v := reflect.ValueOf(parentMap) - t := reflect.TypeOf(parentMap) - kv := reflect.ValueOf(key) - vv := reflect.ValueOf(value) - - if t.Kind() != reflect.Map { - return fmt.Errorf("InsertIntoMap parent type is %s, must be map", t) - } - - v.SetMapIndex(kv, vv) - - return nil -} - -// UpdateField updates a field called fieldName (which must exist, but may be -// nil) in parentStruct, with value fieldValue. If the field is a slice, -// fieldValue is appended. -func UpdateField(parentStruct interface{}, fieldName string, fieldValue interface{}) error { - DbgPrint("UpdateField field %s of parent type %T with value %v", fieldName, parentStruct, ValueStrDebug(fieldValue)) - - if IsValueNil(parentStruct) { - return fmt.Errorf("parent is nil in UpdateField for field %s", fieldName) - } - - pt := reflect.TypeOf(parentStruct) - - if !IsTypeStructPtr(pt) { - return fmt.Errorf("parent type %T must be a struct ptr", parentStruct) - } - ft, ok := pt.Elem().FieldByName(fieldName) - if !ok { - return fmt.Errorf("parent type %T does not have a field name %s", parentStruct, fieldName) - } - - if ft.Type.Kind() == reflect.Slice { - return InsertIntoSliceStructField(parentStruct, fieldName, fieldValue) - } - - return InsertIntoStruct(parentStruct, fieldName, fieldValue) -} - -// InsertIntoStruct updates a field called fieldName (which must exist, but may -// be nil) in parentStruct, with value fieldValue. -// If the struct field type is a ptr and the value is non-ptr, the field is -// populated with the corresponding ptr type. -func InsertIntoStruct(parentStruct interface{}, fieldName string, fieldValue interface{}) error { - DbgPrint("InsertIntoStruct field %s of parent type %T with value %v", fieldName, parentStruct, ValueStrDebug(fieldValue)) - - v, t := reflect.ValueOf(fieldValue), reflect.TypeOf(fieldValue) - pv, pt := reflect.ValueOf(parentStruct), reflect.TypeOf(parentStruct) - - if !IsTypeStructPtr(pt) { - return fmt.Errorf("parent type %T must be a struct ptr", parentStruct) - } - ft, ok := pt.Elem().FieldByName(fieldName) - if !ok { - return fmt.Errorf("parent type %T does not have a field name %s", parentStruct, fieldName) - } - - // YANG empty fields are represented as a derived bool value defined in the - // generated code. Here we cast the value to the type in the generated code. - if ft.Type.Kind() == reflect.Bool && t.Kind() == reflect.Bool { - nv := reflect.New(ft.Type).Elem() - nv.SetBool(v.Bool()) - v = nv - } - - // YANG binary fields are represented as a derived []byte value defined in the - // generated code. Here we cast the value to the type in the generated code. - // This will also cast a []uint8 value since byte is an alias for uint8. - if ft.Type.Kind() == reflect.Slice && t.Kind() == reflect.Slice && ft.Type.Elem().Kind() == reflect.Uint8 && t.Elem().Kind() == reflect.Uint8 { - nv := reflect.New(ft.Type).Elem() - nv.SetBytes(v.Bytes()) - v = nv - } - - n := v - if n.IsValid() && (ft.Type.Kind() == reflect.Ptr && t.Kind() != reflect.Ptr) { - n = reflect.New(t) - n.Elem().Set(v) - } - - if !n.IsValid() { - if ft.Type.Kind() != reflect.Ptr { - return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, ft.Type, parentStruct) - } - n = reflect.Zero(ft.Type) - } - - if !isFieldTypeCompatible(ft, n) && !IsValueTypeCompatible(ft.Type, v) { - return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, ft.Type, parentStruct) - } - - pv.Elem().FieldByName(fieldName).Set(n) - - return nil -} - -// InsertIntoSliceStructField inserts fieldValue into a field of type slice in -// parentStruct called fieldName (which must exist, but may be nil). -func InsertIntoSliceStructField(parentStruct interface{}, fieldName string, fieldValue interface{}) error { - DbgPrint("InsertIntoSliceStructField field %s of parent type %T with value %v", fieldName, parentStruct, ValueStrDebug(fieldValue)) - - v, t := reflect.ValueOf(fieldValue), reflect.TypeOf(fieldValue) - pv, pt := reflect.ValueOf(parentStruct), reflect.TypeOf(parentStruct) - - if !IsTypeStructPtr(pt) { - return fmt.Errorf("parent type %T must be a struct ptr", parentStruct) - } - ft, ok := pt.Elem().FieldByName(fieldName) - if !ok { - return fmt.Errorf("parent type %T does not have a field name %s", parentStruct, fieldName) - } - if ft.Type.Kind() != reflect.Slice { - return fmt.Errorf("parent type %T, field name %s is type %s, must be a slice", parentStruct, fieldName, ft.Type) - } - et := ft.Type.Elem() - - n := v - if n.IsValid() && (et.Kind() == reflect.Ptr && t.Kind() != reflect.Ptr) { - n = reflect.New(t) - n.Elem().Set(v) - } - if !n.IsValid() { - n = reflect.Zero(et) - } - if !IsValueTypeCompatible(et, n) { - return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, et, parentStruct) - } - - nl := reflect.Append(pv.Elem().FieldByName(fieldName), n) - pv.Elem().FieldByName(fieldName).Set(nl) - - return nil -} - -// InsertIntoMapStructField inserts fieldValue into a field of type map in -// parentStruct called fieldName (which must exist, but may be nil), using the -// given key. If the key already exists in the map, the corresponding value is -// updated. -func InsertIntoMapStructField(parentStruct interface{}, fieldName string, key, fieldValue interface{}) error { - DbgPrint("InsertIntoMapStructField field %s of parent type %T with key %v, value %v", fieldName, parentStruct, key, ValueStrDebug(fieldValue)) - - v := reflect.ValueOf(parentStruct) - t := reflect.TypeOf(parentStruct) - if v.Kind() == reflect.Ptr { - t = reflect.TypeOf(v.Elem().Interface()) - } - ft, ok := t.FieldByName(fieldName) - if !ok { - return fmt.Errorf("field %s not found in parent type %T", fieldName, parentStruct) - } - - if ft.Type.Kind() != reflect.Map { - return fmt.Errorf("field %s to insert into must be a map, type is %v", fieldName, ft.Type.Kind()) - } - vv := v - if v.Kind() == reflect.Ptr { - vv = v.Elem() - } - fvn := reflect.TypeOf(vv.FieldByName(fieldName).Interface()).Elem() - if fvn.Kind() != reflect.ValueOf(fieldValue).Kind() && !(fieldValue == nil && fvn.Kind() == reflect.Ptr) { - return fmt.Errorf("cannot assign value %v (type %T) to field %s (type %v) in struct %s", - fieldValue, fieldValue, fieldName, fvn.Kind(), t.Name()) - } - - n := reflect.New(fvn) - if fieldValue != nil { - n.Elem().Set(reflect.ValueOf(fieldValue)) - } - fv := v.Elem().FieldByName(fieldName) - if fv.IsNil() { - fv.Set(reflect.MakeMap(fv.Type())) - } - fv.SetMapIndex(reflect.ValueOf(key), n.Elem()) - - return nil -} - -// InitializeStructField initializes the given field in the given struct. Only -// pointer fields and some of the composite types are initialized(Map). -// It initializes to zero value of the underlying type if the field is a pointer. -// If the field is a slice, no need to initialize as appending a new element -// will do the same thing. Note that if the field is initialized already, this -// function doesn't re-initialize it. -func InitializeStructField(parent interface{}, fieldName string) error { - if parent == nil { - return errors.New("parent is nil") - } - pV := reflect.ValueOf(parent) - if IsValuePtr(pV) { - pV = pV.Elem() - } - - if !IsValueStruct(pV) { - return fmt.Errorf("%T is not a struct kind", parent) - } - - fV := pV.FieldByName(fieldName) - if !fV.IsValid() { - return fmt.Errorf("invalid %T %v field value", parent, fieldName) - } - switch { - case IsValuePtr(fV) && fV.IsNil(): - fV.Set(reflect.New(fV.Type().Elem())) - case IsValueMap(fV) && fV.IsNil(): - fV.Set(reflect.MakeMap(fV.Type())) - } - - return nil -} - -// isFieldTypeCompatible reports whether f.Set(v) can be called successfully on -// a struct field f corresponding to ft. It is assumed that f is exported and -// addressable. -func isFieldTypeCompatible(ft reflect.StructField, v reflect.Value) bool { - if ft.Type.Kind() == reflect.Ptr { - if !v.IsValid() { - return true - } - return v.Type() == ft.Type - } - - if !v.IsValid() { - return false - } - - return v.Type() == ft.Type -} - -// IsValueTypeCompatible reports whether f.Set(v) can be called successfully on -// a struct field f with type t. It is assumed that f is exported and -// addressable. -func IsValueTypeCompatible(t reflect.Type, v reflect.Value) bool { - switch { - case !v.IsValid(): - return t.Kind() == reflect.Ptr - case t.Kind() != reflect.Interface: - return v.Type().Kind() == t.Kind() - default: - return v.Type().Implements(t) - } -} - -// DeepEqualDerefPtrs compares the values of a and b. If either value is a ptr, -// it is dereferenced prior to the comparison. -func DeepEqualDerefPtrs(a, b interface{}) bool { - aa := a - bb := b - if !IsValueNil(a) && reflect.TypeOf(a).Kind() == reflect.Ptr { - aa = reflect.ValueOf(a).Elem().Interface() - } - if !IsValueNil(b) && reflect.TypeOf(b).Kind() == reflect.Ptr { - bb = reflect.ValueOf(b).Elem().Interface() - } - return reflect.DeepEqual(aa, bb) -} - -// NodeInfo describes a node in a tree being traversed. It is passed to the -// iterator function supplied to a traversal driver function like ForEachField. -type NodeInfo struct { - // Schema is the schema for the node. - Schema *yang.Entry - // Path is the relative path from the parent to the current schema node. - PathFromParent []string - // Parent is a ptr to the containing node. - Parent *NodeInfo - // StructField is the StructField for the field being traversed. - StructField reflect.StructField - // FieldValue is the Value for the field being traversed. - FieldValue reflect.Value - // FieldKeys is the slice of keys in the map being traversed. nil if type - // being traversed is not a map. - FieldKeys []reflect.Value - // FieldKey is the key of the map element being traversed. ValueOf(nil) if - // type being traversed is not a map. - FieldKey reflect.Value - // Annotation is a field that can be populated by an iterFunction such that - // context can be carried with a node throughout the iteration. - Annotation []interface{} -} - -// PathQueryMemo caches nodes retrieved from (string) path queries. This memo -// may be useful if an algorithm may do multiple queries against the same path -// from the same node, any of which could be very expensive since the tree could -// be deep and wide. -type PathQueryMemo map[string]PathQueryResult - -// PathQueryResult stores a datanode query result. -type PathQueryResult struct { - Nodes []interface{} - Err error -} - -// PathQueryNodeMemo caches previous path queries done against a particular node. -// Parent pointer allows looking up the memos of its ancestor nodes. -type PathQueryNodeMemo struct { - Parent *PathQueryNodeMemo - Memo PathQueryMemo -} - -// GetRoot returns the PathQueryNodeMemo of the current node's tree's root. -func (node *PathQueryNodeMemo) GetRoot() *PathQueryNodeMemo { - for node.Parent != nil { - node = node.Parent - } - return node -} - -// FieldIteratorFunc is an iteration function for arbitrary field traversals. -// in, out are passed through from the caller to the iteration vistior 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 - -// ForEachField recursively iterates through the fields of value (which may be -// any Go type) and executes iterFunction on each field. Any nil fields -// (including value) are traversed in the schema tree only. This is done to -// support iterations that need to detect the absence of some data item e.g. -// leafref. Fields that are present in value that are explicitly noted not to -// have a corresponding schema (e.g., annotation/metadata fields added by ygen) -// are skipped during traversal. -// schema is the schema corresponding to value. -// in, out are passed to the iterator function and can be used to carry state -// and return results from the iterator. -// 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) { - 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() - - switch { - 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, - FieldValue: reflect.Zero(sf.Type), - } - if !IsNilOrInvalidValue(v) { - nn.FieldValue = v.Field(i) - } - ps, err := SchemaPaths(nn.StructField) - if err != nil { - return NewErrs(err) - } - - for _, p := range ps { - nn.Schema = ChildSchema(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 - // In the case of a map/slice, the path is of the form - // "container/element" in the compressed schema, so trim off - // any extra path elements in this case. - if IsTypeSlice(sf.Type) || IsTypeMap(sf.Type) { - nn.PathFromParent = p[0:1] - } - 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)) - } - } - } - - case IsTypeSlice(t): - // Leaf-list elements share the parent schema with listattr unset. - schema := *(ni.Schema) - schema.ListAttr = nil - var pp []string - if !schema.IsLeafList() { - pp = []string{schema.Name} - } - if IsNilOrInvalidValue(v) { - // Traverse the type tree only from this point. - nn := &NodeInfo{ - Parent: ni, - PathFromParent: pp, - Schema: &schema, - FieldValue: reflect.Zero(t.Elem()), - } - 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 { - for i := 0; i < ni.FieldValue.Len(); i++ { - nn := *ni - // The schema for each element is the list schema minus the list - // attrs. - nn.Schema = &schema - nn.Parent = ni - nn.PathFromParent = pp - nn.FieldValue = ni.FieldValue.Index(i) - 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)) - } - } - } - - case IsTypeMap(t): - schema := *(ni.Schema) - schema.ListAttr = nil - if IsNilOrInvalidValue(v) { - nn := &NodeInfo{ - Parent: ni, - PathFromParent: []string{schema.Name}, - Schema: &schema, - FieldValue: reflect.Zero(t.Elem()), - } - 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 { - for _, key := range ni.FieldValue.MapKeys() { - nn := *ni - nn.Schema = &schema - nn.Parent = ni - nn.PathFromParent = []string{schema.Name} - nn.FieldValue = ni.FieldValue.MapIndex(key) - nn.FieldKey = key - nn.FieldKeys = ni.FieldValue.MapKeys() - 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)) - } - } - } - } - - return errs -} - -// IsCompressedSchema determines whether the yang.Entry s provided is part of a -// generated set of structs that have schema compression enabled. It traverses -// to the schema root, and determines the presence of an annotation with the name -// CompressedSchemaAnnotation which is added by ygen. -func IsCompressedSchema(s *yang.Entry) bool { - var e *yang.Entry - for e = s; e.Parent != nil; e = e.Parent { - } - _, ok := e.Annotation[CompressedSchemaAnnotation] - return ok -} - -// ForEachDataField iterates the value supplied and calls the iterFunction for -// each data tree node found in the supplied value. No schema information is required -// to perform the iteration. 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. -func ForEachDataField(value, in, out interface{}, iterFunction FieldIteratorFunc) Errors { - if IsValueNil(value) { - return nil - } - - return forEachDataFieldInternal(&NodeInfo{FieldValue: reflect.ValueOf(value)}, in, out, iterFunction) -} - -func forEachDataFieldInternal(ni *NodeInfo, in, out interface{}, iterFunction FieldIteratorFunc) Errors { - if IsValueNil(ni) { - return nil - } - - if IsNilOrInvalidValue(ni.FieldValue) { - // Skip any fields that are nil within the data tree, since we - // do not need to iterate on them. - return nil - } - - var errs Errors - // Run the iterator function for this field. - errs = AppendErrs(errs, iterFunction(ni, in, out)) - - v := ni.FieldValue - t := v.Type() - - // 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 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)) - } - } - return errs -} - -// GetNodes returns the nodes in the data tree at the indicated path, relative -// to the supplied root and their corresponding schemas at the same slice index. -// schema is the schema for root. -// If the key for a list node is missing, all values in the list are returned. -// If the key is partial, all nodes matching the values present in the key are -// returned. -// If the root is the tree root, the path may be absolute. -// GetNodes returns an error if the path is not found in the tree, or an element -// along the path is nil. -func GetNodes(schema *yang.Entry, root interface{}, path *gpb.Path) ([]interface{}, []*yang.Entry, error) { - return getNodesInternal(schema, root, path) -} - -// getNodesInternal is the internal implementation of GetNode. In addition to -// GetNode functionality, it can accept non GoStruct types e.g. map for a keyed -// list, or a leaf. -// See GetNodes for parameter and return value descriptions. -func getNodesInternal(schema *yang.Entry, root interface{}, path *gpb.Path) ([]interface{}, []*yang.Entry, error) { - if IsValueNil(root) { - ResetIndent() - return nil, nil, nil - } - if len(path.GetElem()) == 0 { - ResetIndent() - return []interface{}{root}, []*yang.Entry{schema}, nil - } - if schema == nil { - return nil, nil, fmt.Errorf("nil schema for data element type %T, remaining path %v", root, path) - } - // Strip off the absolute path prefix since the relative and absolute paths - // are assumed to be equal. - if path.GetElem()[0].GetName() == "" { - path.Elem = path.GetElem()[1:] - } - - Indent() - DbgPrint("GetNode next path %v, value %v", path.GetElem()[0], ValueStrDebug(root)) - - switch { - case schema.IsContainer() || (schema.IsList() && IsTypeStructPtr(reflect.TypeOf(root))): - // Either a container or list schema with struct data node (which could - // be an element of a list). - return getNodesContainer(schema, root, path) - case schema.IsList(): - // A list schema with the list parent container node as the root. - return getNodesList(schema, root, path) - } - - return nil, nil, fmt.Errorf("bad schema type for %s, struct type %T", schema.Name, root) -} - -// getNodesContainer traverses the container root, which must be a struct ptr -// type and matches each field against the first path element in path. If a -// field matches, it recurses into that field with the remaining path. -func getNodesContainer(schema *yang.Entry, root interface{}, path *gpb.Path) ([]interface{}, []*yang.Entry, error) { - DbgPrint("getNodesContainer: schema %s, next path %v, value %v", schema.Name, path.GetElem()[0], ValueStrDebug(root)) - - rv := reflect.ValueOf(root) - if !IsValueStructPtr(rv) { - return nil, nil, fmt.Errorf("getNodesContainer: root has type %T, expect struct ptr", root) - } - - v := rv.Elem() - - for i := 0; i < v.NumField(); i++ { - f := v.Field(i) - ft := v.Type().Field(i) - - // Skip annotation fields, since they do not have a schema. - if IsYgotAnnotation(ft) { - continue - } - - cschema, err := FieldSchema(schema, ft) - if err != nil { - return nil, nil, fmt.Errorf("error for schema for type %T, field name %s: %s", root, ft.Name, err) - } - if cschema == nil { - return nil, nil, fmt.Errorf("could not find schema for type %T, field name %s", root, ft.Name) - } - - ps, err := SchemaPaths(ft) - DbgPrint("check field name %s, paths %v", cschema.Name, ps) - if err != nil { - return nil, nil, err - } - for _, p := range ps { - if PathMatchesPrefix(path, p) { - // don't trim whole prefix for keyed list since name and key - // are a in the same element. - to := len(p) - if IsTypeMap(ft.Type) { - to-- - } - return getNodesInternal(cschema, f.Interface(), TrimGNMIPathPrefix(path, p[0:to])) - } - } - } - - return nil, nil, DbgErr(fmt.Errorf("could not find path in tree beyond schema node %s, (type %T), remaining path %v", schema.Name, root, path)) -} - -// getNodesList traverses the list root, which must be a map of struct -// type and matches each map key against the keys specified in the first -// PathElem of the Path. If the key matches, it recurses into that field with -// the remaining path. If empty key is specified, all list elements match. -func getNodesList(schema *yang.Entry, root interface{}, path *gpb.Path) ([]interface{}, []*yang.Entry, error) { - DbgPrint("getNodesList: schema %s, next path %v, value %v", schema.Name, path.GetElem()[0], ValueStrDebug(root)) - - rv := reflect.ValueOf(root) - if schema.Key == "" { - return nil, nil, fmt.Errorf("getNodesList: path %v cannot traverse unkeyed list type %T", path, root) - } - if !IsValueMap(rv) { - // Only keyed lists can be traversed with a path. - return nil, nil, fmt.Errorf("getNodesList: root has type %T, expect map", root) - } - emptyKey := false - if len(path.GetElem()[0].GetKey()) == 0 { - DbgPrint("path %v at %T points to list with empty wildcard key", path, root) - emptyKey = true - } - - listElementType := rv.Type().Elem().Elem() - listKeyType := rv.Type().Key() - - var matchNodes []interface{} - var matchSchemas []*yang.Entry - - // Iterate through all the map keys to see if any match the path. - for _, k := range rv.MapKeys() { - ev := rv.MapIndex(k) - DbgPrint("checking key %v, value %v", k.Interface(), ValueStrDebug(ev.Interface())) - match := true - if !emptyKey { // empty key matches everything. - if !IsValueStruct(k) { - // Compare just the single value of the key represented as a string. - pathKey, ok := path.GetElem()[0].GetKey()[schema.Key] - if !ok { - return nil, nil, fmt.Errorf("gnmi path %v does not contain a map entry for the schema key field name %s, parent type %T", - path, schema.Key, root) - } - kv, err := getKeyValue(ev.Elem(), schema.Key) - if err != nil { - return nil, nil, err - } - match = (fmt.Sprint(kv) == pathKey) - DbgPrint("check simple key value %s==%s ? %t", kv, pathKey, match) - } else { - // Must compare all the key fields. - for i := 0; i < k.NumField(); i++ { - kfn := listKeyType.Field(i).Name - fv := ev.Elem().FieldByName(kfn) - if !fv.IsValid() { - return nil, nil, fmt.Errorf("element struct type %s does not contain key field %s", k.Type(), kfn) - } - nv := fv - if fv.Type().Kind() == reflect.Ptr { - // Ptr values are deferenced in key struct. - nv = nv.Elem() - } - kf, ok := listElementType.FieldByName(kfn) - if !ok { - return nil, nil, fmt.Errorf("element struct type %s does not contain key field %s", k.Type(), kfn) - } - pathKey, ok := path.GetElem()[0].GetKey()[pathStructTagKey(kf)] - if !ok { - // If the key is not filled, it is assumed to match. - continue - } - if pathKey != fmt.Sprint(k.Field(i).Interface()) { - match = false - break - } - DbgPrint("key field value %s matches", pathKey) - } - } - } - - if match { - // Pass in the list schema, but the actual selected element - // rather than the whole list. - DbgPrint("key matches") - n, s, err := getNodesInternal(schema, ev.Interface(), PopGNMIPath(path)) - if err != nil { - return nil, nil, err - } - if n != nil { - matchNodes = append(matchNodes, n...) - matchSchemas = append(matchSchemas, s...) - } - } - } - - if len(matchNodes) == 0 { - return nil, nil, nil - } - return matchNodes, matchSchemas, nil -} - -// pathStructTagKey returns the string label of the struct field sf when it is -// used in a YANG list. This is the last path element of the struct path tag. -func pathStructTagKey(f reflect.StructField) string { - p, err := pathToSchema(f) - if err != nil { - log.Errorf("struct field %s does not have a path tag, bad schema?", f.Name) - return "" - } - return p[len(p)-1] -} - -// getKeyValue returns the value from the structVal field whose last path -// element is key. The value is dereferenced if it is a ptr type. This function -// is used to create a key value for a keyed list. -// getKeyValue returns an error if no path in any of the fields of structVal has -// key as the last path element. -func getKeyValue(structVal reflect.Value, key string) (interface{}, error) { - for i := 0; i < structVal.NumField(); i++ { - f := structVal.Type().Field(i) - p, err := pathToSchema(f) - if err != nil { - return nil, err - } - if p[len(p)-1] == key { - fv := structVal.Field(i) - if fv.Type().Kind() == reflect.Ptr { - // The type for the key is the dereferenced type, if the type - // is a ptr. - if !fv.Elem().IsValid() { - return nil, fmt.Errorf("key field %s (%s) has nil value %v", key, fv.Type(), fv) - } - return fv.Elem().Interface(), nil - } - return fv.Interface(), nil - } - } - - return nil, fmt.Errorf("could not find key field %s in struct type %s", key, structVal.Type()) -} diff --git a/ygot-modified-files/schema.go b/ygot-modified-files/schema.go deleted file mode 100644 index 3ad033ef71..0000000000 --- a/ygot-modified-files/schema.go +++ /dev/null @@ -1,349 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package util - -import ( - "fmt" - "reflect" - "strings" - - "github.com/openconfig/goyang/pkg/yang" -) - -// IsLeafRef reports whether schema is a leafref schema node type. -func IsLeafRef(schema *yang.Entry) bool { - if schema == nil || schema.Type == nil { - return false - } - return schema.Type.Kind == yang.Yleafref -} - -// IsChoiceOrCase returns true if the entry is either a 'case' or a 'choice' -// node within the schema. These are schema nodes only, and the code generation -// operates on data tree paths. -func IsChoiceOrCase(e *yang.Entry) bool { - if e == nil { - return false - } - return e.IsChoice() || e.IsCase() -} - -// IsFakeRoot reports whether the supplied yang.Entry represents the synthesised -// root entity in the generated code. -func IsFakeRoot(e *yang.Entry) bool { - if e == nil { - return false - } - if _, ok := e.Annotation["isFakeRoot"]; ok { - return true - } - return false -} - -// IsUnkeyedList reports whether e is an unkeyed list. -func IsUnkeyedList(e *yang.Entry) bool { - if e == nil { - return false - } - return e.IsList() && e.Key == "" -} - -// IsYgotAnnotation reports whether struct field s is an annotation field. -func IsYgotAnnotation(s reflect.StructField) bool { - _, ok := s.Tag.Lookup("ygotAnnotation") - return ok -} - -// SchemaPaths returns all the paths in the path tag. -func SchemaPaths(f reflect.StructField) ([][]string, error) { - var out [][]string - pathTag, ok := f.Tag.Lookup("path") - if !ok || pathTag == "" { - return nil, fmt.Errorf("field %s did not specify a path", f.Name) - } - - ps := strings.Split(pathTag, "|") - for _, p := range ps { - out = append(out, StripModulePrefixes(strings.Split(p, "/"))) - } - return out, nil -} - -// ChildSchema returns the first child schema that matches path from the given -// schema root. When comparing the path, only nodes that appear in the data tree -// are considered. It returns nil if no node matches the path. -func ChildSchema(schema *yang.Entry, path []string) *yang.Entry { - path = StripModulePrefixes(path) - entries := FindFirstNonChoiceOrCase(schema) - - for _, e := range entries { - m := MatchingNonChoiceCaseSchema(e, path) - if m != nil { - return m - } - } - - return nil -} - -// FindFirstNonChoiceOrCase recursively traverses the schema tree and returns a -// map with the set of the first nodes in every path that are neither case nor -// choice nodes. The keys in the map are the paths to the matching elements -// from the parent data struct, which always have length 1. -func FindFirstNonChoiceOrCase(e *yang.Entry) map[string]*yang.Entry { - m := make(map[string]*yang.Entry) - for _, ch := range e.Dir { - addToEntryMap(m, findFirstNonChoiceOrCaseInternal(ch)) - } - return m -} - -// findFirstNonChoiceOrCaseInternal is an internal part of -// FindFirstNonChoiceOrCase. -func findFirstNonChoiceOrCaseInternal(e *yang.Entry) map[string]*yang.Entry { - m := make(map[string]*yang.Entry) - switch { - case !IsChoiceOrCase(e): - m[e.Name] = e - case e.IsDir(): - for _, ch := range e.Dir { - addToEntryMap(m, findFirstNonChoiceOrCaseInternal(ch)) - } - } - return m -} - -// addToEntryMap merges from into to. -func addToEntryMap(to, from map[string]*yang.Entry) map[string]*yang.Entry { - for k, v := range from { - to[k] = v - } - return to -} - -// MatchingNonChoiceCaseSchema returns the child schema at the given path from -// schema if one is found, or nil otherwise. -func MatchingNonChoiceCaseSchema(schema *yang.Entry, path []string) *yang.Entry { - s := schema - if len(path) == 0 || schema.Name != path[0] { - return nil - } - path = path[1:] - for i := 0; i < len(path); i++ { - if s = s.Dir[path[i]]; s == nil { - return nil - } - } - return s -} - -// ResolveIfLeafRef returns a ptr to the schema pointed to by the leaf-ref path -// in schema if it's a leafref, or schema itself if it's not. -func ResolveIfLeafRef(schema *yang.Entry) (*yang.Entry, error) { - if schema == nil { - return nil, nil - } - // fakeroot or test cases may have this unset. They are definitely not - // leafrefs. - if schema.Type == nil { - return schema, nil - } - - orig := schema - s := schema - for ykind := s.Type.Kind; ykind == yang.Yleafref; { - ns, err := findLeafRefSchema(s, s.Type.Path) - if err != nil { - return schema, err - } - s = ns - ykind = s.Type.Kind - } - - if s != orig { - DbgPrint("follow schema leaf-ref from %s to %s, type %v", orig.Name, s.Name, s.Type.Kind) - } - return s, nil -} - -// findLeafRefSchema returns a schema Entry at the path pathStr relative to -// schema if it exists, or an error otherwise. -// pathStr has either: -// - the relative form "../a/b/../b/c", where ".." indicates the parent of the -// node, or -// - the absolute form "/a/b/c", which indicates the absolute path from the -// root of the schema tree. -func findLeafRefSchema(schema *yang.Entry, pathStr string) (*yang.Entry, error) { - if pathStr == "" { - return nil, fmt.Errorf("leafref schema %s has empty path", schema.Name) - } - - refSchema := schema - path := strings.Split(pathStr, "/") - - // For absolute path, reset to root of the schema tree. - if pathStr[0] == '/' { - refSchema = schemaTreeRoot(schema) - path = path[1:] - } - - for i := 0; i < len(path); i++ { - pe := StripModulePrefix(path[i]) - if pe == ".." { - if refSchema.Parent == nil { - return nil, fmt.Errorf("parent of %s is nil for leafref schema %s with path %s", refSchema.Name, schema.Name, pathStr) - } - refSchema = refSchema.Parent - continue - } - if refSchema.Dir[pe] == nil { - return nil, fmt.Errorf("schema node %s is nil for leafref schema %s with path %s", pe, schema.Name, pathStr) - } - refSchema = refSchema.Dir[pe] - } - - return refSchema, nil -} - -// FieldSchema returns the schema for the struct field f, if f contains a valid -// path tag and the schema path is found in the schema tree. It returns an error -// if the struct tag is invalid, or nil if tag is valid but the schema is not -// found in the tree at the specified path. -func FieldSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) { - DbgSchema("FieldSchema for parent schema %s, struct field %s\n", schema.Name, f.Name) - p, err := pathToSchema(f) - if err != nil { - return nil, err - } - p = StripModulePrefixes(p) - DbgSchema("pathToSchema yields %v\n", p) - s := schema - found := true - DbgSchema("traversing schema Dirs...") - for ; len(p) > 0; p = p[1:] { - if IsDebugSchemaEnabled() { - DbgSchema("/%s", p[0]) - } - var ok bool - s, ok = s.Dir[p[0]] - if !ok { - found = false - break - } - } - if found { - DbgSchema(" - found\n") - return s, nil - } - DbgSchema(" - not found\n") - - // Path is not null and was not found in the schema. It could be inside a - // choice/case schema element which is not represented in the path tags. - // e.g. choice1/case1/leaf1 could have abbreviated tag `path: "leaf1"`. - // In this case, try to match against any named elements within any choice/ - // case subtrees. These are guaranteed to be unique within the current - // level namespace so a path tag name match will be unique if one is found. - if len(p) != 1 { - // Nodes within choice/case have a path tag with only the last schema - // path element i.e. choice1/case1/leaf1 path in the schema will have - // struct tag `path:"leaf1"`. This implies that only paths with length - // 1 are eligible for this matching. - return nil, nil - } - entries := FindFirstNonChoiceOrCase(schema) - if IsDebugSchemaEnabled() { - DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) - } - for pe, entry := range entries { - if IsDebugSchemaEnabled() { - DbgSchema("%s ? ", pe) - } - if pe == p[0] { - DbgSchema(" - match\n") - return entry, nil - } - } - - DbgSchema(" - no matches\n") - return nil, nil -} - -// pathToSchema returns a path to the schema for the struct field f. -// Paths are embedded in the "path" struct tag and can be either simple: -// e.g. "path:a" -// or composite (if path compression is used) e.g. -// e.g. "path:config/a|a" -// In the latter case, this function returns {"config", "a"}, because only -// the longer path exists in the data tree and we want the schema for that -// node. -func pathToSchema(f reflect.StructField) ([]string, error) { - pathAnnotation, ok := f.Tag.Lookup("path") - if !ok { - return nil, fmt.Errorf("field %s did not specify a path", f.Name) - } - - paths := strings.Split(pathAnnotation, "|") - if len(paths) == 1 { - pathAnnotation = strings.TrimPrefix(pathAnnotation, "/") - return strings.Split(pathAnnotation, "/"), nil - } - for _, pv := range paths { - pv = strings.TrimPrefix(pv, "/") - pe := strings.Split(pv, "/") - if len(pe) > 1 { - return pe, nil - } - } - - return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathAnnotation) -} - -// schemaTreeRoot returns the root of the schema tree, given any node in that -// tree. It returns nil if schema is nil. -func schemaTreeRoot(schema *yang.Entry) *yang.Entry { - if schema == nil { - return nil - } - - root := schema - for root.Parent != nil { - root = root.Parent - } - - return root -} - -// StripModulePrefixesStr returns "in" with each element with the format "A:B" -// changed to "B". -func StripModulePrefixesStr(in string) string { - return strings.Join(StripModulePrefixes(strings.Split(in, "/")), "/") -} - -// StripModulePrefixes returns "in" with each element with the format "A:B" -// changed to "B". -func StripModulePrefixes(in []string) []string { - var out []string - for _, v := range in { - out = append(out, StripModulePrefix(v)) - } - return out -} - -// StripModulePrefix returns s with any prefix up to and including the last ':' -// character removed. -func StripModulePrefix(s string) string { - sv := strings.Split(s, ":") - return sv[len(sv)-1] -} diff --git a/ygot-modified-files/steps-patch-changes.txt b/ygot-modified-files/steps-patch-changes.txt deleted file mode 100644 index 380e611673..0000000000 --- a/ygot-modified-files/steps-patch-changes.txt +++ /dev/null @@ -1,8 +0,0 @@ - -Steps: To support the URI binding for which the URI comes from REST and GNMI server without keys and values, we need to make the changes as mentioned below to get the fix. -====== - -1. copy the files list.go, and node.go from this directory /godev/ygot-modified-files/ to this /godev/src/github.com/openconfig/ygot/ytypes/ - -2. compile and build the ygot package using the command "go install -v -gcflags "-N -l" github.com/openconfig/..." from the /godev/src/ directory - diff --git a/ygot-modified-files/unmarshal.go b/ygot-modified-files/unmarshal.go deleted file mode 100644 index 9cc876a8d8..0000000000 --- a/ygot-modified-files/unmarshal.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "errors" - "fmt" - - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" -) - -// UnmarshalOpt is an interface used for any option to be supplied -// to the Unmarshal function. Types implementing it can be used to -// control the behaviour of JSON unmarshalling. -type UnmarshalOpt interface { - IsUnmarshalOpt() -} - -// IgnoreExtraFields is an unmarshal option that controls the -// behaviour of the Unmarshal function when additional fields are -// found in the input JSON. By default, an error will be returned, -// by specifying the IgnoreExtraFields option to Unmarshal, extra -// fields will be discarded. -type IgnoreExtraFields struct{} - -// IsUnmarshalOpt marks IgnoreExtraFields as a valid UnmarshalOpt. -func (*IgnoreExtraFields) IsUnmarshalOpt() {} - -// Unmarshal recursively unmarshals JSON data tree in value into the given -// parent, using the given schema. Any values already in the parent that are -// not present in value are preserved. If provided schema is a leaf or leaf -// list, parent must be referencing the parent GoStruct. -func Unmarshal(schema *yang.Entry, parent interface{}, value interface{}, opts ...UnmarshalOpt) error { - return unmarshalGeneric(schema, parent, value, JSONEncoding, opts...) -} - -// Encoding specifies how the value provided to UnmarshalGeneric function is encoded. -type Encoding int - -const ( - // JSONEncoding indicates that provided value is JSON encoded. - JSONEncoding = iota - - // GNMIEncoding indicates that provided value is gNMI TypedValue. - GNMIEncoding -) - -// unmarshalGeneric unmarshals the provided value encoded with the given -// encoding type into the parent with the provided schema. When encoding mode -// is GNMIEncoding, the schema needs to be pointing to a leaf or leaf list -// schema. -func unmarshalGeneric(schema *yang.Entry, parent interface{}, value interface{}, enc Encoding, opts ...UnmarshalOpt) error { - util.Indent() - defer util.Dedent() - - // Nil value means the field is unset. - if util.IsValueNil(value) { - return nil - } - if schema == nil { - return fmt.Errorf("nil schema for parent type %T, value %v (%T)", parent, value, value) - } - - if (util.IsDebugLibraryEnabled()) { - util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) - } - - if enc == GNMIEncoding && !(schema.IsLeaf() || schema.IsLeafList()) { - return errors.New("unmarshalling a non leaf node isn't supported in GNMIEncoding mode") - } - - switch { - case schema.IsLeaf(): - return unmarshalLeaf(schema, parent, value, enc) - case schema.IsLeafList(): - return unmarshalLeafList(schema, parent, value, enc) - case schema.IsList(): - return unmarshalList(schema, parent, value, enc, opts...) - case schema.IsChoice(): - return fmt.Errorf("cannot pass choice schema %s to Unmarshal", schema.Name) - case schema.IsContainer(): - return unmarshalContainer(schema, parent, value, enc, opts...) - } - return fmt.Errorf("unknown schema type for type %T, value %v", value, value) -} - -// hasIgnoreExtraFields determines whether the supplied slice of UnmarshalOpts contains -// the IgnoreExtraFields option. -func hasIgnoreExtraFields(opts []UnmarshalOpt) bool { - for _, o := range opts { - if _, ok := o.(*IgnoreExtraFields); ok { - return true - } - } - return false -} diff --git a/ygot-modified-files/util_schema.go b/ygot-modified-files/util_schema.go deleted file mode 100644 index b12c79abb1..0000000000 --- a/ygot-modified-files/util_schema.go +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "fmt" - "reflect" - "strings" - - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" -) - -// validateLengthSchema validates whether the given schema has a valid length -// specification. -func validateLengthSchema(schema *yang.Entry) error { - if len(schema.Type.Length) == 0 { - return nil - } - for _, r := range schema.Type.Length { - // This is a limited sanity check. It's assumed that a full check is - // done in the goyang parser. - minLen, maxLen := r.Min, r.Max - if minLen.Kind != yang.MinNumber && minLen.Kind != yang.Positive { - return fmt.Errorf("length Min must be Positive or MinNumber: %v for schema %s", minLen, schema.Name) - } - if maxLen.Kind != yang.MaxNumber && maxLen.Kind != yang.Positive { - return fmt.Errorf("length Max must be Positive or MaxNumber: %v for schema %s", minLen, schema.Name) - } - if maxLen.Less(minLen) { - return fmt.Errorf("schema has bad length min[%v] > max[%v] for schema %s", minLen, maxLen, schema.Name) - } - } - - return nil -} - -// lengthOk reports whether the given value of length falls within the ranges -// allowed by yrs. Always returns true is yrs is empty. -func lengthOk(yrs yang.YangRange, val uint64) bool { - return isInRanges(yrs, yang.FromUint(val)) -} - -// isInRanges reports whether the given value falls within the ranges allowed by -// yrs. Always returns true is yrs is empty. -func isInRanges(yrs yang.YangRange, val yang.Number) bool { - if len(yrs) == 0 { - return true - } - for _, yr := range yrs { - if isInRange(yr, val) { - return true - } - } - return false -} - -// isInRange reports whether the given value falls within the range allowed by -// yr. -func isInRange(yr yang.YRange, val yang.Number) bool { - return (val.Less(yr.Max) || val.Equal(yr.Max)) && - (yr.Min.Less(val) || yr.Min.Equal(val)) -} - -// validateListAttr validates any attributes of value present in the schema, -// such as min/max elements. The schema and value can be a container, -// list, or leaf-list type. -func validateListAttr(schema *yang.Entry, value interface{}) util.Errors { - var errors []error - if schema == nil { - return util.NewErrs(fmt.Errorf("schema is nil")) - } - if schema.ListAttr == nil { - return util.NewErrs(fmt.Errorf("schema %s ListAttr is nil", schema.Name)) - } - - var size int - if value == nil { - size = 0 - } else { - switch reflect.TypeOf(value).Kind() { - case reflect.Slice, reflect.Map: - size = reflect.ValueOf(value).Len() - default: - return util.NewErrs(fmt.Errorf("value %v type %T must be map or slice type for schema %s", value, value, schema.Name)) - } - } - - // If min/max element attr is present in the schema, this must be a list or - // leaf-list. Check that the data tree falls within the required size - // bounds. - if v := schema.ListAttr.MinElements; v != nil { - if minN, err := yang.ParseNumber(v.Name); err != nil { - errors = util.AppendErr(errors, err) - } else if min, err := minN.Int(); err != nil { - errors = util.AppendErr(errors, err) - } else if min < 0 { - errors = util.AppendErr(errors, fmt.Errorf("list %s has negative min required elements", schema.Name)) - } else if int64(size) < min { - errors = util.AppendErr(errors, fmt.Errorf("list %s contains fewer than min required elements: %d < %d", schema.Name, size, min)) - } - } - if v := schema.ListAttr.MaxElements; v != nil { - if maxN, err := yang.ParseNumber(v.Name); err != nil { - errors = util.AppendErr(errors, err) - } else if max, err := maxN.Int(); err != nil { - errors = util.AppendErr(errors, err) - } else if max < 0 { - errors = util.AppendErr(errors, fmt.Errorf("list %s has negative max required elements", schema.Name)) - } else if int64(size) > max { - errors = util.AppendErr(errors, fmt.Errorf("list %s contains more than max allowed elements: %d > %d", schema.Name, size, max)) - } - } - - return errors -} - -// isValueScalar reports whether v is a scalar (non-composite) type. -func isValueScalar(v reflect.Value) bool { - return !util.IsValueStruct(v) && !util.IsValueStructPtr(v) && !util.IsValueMap(v) && !util.IsValueSlice(v) -} - -// childSchema returns the schema for the struct field f, if f contains a valid -// path tag and the schema path is found in the schema tree. It returns an error -// if the struct tag is invalid, or nil if tag is valid but the schema is not -// found in the tree at the specified path. -func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) { - if util.IsDebugSchemaEnabled() { - pathTag, _ := f.Tag.Lookup("path") - util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) - } - p, err := pathToSchema(f) - if err != nil { - return nil, err - } - - // Containers have the container schema name as the first element in the - // path tag for each field e.g. System { Dns ... path: "system/dns" - // Strip this off since the supplied schema already refers to the struct - // schema element. - if schema.IsContainer() && len(p) > 1 && p[0] == schema.Name { - p = p[1:] - } - util.DbgSchema("pathToSchema yields %v\n", p) - // For empty path, return the parent schema. - childSchema := schema - foundSchema := true - // Traverse the returned schema path to get the child schema. - util.DbgSchema("traversing schema Dirs...") - for ; len(p) > 0; p = p[1:] { - util.DbgSchema("/%s", p[0]) - ns, ok := childSchema.Dir[util.StripModulePrefix(p[0])] - if !ok { - foundSchema = false - break - } - childSchema = ns - } - if foundSchema { - util.DbgSchema(" - found\n") - return childSchema, nil - } - util.DbgSchema(" - not found\n") - - // Path is not null and was not found in the schema. It could be inside a - // choice/case schema element which is not represented in the path tags. - // e.g. choice1/case1/leaf1 could have abbreviated tag `path: "leaf1"`. - // In this case, try to match against any named elements within any choice/ - // case subtrees. These are guaranteed to be unique within the current - // level namespace so a path tag name match will be unique if one is found. - if len(p) != 1 { - // Nodes within choice/case have a path tag with only the last schema - // path element i.e. choice1/case1/leaf1 path in the schema will have - // struct tag `path:"leaf1"`. This implies that only paths with length - // 1 are eligible for this matching. - return nil, nil - } - entries := util.FindFirstNonChoiceOrCase(schema) - if util.IsDebugSchemaEnabled() { - util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) - } - for name, entry := range entries { - util.DbgSchema("%s ? ", name) - - if util.StripModulePrefix(name) == p[0] { - util.DbgSchema(" - match\n") - return entry, nil - } - } - - util.DbgSchema(" - no matches\n") - return nil, nil -} - -// schemaTreeRoot returns the root of the schema tree, given any node in that -// tree. It returns nil if schema is nil. -func schemaTreeRoot(schema *yang.Entry) *yang.Entry { - if schema == nil { - return nil - } - - root := schema - for root.Parent != nil { - root = root.Parent - } - - return root -} - -// absoluteSchemaDataPath returns the absolute path of the schema, excluding -// any choice or case entries. -// TODO(mostrowski): why are these excluded? -func absoluteSchemaDataPath(schema *yang.Entry) string { - out := []string{schema.Name} - for s := schema.Parent; s != nil; s = s.Parent { - if !util.IsChoiceOrCase(s) && !util.IsFakeRoot(s) { - out = append([]string{s.Name}, out...) - } - } - - return "/" + strings.Join(out, "/") -} - -// pathToSchema returns a path to the schema for the struct field f. -// Paths are embedded in the "path" struct tag and can be either simple: -// e.g. "path:a" -// or composite e.g. -// e.g. "path:config/a|a" -// which is found in OpenConfig leaf-ref cases where the key of a list is a -// leafref. In the latter case, this function returns {"config", "a"}, and the -// schema *yang.Entry for the field is given by schema.Dir["config"].Dir["a"]. -func pathToSchema(f reflect.StructField) ([]string, error) { - pathAnnotation, ok := f.Tag.Lookup("path") - if !ok { - return nil, fmt.Errorf("field %s did not specify a path", f.Name) - } - - paths := strings.Split(pathAnnotation, "|") - if len(paths) == 1 { - pathAnnotation = strings.TrimPrefix(pathAnnotation, "/") - return strings.Split(pathAnnotation, "/"), nil - } - for _, pv := range paths { - pv = strings.TrimPrefix(pv, "/") - pe := strings.Split(pv, "/") - if len(pe) > 1 { - return pe, nil - } - } - - return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathAnnotation) -} - -// directDescendantSchema returns the direct descendant schema for the struct -// field f. Paths are embedded in the "path" struct tag and can be either simple: -// e.g. "path:a" -// or composite e.g. -// e.g. "path:config/a|a" -// Function checks for presence of first schema without '/' and returns it. -func directDescendantSchema(f reflect.StructField) (string, error) { - pathAnnotation, ok := f.Tag.Lookup("path") - if !ok { - return "", fmt.Errorf("field %s did not specify a path", f.Name) - } - paths := strings.Split(pathAnnotation, "|") - - for _, pth := range paths { - if len(strings.Split(pth, "/")) == 1 { - return pth, nil - } - } - return "", fmt.Errorf("failed to find a schema path for %v", f) -} - -// dataTreePaths returns all the data tree paths corresponding to schemaPaths. -// Any intermediate nodes not found in the data tree (i.e. choice/case) are -// removed from the paths. -func dataTreePaths(parentSchema, schema *yang.Entry, f reflect.StructField) ([][]string, error) { - out, err := util.SchemaPaths(f) - if err != nil { - return nil, err - } - n, err := removeNonDataPathElements(parentSchema, schema, out) - util.DbgPrint("have paths %v, removing non-data from %s -> %v", out, schema.Name, n) - return n, err -} - -// removeNonDataPathElements removes any path elements in paths not found in -// the data tree given the terminal node schema and the schema of its parent. -func removeNonDataPathElements(parentSchema, schema *yang.Entry, paths [][]string) ([][]string, error) { - var out [][]string - for _, path := range paths { - var po []string - s := parentSchema - if path[0] == s.Name { - po = append(po, path[0]) - path = path[1:] - } - for _, pe := range path { - s = s.Dir[pe] - if s == nil { - // Some paths exist only in the data tree but not the schema - // tree. In this case just retain the path purely on trust. - // TODO(mostrowski): make this more robust. It should be in - // the root only. - po = path - break - } - if !util.IsChoiceOrCase(s) { - po = append(po, pe) - } - } - out = append(out, po) - } - - return out, nil -} - -// checkDataTreeAgainstPaths checks each of dataPaths against the first level -// of the data tree. It returns an error with the first element in the data tree first -// level that is not found in dataPaths. -// This function is used to verify that the jsonTree does not contain any elements -// in the first level that do not have data paths found in the schema. -func checkDataTreeAgainstPaths(jsonTree map[string]interface{}, dataPaths [][]string) error { - // Go over all first level JSON tree map keys to make sure they all point - // to valid schema paths. - pm := map[string]bool{} - for _, sp := range dataPaths { - pm[util.StripModulePrefix(sp[0])] = true - } - util.DbgSchema("check dataPaths %v against dataTree %v\n", pm, jsonTree) - for jf := range jsonTree { - if !pm[util.StripModulePrefix(jf)] { - return fmt.Errorf("JSON contains unexpected field %s", jf) - } - } - - return nil -} - -// removeRootPrefix removes the root prefix from root schema entities e.g. -// Bgp_Global has path "/bgp/global" == {"", "bgp", "global"} -// -> {"global"} -func removeRootPrefix(path []string) []string { - if len(path) < 2 || path[0] != "" { - // not a root path - return path - } - return path[2:] -} - -// resolveLeafRef returns a ptr to the schema pointed to by the provided leaf-ref -// schema. It returns schema itself if schema is not a leaf-ref. -func resolveLeafRef(schema *yang.Entry) (*yang.Entry, error) { - if schema == nil { - return nil, nil - } - // TODO(mostrowski): this should only be possible in fakeroot. Add an - // explicit check for that once data is available in the schema. - if schema.Type == nil { - return schema, nil - } - - orig := schema - s := schema - for ykind := s.Type.Kind; ykind == yang.Yleafref; { - ns, err := findLeafRefSchema(s, s.Type.Path) - if err != nil { - return schema, err - } - s = ns - ykind = s.Type.Kind - } - - if s != orig { - util.DbgPrint("follow schema leaf-ref from %s to %s, type %v", orig.Name, s.Name, s.Type.Kind) - } - return s, nil -} - -// schemaToStructFieldName returns the string name of the field, which must be -// contained in parent (a struct ptr), given the schema for the field. -// It returns empty string and nil error if the field does not exist in the -// parent struct. -func schemaToStructFieldName(schema *yang.Entry, parent interface{}) (string, *yang.Entry, error) { - - v := reflect.ValueOf(parent) - if util.IsNilOrInvalidValue(v) { - return "", nil, fmt.Errorf("parent field is nil in schemaToStructFieldName for node %s", schema.Name) - } - - t := reflect.TypeOf(parent) - switch t.Kind() { - case reflect.Map, reflect.Slice: - t = t.Elem() - } - // If parent is a map of struct ptrs, still need to deref the element type. - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - fieldName := f.Name - p, err := pathToSchema(f) - if err != nil { - return "", nil, err - } - if hasRelativePath(schema, p) { - return fieldName, schema, nil - } - if ns := findSchemaAtPath(schema, p); ns != nil { - return fieldName, ns, nil - } - } - - return "", nil, fmt.Errorf("struct field %s not found in parent %v, type %T", schema.Name, parent, parent) -} - -// findSchemaAtPath returns the schema at the given path, ignoring module -// prefixes in the path. It returns nil if no schema is found. -func findSchemaAtPath(schema *yang.Entry, path []string) *yang.Entry { - s := schema - for i := 0; i < len(path); i++ { - pe := util.StripModulePrefix(path[i]) - if s.Dir[pe] == nil { - return nil - } - s = s.Dir[pe] - } - return s -} - -// hasRelativePath reports whether the given schema node matches the given -// relative path in the schema tree. It walks the schema tree towards the root, -// comparing each path element against nodes in the tree. It returns success -// only if all path elements are present as parent nodes in the schema tree. -func hasRelativePath(schema *yang.Entry, path []string) bool { - s, p := schema, path - for { - if s == nil || len(p) == 0 { - break - } - n := util.StripModulePrefix(p[len(p)-1]) - if s.Name != n { - return false - } - s = s.Parent - p = p[:len(p)-1] - } - - return len(p) == 0 -} - -// derefIfStructPtr returns the dereferenced reflect.Value of value if it is a -// ptr, or value if it is not. -func derefIfStructPtr(value reflect.Value) reflect.Value { - if util.IsValueStructPtr(value) { - return value.Elem() - } - return value -} diff --git a/ygot-modified-files/validate.go b/ygot-modified-files/validate.go deleted file mode 100644 index 99ea48f1f0..0000000000 --- a/ygot-modified-files/validate.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ytypes - -import ( - "fmt" - - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" - "github.com/openconfig/ygot/ygot" -) - -// LeafrefOptions controls the behaviour of validation functions for leaf-ref -// data types. -type LeafrefOptions struct { - // IgnoreMissingData determines whether leafrefs that target a node - // that does not exist should return an error to the calling application. When - // set to true, no error is returned. - // - // This functionality is typically used where a partial set of schema information - // is populated, but validation is required - for example, configuration for - // a protocol within OpenConfig references an interface, but the schema being - // validated does not contain the interface definitions. - IgnoreMissingData bool - // Log specifies whether log entries should be created where a leafref - // cannot be successfully resolved. - Log bool -} - -// IsValidationOption ensures that LeafrefOptions implements the ValidationOption -// interface. -func (*LeafrefOptions) IsValidationOption() {} - -// Validate recursively validates the value of the given data tree struct -// against the given schema. -func Validate(schema *yang.Entry, value interface{}, opts ...ygot.ValidationOption) util.Errors { - // Nil value means the field is unset. - if util.IsValueNil(value) { - return nil - } - if schema == nil { - return util.NewErrs(fmt.Errorf("nil schema for type %T, value %v", value, value)) - } - - // TODO(robjs): Consider making this function a utility function when - // additional validation options are added here. Note that this code - // currently will accept multiple of the same option being specified, - // and overwrite with the last within the options slice, rather than - // explicitly returning an error. - var leafrefOpt *LeafrefOptions - for _, o := range opts { - switch o.(type) { - case *LeafrefOptions: - leafrefOpt = o.(*LeafrefOptions) - } - } - - var errs util.Errors - if util.IsFakeRoot(schema) { - // Leafref validation traverses entire tree from the root. Do this only - // once from the fakeroot. - errs = ValidateLeafRefData(schema, value, leafrefOpt) - } - - util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStrDebug(value), value, schema.Name) - - switch { - case schema.IsLeaf(): - return util.AppendErrs(errs, validateLeaf(schema, value)) - case schema.IsContainer(): - gsv, ok := value.(ygot.GoStruct) - if !ok { - return util.AppendErr(errs, fmt.Errorf("type %T is not a GoStruct for schema %s", value, schema.Name)) - } - return util.AppendErrs(errs, validateContainer(schema, gsv)) - case schema.IsLeafList(): - return util.AppendErrs(errs, validateLeafList(schema, value)) - case schema.IsList(): - return util.AppendErrs(errs, validateList(schema, value)) - case schema.IsChoice(): - return util.AppendErrs(errs, util.NewErrs(fmt.Errorf("cannot pass choice schema %s to Validate", schema.Name))) - } - - return util.AppendErrs(errs, util.NewErrs(fmt.Errorf("unknown schema type for type %T, value %v", value, value))) -} diff --git a/ygot-modified-files/ygot.patch b/ygot-modified-files/ygot.patch new file mode 100644 index 0000000000..a6f274019c --- /dev/null +++ b/ygot-modified-files/ygot.patch @@ -0,0 +1,448 @@ +Binary files ygot-dir-orig/ygot/.git/index and ygot-dir/ygot/.git/index differ +diff -ruN ygot-dir-orig/ygot/util/debug.go ygot-dir/ygot/util/debug.go +--- ygot-dir-orig/ygot/util/debug.go 2019-09-19 13:30:08.402915000 -0700 ++++ ygot-dir/ygot/util/debug.go 2019-09-19 16:11:34.544512000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -53,6 +56,14 @@ + fmt.Println(globalIndent + out) + } + ++func IsDebugLibraryEnabled () bool { ++ return debugLibrary ++} ++ ++func IsDebugSchemaEnabled () bool { ++ return debugSchema ++} ++ + // DbgSchema prints v if the package global variable debugSchema is set. + // v has the same format as Printf. + func DbgSchema(v ...interface{}) { +@@ -177,6 +188,9 @@ + + // YangTypeToDebugString returns a debug string representation of a YangType. + func YangTypeToDebugString(yt *yang.YangType) string { ++ if !debugLibrary { ++ return "" ++ } + out := fmt.Sprintf("(TypeKind: %s", yang.TypeKindToName[yt.Kind]) + if len(yt.Pattern) != 0 { + out += fmt.Sprintf(", Pattern: %s", strings.Join(yt.Pattern, " or ")) +diff -ruN ygot-dir-orig/ygot/util/reflect.go ygot-dir/ygot/util/reflect.go +--- ygot-dir-orig/ygot/util/reflect.go 2019-09-19 13:30:08.407695000 -0700 ++++ ygot-dir/ygot/util/reflect.go 2019-09-19 16:12:05.207782000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -196,8 +199,10 @@ + + // InsertIntoMap inserts value with key into parent which must be a map. + func InsertIntoMap(parentMap interface{}, key interface{}, value interface{}) error { +- DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", +- parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) ++ if debugLibrary { ++ DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", ++ parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) ++ } + + v := reflect.ValueOf(parentMap) + t := reflect.TypeOf(parentMap) +@@ -288,7 +293,7 @@ + n = reflect.Zero(ft.Type) + } + +- if !isFieldTypeCompatible(ft, n) { ++ if !isFieldTypeCompatible(ft, n) && !IsValueTypeCompatible(ft.Type, v) { + return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, ft.Type, parentStruct) + } + +diff -ruN ygot-dir-orig/ygot/util/schema.go ygot-dir/ygot/util/schema.go +--- ygot-dir-orig/ygot/util/schema.go 2019-09-19 13:30:08.411894000 -0700 ++++ ygot-dir/ygot/util/schema.go 2019-09-19 16:12:18.706934000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -233,7 +236,9 @@ + found := true + DbgSchema("traversing schema Dirs...") + for ; len(p) > 0; p = p[1:] { +- DbgSchema("/%s", p[0]) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("/%s", p[0]) ++ } + var ok bool + s, ok = s.Dir[p[0]] + if !ok { +@@ -261,10 +266,13 @@ + return nil, nil + } + entries := FindFirstNonChoiceOrCase(schema) +- +- DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ } + for pe, entry := range entries { +- DbgSchema("%s ? ", pe) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("%s ? ", pe) ++ } + if pe == p[0] { + DbgSchema(" - match\n") + return entry, nil +diff -ruN ygot-dir-orig/ygot/ytypes/container.go ygot-dir/ygot/ytypes/container.go +--- ygot-dir-orig/ygot/ytypes/container.go 2019-09-19 13:30:08.416535000 -0700 ++++ ygot-dir/ygot/ytypes/container.go 2019-09-19 16:11:22.883975000 -0700 +@@ -12,12 +12,15 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( + "fmt" + "reflect" +- ++ + "github.com/kylelemons/godebug/pretty" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/util" +@@ -71,7 +74,7 @@ + if errs := Validate(cschema, fieldValue); errs != nil { + errors = util.AppendErrs(errors, util.PrefixErrors(errs, cschema.Path())) + } +- case !util.IsValueNilOrDefault(structElems.Field(i).Interface()): ++ case !structElems.Field(i).IsNil(): + // Either an element in choice schema subtree, or bad field. + // If the former, it will be found in the choice check below. + extraFields[fieldName] = nil +@@ -217,7 +220,10 @@ + } + } + +- util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) ++ } ++ + return nil + } + +diff -ruN ygot-dir-orig/ygot/ytypes/leaf.go ygot-dir/ygot/ytypes/leaf.go +--- ygot-dir-orig/ygot/ytypes/leaf.go 2019-09-19 13:30:08.421645000 -0700 ++++ ygot-dir/ygot/ytypes/leaf.go 2019-09-19 16:11:40.554385000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -79,7 +82,7 @@ + + switch ykind { + case yang.Ybinary: +- return util.NewErrs(validateBinary(schema, value)) ++ return util.NewErrs(validateBinary(schema, rv)) + case yang.Ybits: + return nil + // TODO(mostrowski): restore when representation is decided. +@@ -418,12 +421,10 @@ + return nil + } + +-// YANGEmpty is a derived type which is used to represent the YANG empty type. ++// YANGEmpty is a derived type which is used to represent the YANG ++// empty type. + type YANGEmpty bool + +-// Binary is a derived type which is used to represent the YANG binary type. +-type Binary []byte +- + // unmarshalLeaf unmarshals a scalar value (determined by json.Unmarshal) into + // the parent containing the leaf. + // schema points to the schema for the leaf type. +@@ -720,7 +721,9 @@ + return nil, fmt.Errorf("%s ΛEnumTypes function returned wrong type %T, want map[string][]reflect.Type", t, ei) + } + +- util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) ++ } + + return enumTypesMap[absoluteSchemaDataPath(schema)], nil + } +diff -ruN ygot-dir-orig/ygot/ytypes/list.go ygot-dir/ygot/ytypes/list.go +--- ygot-dir-orig/ygot/ytypes/list.go 2019-09-19 13:30:08.426123000 -0700 ++++ ygot-dir/ygot/ytypes/list.go 2019-09-19 16:11:45.587618000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -350,7 +353,9 @@ + return err + } + } +- util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) ++ } + + return nil + } +@@ -388,17 +393,96 @@ + if err != nil { + return err + } +- + fv := val.Elem().FieldByName(fn) + ft := fv.Type() + if util.IsValuePtr(fv) { + ft = ft.Elem() + } +- +- nv, err := StringToType(ft, fieldVal) ++ sf, ok := val.Elem().Type().FieldByName(fn) ++ if ok == false { ++ return fmt.Errorf("Field %s not present in the struct %s", fn, val.Elem()) ++ } ++ cschema, err := childSchema(schema, sf) + if err != nil { + return err + } ++ keyLeafKind := cschema.Type.Kind ++ if keyLeafKind == yang.Yleafref { ++ lrfschema, err := resolveLeafRef(cschema) ++ if err != nil { ++ return err ++ } ++ keyLeafKind = lrfschema.Type.Kind ++ } ++ ++ var nv reflect.Value ++ if keyLeafKind == yang.Yunion { ++ sks, err := getUnionKindsNotEnums(cschema) ++ if err != nil { ++ return err ++ } ++ for _, sk := range sks { ++ gv, err := StringToType(reflect.TypeOf(yangBuiltinTypeToGoType(sk)), fieldVal) ++ if err == nil { ++ mn := "To_" + ft.Name() ++ mapMethod := val.MethodByName(mn) ++ if !mapMethod.IsValid() { ++ return fmt.Errorf("%s does not have a %s function", val, mn) ++ } ++ ec := mapMethod.Call([]reflect.Value{gv}) ++ if len(ec) != 2 { ++ return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) ++ } ++ ei := ec[0].Interface() ++ ee := ec[1].Interface() ++ if ee != nil { ++ return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) ++ } ++ nv = reflect.ValueOf(ei) ++ break ++ } ++ } ++ ++ if nv.IsValid() == false { ++ ets, err := schemaToEnumTypes(cschema, elmT) ++ if err != nil { ++ return err ++ } ++ for _, et := range ets { ++ ev, err := castToEnumValue(et, fieldVal) ++ if err != nil { ++ return err ++ } ++ if ev != nil { ++ mn := "To_" + ft.Name() ++ mapMethod := val.MethodByName(mn) ++ if !mapMethod.IsValid() { ++ return fmt.Errorf("%s does not have a %s function", val, mn) ++ } ++ ec := mapMethod.Call([]reflect.Value{reflect.ValueOf(ev)}) ++ if len(ec) != 2 { ++ return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) ++ } ++ ei := ec[0].Interface() ++ ee := ec[1].Interface() ++ if ee != nil { ++ return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) ++ } ++ nv = reflect.ValueOf(ei) ++ break ++ } ++ fmt.Errorf("could not unmarshal %v into enum type: %s\n", fieldVal, err) ++ } ++ if nv.IsValid() == false { ++ return fmt.Errorf("could not create the value type for the field name %s with the value %s", fn, fieldVal) ++ } ++ } ++ } else { ++ nv, err = StringToType(ft, fieldVal) ++ if err != nil { ++ return err ++ } ++ } + return util.InsertIntoStruct(val.Interface(), fn, nv.Interface()) + } + +@@ -494,6 +578,9 @@ + } + + // TODO(yusufsn): When the key is a leafref, its target should be filled out. ++ if (len(keys) == 0) { ++ return nil, nil ++ } + mapVal, err := makeValForInsert(schema, root, keys) + if err != nil { + return nil, fmt.Errorf("failed to create map value for insert, root %T, keys %v: %v", root, keys, err) +diff -ruN ygot-dir-orig/ygot/ytypes/node.go ygot-dir/ygot/ytypes/node.go +--- ygot-dir-orig/ygot/ytypes/node.go 2019-09-19 13:30:08.431085000 -0700 ++++ ygot-dir/ygot/ytypes/node.go 2019-09-19 16:11:51.435738000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -286,6 +289,11 @@ + if err != nil { + return nil, err + } ++ ++ if (key == nil) { ++ return []*TreeNode{{Path: traversedPath,Schema: schema,Data: root,}}, nil ++ } ++ + nodes, err := retrieveNode(schema, rv.MapIndex(reflect.ValueOf(key)).Interface(), util.PopGNMIPath(path), appendElem(traversedPath, path.GetElem()[0]), args) + if err != nil { + return nil, err +diff -ruN ygot-dir-orig/ygot/ytypes/unmarshal.go ygot-dir/ygot/ytypes/unmarshal.go +--- ygot-dir-orig/ygot/ytypes/unmarshal.go 2019-09-19 13:30:08.436899000 -0700 ++++ ygot-dir/ygot/ytypes/unmarshal.go 2019-09-19 16:12:24.257821000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -73,7 +76,10 @@ + if schema == nil { + return fmt.Errorf("nil schema for parent type %T, value %v (%T)", parent, value, value) + } +- util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) ++ ++ if (util.IsDebugLibraryEnabled()) { ++ util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) ++ } + + if enc == GNMIEncoding && !(schema.IsLeaf() || schema.IsLeafList()) { + return errors.New("unmarshalling a non leaf node isn't supported in GNMIEncoding mode") +diff -ruN ygot-dir-orig/ygot/ytypes/util_schema.go ygot-dir/ygot/ytypes/util_schema.go +--- ygot-dir-orig/ygot/ytypes/util_schema.go 2019-09-19 13:30:08.441339000 -0700 ++++ ygot-dir/ygot/ytypes/util_schema.go 2019-09-19 16:12:30.149209000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -137,8 +140,10 @@ + // if the struct tag is invalid, or nil if tag is valid but the schema is not + // found in the tree at the specified path. + func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) { +- pathTag, _ := f.Tag.Lookup("path") +- util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) ++ if util.IsDebugSchemaEnabled() { ++ pathTag, _ := f.Tag.Lookup("path") ++ util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) ++ } + p, err := pathToSchema(f) + if err != nil { + return nil, err +@@ -186,8 +191,9 @@ + return nil, nil + } + entries := util.FindFirstNonChoiceOrCase(schema) +- +- util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ if util.IsDebugSchemaEnabled() { ++ util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ } + for name, entry := range entries { + util.DbgSchema("%s ? ", name) + +diff -ruN ygot-dir-orig/ygot/ytypes/validate.go ygot-dir/ygot/ytypes/validate.go +--- ygot-dir-orig/ygot/ytypes/validate.go 2019-09-19 13:30:08.445376000 -0700 ++++ ygot-dir/ygot/ytypes/validate.go 2019-09-19 16:12:40.050604000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -74,7 +77,7 @@ + errs = ValidateLeafRefData(schema, value, leafrefOpt) + } + +- util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStr(value), value, schema.Name) ++ util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStrDebug(value), value, schema.Name) + + switch { + case schema.IsLeaf():