kops/util/pkg/reflectutils/walk.go

272 lines
7.4 KiB
Go

/*
Copyright 2019 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 reflectutils
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"k8s.io/klog/v2"
)
var SkipReflection = errors.New("skip this value")
type MethodNotFoundError struct {
Name string
Target interface{}
}
func (e *MethodNotFoundError) Error() string {
return fmt.Sprintf("method %s not found on %T", e.Name, e.Target)
}
func IsMethodNotFound(err error) bool {
_, ok := err.(*MethodNotFoundError)
return ok
}
// JSONMergeStruct merges src into dest
// It uses a JSON marshal & unmarshal, so only fields that are JSON-visible will be copied
// If both source and destination has a value for a field, source takes presedence
func JSONMergeStruct(dest, src interface{}) {
// Not the most efficient approach, but simple & relatively well defined
j, err := json.Marshal(src)
if err != nil {
klog.Fatalf("error marshaling config: %v", err)
}
err = json.Unmarshal(j, dest)
if err != nil {
klog.Fatalf("error unmarshaling config: %v", err)
}
}
// InvokeMethod calls the specified method by reflection
func InvokeMethod(target interface{}, name string, args ...interface{}) ([]reflect.Value, error) {
v := reflect.ValueOf(target)
method, found := v.Type().MethodByName(name)
if !found {
return nil, &MethodNotFoundError{
Name: name,
Target: target,
}
}
var argValues []reflect.Value
for _, a := range args {
argValues = append(argValues, reflect.ValueOf(a))
}
klog.V(12).Infof("Calling method %s on %T", method.Name, target)
m := v.MethodByName(method.Name)
rv := m.Call(argValues)
return rv, nil
}
func BuildTypeName(t reflect.Type) string {
switch t.Kind() {
case reflect.Ptr:
return "*" + BuildTypeName(t.Elem())
case reflect.Slice:
return "[]" + BuildTypeName(t.Elem())
case reflect.Struct, reflect.Interface:
return t.Name()
case reflect.String, reflect.Bool, reflect.Int64, reflect.Uint8:
return t.Name()
case reflect.Map:
return "map[" + BuildTypeName(t.Key()) + "]" + BuildTypeName(t.Elem())
default:
klog.Errorf("cannot find type name for: %v, assuming %s", t, t.Name())
return t.Name()
}
}
type visitorFunc func(path *FieldPath, field *reflect.StructField, v reflect.Value) error
type ReflectOptions struct {
// JSONNames means the elements of the FieldPath will be the JSON field names (instead of the go field names)
JSONNames bool
// DeprecatedDoubleVisit activates the compatibility mode of the walker, where we end up visiting every struct field twice.
// Ideally we can replace these instances over time.
DeprecatedDoubleVisit bool
}
// ReflectRecursive calls visitor with v and every recursive sub-value, skipping subtrees if SkipReflection is returned
func ReflectRecursive(v reflect.Value, visitor visitorFunc, options *ReflectOptions) error {
emptyFieldPath := FieldPath{}
return reflectRecursive(&emptyFieldPath, v, visitor, options)
}
func reflectRecursive(path *FieldPath, v reflect.Value, visitor visitorFunc, options *ReflectOptions) error {
vType := v.Type()
err := visitor(path, nil, v)
if err != nil {
if err == SkipReflection {
return nil
}
return err
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
structField := vType.Field(i)
if structField.PkgPath != "" {
// Field not exported
continue
}
f := v.Field(i)
fieldName := structField.Name
if options.JSONNames {
jsonTag, ok := structField.Tag.Lookup("json")
if ok {
v := strings.Split(jsonTag, ",")[0]
if v != "" {
fieldName = v
}
}
}
childPath := path.Extend(FieldPathElement{Type: FieldPathElementTypeField, token: fieldName})
var err error
if options.DeprecatedDoubleVisit {
// The legacy behaviour here involves visiting fields twice; only as the StructField, once as the value itself
err = visitor(childPath, &structField, f)
if err != nil && err != SkipReflection {
return err
}
}
if err == nil {
err = reflectRecursive(childPath, f, visitor, options)
if err != nil {
return err
}
}
}
case reflect.Map:
keys := v.MapKeys()
for _, key := range keys {
mv := v.MapIndex(key)
childPath := path.Extend(FieldPathElement{Type: FieldPathElementTypeMapKey, token: fmt.Sprintf("%s", key.Interface())})
err := visitor(childPath, nil, mv)
if err != nil && err != SkipReflection {
return err
}
if err == nil {
err = reflectRecursive(childPath, mv, visitor, options)
if err != nil {
return err
}
}
}
case reflect.Array, reflect.Slice:
len := v.Len()
for i := 0; i < len; i++ {
av := v.Index(i)
childPath := path.Extend(FieldPathElement{Type: FieldPathElementTypeArrayIndex, number: i})
err := visitor(childPath, nil, av)
if err != nil && err != SkipReflection {
return err
}
if err == nil {
err = reflectRecursive(childPath, av, visitor, options)
if err != nil {
return err
}
}
}
case reflect.Ptr, reflect.Interface:
if !v.IsNil() {
e := v.Elem()
err = reflectRecursive(path, e, visitor, options)
if err != nil {
return err
}
}
}
return nil
}
// IsPrimitiveValue returns true if passed a value of primitive type: int, bool, etc
// Note that string (like []byte) is not treated as a primitive type
func IsPrimitiveValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
return true
// The less-obvious cases!
case reflect.String, reflect.Slice, reflect.Array:
return false
case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Struct, reflect.UnsafePointer:
return false
default:
klog.Fatalf("Unhandled kind: %v", v.Kind())
return false
}
}
// FormatValue returns a string representing the value
func FormatValue(value interface{}) string {
// Based on code in k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go
valueType := reflect.TypeOf(value)
if value == nil || valueType == nil {
value = "null"
} else if valueType.Kind() == reflect.Ptr {
if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() {
value = "null"
} else {
value = reflectValue.Elem().Interface()
}
}
switch t := value.(type) {
case int64, int32, float64, float32, bool:
// use simple printer for simple types
return fmt.Sprintf("%v", value)
case string:
return fmt.Sprintf("%q", t)
case fmt.Stringer:
// anything that defines String() is better than raw struct
return t.String()
default:
// fallback to raw struct
// TODO: internal types have panic guards against json.Marshaling to prevent
// accidental use of internal types in external serialized form. For now, use
// %#v, although it would be better to show a more expressive output in the future
return fmt.Sprintf("%#v", value)
}
}