@@ -53,12 +53,6 @@ type retrieveNodeArgs struct {
5353 // specifically to deal with uint values being streamed as positive int
5454 // values.
5555 tolerateJSONInconsistenciesForVal bool
56- // shadowPath is an implementation field indicating that the current
57- // path being traversed is a shadow path, and should result in a silent
58- // traversal, i.e. a normal traversal except any modifications to leaf
59- // values are skipped (non-existing GoStructs are still initialized),
60- // and any value retrieved is always nil.
61- shadowPath bool
6256 // reverseShadowPath reverses the meaning of the "path" and
6357 // "shadow-path" tags when both are present while processing a
6458 // GoStruct.
@@ -80,12 +74,6 @@ func retrieveNode(schema *yang.Entry, root interface{}, path, traversedPath *gpb
8074 return nil , status .Errorf (codes .Unknown , "path %v points to a node with non-leaf schema %v" , traversedPath , schema )
8175 }
8276 }
83- // Shadow traversal returns the node with nil schema and data.
84- if args .shadowPath {
85- return []* TreeNode {{
86- Path : traversedPath ,
87- }}, nil
88- }
8977 return []* TreeNode {{
9078 Path : traversedPath ,
9179 Schema : schema ,
@@ -128,6 +116,8 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
128116 for i := 0 ; i < v .NumField (); i ++ {
129117 fv , ft := v .Field (i ), v .Type ().Field (i )
130118
119+ // TODO(low priority): ChildSchema should return the shadow
120+ // schema if we're traversing a shadow path.
131121 cschema , err := util .ChildSchema (schema , ft )
132122 if ! util .IsYgotAnnotation (ft ) {
133123 switch {
@@ -138,12 +128,36 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
138128 }
139129 }
140130
141- checkPath := func (p []string , args retrieveNodeArgs ) ([]* TreeNode , error ) {
131+ checkPath := func (p []string , args retrieveNodeArgs , shadowLeaf bool ) ([]* TreeNode , error ) {
142132 to := len (p )
143133 if util .IsTypeMap (ft .Type ) {
144134 to --
145135 }
136+ np := & gpb.Path {}
137+ if traversedPath != nil {
138+ np = proto .Clone (traversedPath ).(* gpb.Path )
139+ }
140+ for i := range p [0 :to ] {
141+ np .Elem = append (np .Elem , path .GetElem ()[i ])
142+ }
143+
144+ // If the current node is a shadow leaf, this means the input path is a shadow path
145+ // that the GoStruct recognizes, but doesn't have space for. We will therefore
146+ // silently ignore this path.
147+ if shadowLeaf {
148+ switch {
149+ case cschema == nil :
150+ return nil , status .Errorf (codes .InvalidArgument , "could not find schema for path %v" , np )
151+ case ! cschema .IsLeaf ():
152+ return nil , status .Errorf (codes .InvalidArgument , "shadow path traverses a non-leaf node, this is not allowed, path: %v" , np )
153+ default :
154+ return []* TreeNode {{
155+ Path : np ,
156+ }}, nil
157+ }
158+ }
146159
160+ // If args.modifyRoot is true, then initialize the field before possibly searching further.
147161 if args .modifyRoot {
148162 if err := util .InitializeStructField (root , ft .Name ); err != nil {
149163 return nil , status .Errorf (codes .Unknown , "failed to initialize struct field %s in %T, child schema %v, path %v" , ft .Name , root , cschema , path )
@@ -154,17 +168,15 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
154168 // corresponding field to its zero value. The zero value is the unset value for
155169 // any node type, whether leaf or non-leaf.
156170 if args .delete && len (path .Elem ) == to {
157- if ! args .shadowPath {
158- fv .Set (reflect .Zero (ft .Type ))
159- }
171+ fv .Set (reflect .Zero (ft .Type ))
160172 return nil , nil
161173 }
162174
163175 // If val in args is set to a non-nil value and the path is exhausted, we
164176 // may be dealing with a leaf or leaf list node. We should set the val
165177 // to the corresponding field in GoStruct. If the field is an annotation,
166178 // the field doesn't have a schema, so it is handled separately.
167- if ! util .IsValueNil (args .val ) && len (path .Elem ) == to && ! args . shadowPath {
179+ if ! util .IsValueNil (args .val ) && len (path .Elem ) == to {
168180 switch {
169181 case util .IsYgotAnnotation (ft ):
170182 if err := util .UpdateField (root , ft .Name , args .val ); err != nil {
@@ -184,13 +196,6 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
184196 }
185197 }
186198
187- np := & gpb.Path {}
188- if traversedPath != nil {
189- np = proto .Clone (traversedPath ).(* gpb.Path )
190- }
191- for i := range p [0 :to ] {
192- np .Elem = append (np .Elem , path .GetElem ()[i ])
193- }
194199 return retrieveNode (cschema , fv .Interface (), util .TrimGNMIPathPrefix (path , p [0 :to ]), np , args )
195200 }
196201
@@ -200,26 +205,26 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
200205 // Note that we first look through the non-shadow path, and if
201206 // no matches are found, we then look through the shadow path
202207 // to find matches. If the input path matches a shadow path,
203- // then the traversal continues, although the final operation
204- // is marked as a no-op .
208+ // then we're guaranteed to have reached a leaf, since shadow
209+ // paths can only occur for direct leaves under config/state .
205210 //
206211 // If the user has opted to reverse the "shadow-path" and
207212 // "path" tags, then the order of the look-ups is reversed.
208- args := args
213+ var shadowLeaf bool
209214 if args .reverseShadowPath {
210215 // Look through shadow paths first instead.
211216 schPaths := util .ShadowSchemaPaths (ft )
212217 for _ , p := range schPaths {
213218 if util .PathMatchesPrefix (path , p ) {
214- return checkPath (p , args )
219+ return checkPath (p , args , false )
215220 }
216221 }
217222
218223 if len (schPaths ) != 0 {
219- // Only if there exists shadow paths do we
220- // treat the non-shadow paths as no-ops .
221- // Otherwise, there is no reversal to do .
222- args . shadowPath = true
224+ // Only if there exists shadow paths is there
225+ // such thing as doing a reverse look-up, i.e .
226+ // reversing non-shadow paths with shadow paths .
227+ shadowLeaf = true
223228 }
224229 }
225230 schPaths , err := util .SchemaPaths (ft )
@@ -228,16 +233,14 @@ func retrieveNodeContainer(schema *yang.Entry, root interface{}, path *gpb.Path,
228233 }
229234 for _ , p := range schPaths {
230235 if util .PathMatchesPrefix (path , p ) {
231- return checkPath (p , args )
236+ return checkPath (p , args , shadowLeaf )
232237 }
233238 }
234239 if ! args .reverseShadowPath {
235- // Look through shadow paths last, and mark operations
236- // as no-ops.
237- args .shadowPath = true
240+ // Look through shadow paths last.
238241 for _ , p := range util .ShadowSchemaPaths (ft ) {
239242 if util .PathMatchesPrefix (path , p ) {
240- return checkPath (p , args )
243+ return checkPath (p , args , true )
241244 }
242245 }
243246 }
@@ -303,7 +306,7 @@ func retrieveNodeList(schema *yang.Entry, root interface{}, path, traversedPath
303306 }
304307 if keyAsString == pathKey {
305308 remainingPath := util .PopGNMIPath (path )
306- if args .delete && len (remainingPath .GetElem ()) == 0 && ! args . shadowPath {
309+ if args .delete && len (remainingPath .GetElem ()) == 0 {
307310 rv .SetMapIndex (k , reflect.Value {})
308311 return nil , nil
309312 }
@@ -357,7 +360,7 @@ func retrieveNodeList(schema *yang.Entry, root interface{}, path, traversedPath
357360 return nil , status .Errorf (codes .Unknown , "could not extract keys from %v: %v" , traversedPath , err )
358361 }
359362 remainingPath := util .PopGNMIPath (path )
360- if args .delete && len (remainingPath .GetElem ()) == 0 && ! args . shadowPath {
363+ if args .delete && len (remainingPath .GetElem ()) == 0 {
361364 rv .SetMapIndex (k , reflect.Value {})
362365 return nil , nil
363366 }
@@ -399,6 +402,10 @@ type GetOrCreateNodeOpt interface {
399402// along the path if they are nil.
400403// Function returns the value and schema of the node as well as error.
401404// Note that this function may modify the supplied root even if the function fails.
405+ // Note that this function may create containers or list entries even if the input path is a shadow path.
406+ // TODO(wenbli): a traversal should remember what containers or list entries
407+ // were created so that a failed call or a call to a shadow path can later undo
408+ // this. This applies to SetNode as well.
402409func GetOrCreateNode (schema * yang.Entry , root interface {}, path * gpb.Path , opts ... GetOrCreateNodeOpt ) (interface {}, * yang.Entry , error ) {
403410 nodes , err := retrieveNode (schema , root , path , nil , retrieveNodeArgs {
404411 modifyRoot : true ,
0 commit comments