123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- /*
- 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/v4/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,
- allocator: value.NewFreelistAllocator(),
- do: func(p Path) { s.Insert(p) },
- }
- w.walk()
- return s
- }
- type objectWalker struct {
- path Path
- value value.Value
- allocator value.Allocator
- do func(Path)
- }
- func (w *objectWalker) walk() {
- switch {
- case w.value.IsNull():
- case w.value.IsFloat():
- case w.value.IsInt():
- case w.value.IsString():
- case w.value.IsBool():
- // All leaf fields handled the same way (after the switch
- // statement).
- // Descend
- case w.value.IsList():
- // If the list were atomic, we'd break here, but we don't have
- // a schema, so we can't tell.
- l := w.value.AsListUsing(w.allocator)
- defer w.allocator.Free(l)
- iter := l.RangeUsing(w.allocator)
- defer w.allocator.Free(iter)
- for iter.Next() {
- i, value := iter.Item()
- w2 := *w
- w2.path = append(w.path, w.GuessBestListPathElement(i, value))
- w2.value = value
- w2.walk()
- }
- return
- case w.value.IsMap():
- // If the map/struct were atomic, we'd break here, but we don't
- // have a schema, so we can't tell.
- m := w.value.AsMapUsing(w.allocator)
- defer w.allocator.Free(m)
- m.IterateUsing(w.allocator, func(k string, val value.Value) bool {
- w2 := *w
- w2.path = append(w.path, PathElement{FieldName: &k})
- w2.value = val
- w2.walk()
- return true
- })
- 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 (w *objectWalker) GuessBestListPathElement(index int, item value.Value) PathElement {
- if !item.IsMap() {
- // 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}
- }
- m := item.AsMapUsing(w.allocator)
- defer w.allocator.Free(m)
- var keys value.FieldList
- for _, name := range AssociativeListCandidateFieldNames {
- f, ok := m.Get(name)
- if !ok {
- continue
- }
- // only accept primitive/scalar types as keys.
- if f.IsNull() || f.IsMap() || f.IsList() {
- continue
- }
- keys = append(keys, value.Field{Name: name, Value: f})
- }
- if len(keys) > 0 {
- keys.Sort()
- return PathElement{Key: &keys}
- }
- return PathElement{Index: &index}
- }
|