| /* |
| Copyright 2018 The Kubernetes Authors. |
| |
| 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 fieldpath |
| |
| import ( |
| "sigs.k8s.io/structured-merge-diff/value" |
| ) |
| |
| // SetFromValue creates a set containing every leaf field mentioned in v. |
| func SetFromValue(v value.Value) *Set { |
| s := NewSet() |
| |
| w := objectWalker{ |
| path: Path{}, |
| value: v, |
| do: func(p Path) { s.Insert(p) }, |
| } |
| |
| w.walk() |
| return s |
| } |
| |
| type objectWalker struct { |
| path Path |
| value value.Value |
| |
| do func(Path) |
| } |
| |
| func (w *objectWalker) walk() { |
| switch { |
| case w.value.Null: |
| case w.value.FloatValue != nil: |
| case w.value.IntValue != nil: |
| case w.value.StringValue != nil: |
| case w.value.BooleanValue != nil: |
| // All leaf fields handled the same way (after the switch |
| // statement). |
| |
| // Descend |
| case w.value.ListValue != nil: |
| // If the list were atomic, we'd break here, but we don't have |
| // a schema, so we can't tell. |
| |
| for i, child := range w.value.ListValue.Items { |
| w2 := *w |
| w2.path = append(w.path, GuessBestListPathElement(i, child)) |
| w2.value = child |
| w2.walk() |
| } |
| return |
| case w.value.MapValue != nil: |
| // If the map/struct were atomic, we'd break here, but we don't |
| // have a schema, so we can't tell. |
| |
| for i := range w.value.MapValue.Items { |
| child := w.value.MapValue.Items[i] |
| w2 := *w |
| w2.path = append(w.path, PathElement{FieldName: &child.Name}) |
| w2.value = child.Value |
| w2.walk() |
| } |
| return |
| } |
| |
| // Leaf fields get added to the set. |
| if len(w.path) > 0 { |
| w.do(w.path) |
| } |
| } |
| |
| // AssociativeListCandidateFieldNames lists the field names which are |
| // considered keys if found in a list element. |
| var AssociativeListCandidateFieldNames = []string{ |
| "key", |
| "id", |
| "name", |
| } |
| |
| // GuessBestListPathElement guesses whether item is an associative list |
| // element, which should be referenced by key(s), or if it is not and therefore |
| // referencing by index is acceptable. Currently this is done by checking |
| // whether item has any of the fields listed in |
| // AssociativeListCandidateFieldNames which have scalar values. |
| func GuessBestListPathElement(index int, item value.Value) PathElement { |
| if item.MapValue == nil { |
| // Non map items could be parts of sets or regular "atomic" |
| // lists. We won't try to guess whether something should be a |
| // set or not. |
| return PathElement{Index: &index} |
| } |
| |
| var keys []value.Field |
| for _, name := range AssociativeListCandidateFieldNames { |
| f, ok := item.MapValue.Get(name) |
| if !ok { |
| continue |
| } |
| // only accept primitive/scalar types as keys. |
| if f.Value.Null || f.Value.MapValue != nil || f.Value.ListValue != nil { |
| continue |
| } |
| keys = append(keys, *f) |
| } |
| if len(keys) > 0 { |
| return PathElement{Key: keys} |
| } |
| return PathElement{Index: &index} |
| } |