/* 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) } }