istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/tpath/struct.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /* 16 struct.go contains functions for traversing and modifying trees of Go structs. 17 */ 18 package tpath 19 20 import ( 21 "fmt" 22 "reflect" 23 "strconv" 24 25 "google.golang.org/protobuf/types/known/structpb" 26 27 "istio.io/istio/operator/pkg/util" 28 ) 29 30 // GetFromStructPath returns the value at path from the given node, or false if the path does not exist. 31 func GetFromStructPath(node any, path string) (any, bool, error) { 32 return getFromStructPath(node, util.PathFromString(path)) 33 } 34 35 // getFromStructPath is the internal implementation of GetFromStructPath which recurses through a tree of Go structs 36 // given a path. It terminates when the end of the path is reached or a path element does not exist. 37 func getFromStructPath(node any, path util.Path) (any, bool, error) { 38 scope.Debugf("getFromStructPath path=%s, node(%T)", path, node) 39 if len(path) == 0 { 40 scope.Debugf("getFromStructPath returning node(%T)%v", node, node) 41 return node, !util.IsValueNil(node), nil 42 } 43 // For protobuf types, switch them out with standard types; otherwise we will traverse protobuf internals rather 44 // than the standard representation 45 if v, ok := node.(*structpb.Struct); ok { 46 node = v.AsMap() 47 } 48 if v, ok := node.(*structpb.Value); ok { 49 node = v.AsInterface() 50 } 51 val := reflect.ValueOf(node) 52 kind := reflect.TypeOf(node).Kind() 53 var structElems reflect.Value 54 55 switch kind { 56 case reflect.Map: 57 if path[0] == "" { 58 return nil, false, fmt.Errorf("getFromStructPath path %s, empty map key value", path) 59 } 60 mapVal := val.MapIndex(reflect.ValueOf(path[0])) 61 if !mapVal.IsValid() { 62 return nil, false, fmt.Errorf("getFromStructPath path %s, path does not exist", path) 63 } 64 return getFromStructPath(mapVal.Interface(), path[1:]) 65 case reflect.Slice: 66 idx, err := strconv.Atoi(path[0]) 67 if err != nil { 68 return nil, false, fmt.Errorf("getFromStructPath path %s, expected index number, got %s", path, path[0]) 69 } 70 return getFromStructPath(val.Index(idx).Interface(), path[1:]) 71 case reflect.Ptr: 72 structElems = reflect.ValueOf(node).Elem() 73 if !util.IsStruct(structElems) { 74 return nil, false, fmt.Errorf("getFromStructPath path %s, expected struct ptr, got %T", path, node) 75 } 76 default: 77 return nil, false, fmt.Errorf("getFromStructPath path %s, unsupported type %T", path, node) 78 } 79 80 if util.IsNilOrInvalidValue(structElems) { 81 return nil, false, nil 82 } 83 84 for i := 0; i < structElems.NumField(); i++ { 85 fieldName := structElems.Type().Field(i).Name 86 87 if fieldName != path[0] { 88 continue 89 } 90 91 fv := structElems.Field(i) 92 return getFromStructPath(fv.Interface(), path[1:]) 93 } 94 95 return nil, false, nil 96 } 97 98 // SetFromPath sets out with the value at path from node. out is not set if the path doesn't exist or the value is nil. 99 // All intermediate along path must be type struct ptr. Out must be either a struct ptr or map ptr. 100 // TODO: move these out to a separate package (istio/istio#15494). 101 func SetFromPath(node any, path string, out any) (bool, error) { 102 val, found, err := GetFromStructPath(node, path) 103 if err != nil { 104 return false, err 105 } 106 if !found { 107 return false, nil 108 } 109 110 return true, Set(val, out) 111 } 112 113 // Set sets out with the value at path from node. out is not set if the path doesn't exist or the value is nil. 114 func Set(val, out any) error { 115 // Special case: map out type must be set through map ptr. 116 if util.IsMap(val) && util.IsMapPtr(out) { 117 reflect.ValueOf(out).Elem().Set(reflect.ValueOf(val)) 118 return nil 119 } 120 if util.IsSlice(val) && util.IsSlicePtr(out) { 121 reflect.ValueOf(out).Elem().Set(reflect.ValueOf(val)) 122 return nil 123 } 124 125 if reflect.TypeOf(val) != reflect.TypeOf(out) { 126 return fmt.Errorf("setFromPath from type %T != to type %T, %v", val, out, util.IsSlicePtr(out)) 127 } 128 129 if !reflect.ValueOf(out).CanSet() { 130 return fmt.Errorf("can't set %v(%T) to out type %T", val, val, out) 131 } 132 reflect.ValueOf(out).Set(reflect.ValueOf(val)) 133 return nil 134 }