-
Notifications
You must be signed in to change notification settings - Fork 120
Expand file tree
/
Copy pathcontainer.go
More file actions
266 lines (229 loc) · 9.14 KB
/
container.go
File metadata and controls
266 lines (229 loc) · 9.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// 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"
"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.ValidatedGoStruct) 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 := util.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 !util.IsValueNilOrDefault(structElems.Field(i).Interface()):
// 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) {
// We need to find the paths that we should have unmarshalled here to avoid
// throwing errors to users whilst there is a TODO above.
paths, err := pathTagFromField(ft)
if err != nil {
return fmt.Errorf("cannot find JSON field names for annotation field %s, %v", ft.Name, err)
}
for _, s := range strings.Split(paths, "|") {
pp := strings.Split(s, "/")
allSchemaPaths = append(allSchemaPaths, []string{pp[len(pp)-1]})
}
continue
}
cschema, err := util.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)
}
// 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 there are shadow schema paths, also add them to the allowlist
// avoid an unmarshalling error.
// NOTE: This is more permissive than ideal in that it doesn't
// catch other types of non-compliance errors, i.e. if the JSON
// shadow node is a container (should not occur under
// OpenConfig YANG rules), or if the JSON cannot be
// unmarshalled due to type mismatch.
ssp, err := shadowDataTreePaths(schema, cschema, ft)
if err != nil {
return err
}
allSchemaPaths = append(allSchemaPaths, ssp...)
jsonValue, err := getJSONTreeValForField(schema, cschema, ft, jsonTree)
if err != nil {
return err
}
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)
}
}
util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface()))
return nil
}
// validateContainerSchema validates the given container type schema. This is a
// quick check 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
}