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  }