kubernetes/vendor/sigs.k8s.io/structured-merge-diff/v3/value/valuereflect.go
2020-01-21 15:23:13 -08:00

320 lines
8.3 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 value
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"sync"
)
var reflectPool = sync.Pool{
New: func() interface{} {
return &valueReflect{}
},
}
// NewValueReflect creates a Value backed by an "interface{}" type,
// typically an structured object in Kubernetes world that is uses reflection to expose.
// The provided "interface{}" value must be a pointer so that the value can be modified via reflection.
// The provided "interface{}" may contain structs and types that are converted to Values
// by the jsonMarshaler interface.
func NewValueReflect(value interface{}) (Value, error) {
if value == nil {
return NewValueInterface(nil), nil
}
v := reflect.ValueOf(value)
if v.Kind() != reflect.Ptr {
// The root value to reflect on must be a pointer so that map.Set() and map.Delete() operations are possible.
return nil, fmt.Errorf("value provided to NewValueReflect must be a pointer")
}
return wrapValueReflect(nil, nil, v)
}
func wrapValueReflect(parentMap, parentMapKey *reflect.Value, value reflect.Value) (Value, error) {
// TODO: conversion of json.Marshaller interface types is expensive. This can be mostly optimized away by
// introducing conversion functions that do not require going through JSON and using those here.
if marshaler, ok := getMarshaler(value); ok {
return toUnstructured(marshaler, value)
}
value = dereference(value)
val := reflectPool.Get().(*valueReflect)
val.Value = value
val.ParentMap = parentMap
val.ParentMapKey = parentMapKey
return Value(val), nil
}
func mustWrapValueReflect(value reflect.Value) Value {
v, err := wrapValueReflect(nil, nil, value)
if err != nil {
panic(err)
}
return v
}
func mustWrapValueReflectMapItem(parentMap, parentMapKey *reflect.Value, value reflect.Value) Value {
v, err := wrapValueReflect(parentMap, parentMapKey, value)
if err != nil {
panic(err)
}
return v
}
func dereference(val reflect.Value) reflect.Value {
kind := val.Kind()
if (kind == reflect.Interface || kind == reflect.Ptr) && !safeIsNil(val) {
return val.Elem()
}
return val
}
type valueReflect struct {
ParentMap *reflect.Value
ParentMapKey *reflect.Value
Value reflect.Value
}
func (r valueReflect) IsMap() bool {
return r.isKind(reflect.Map, reflect.Struct)
}
func (r valueReflect) IsList() bool {
typ := r.Value.Type()
return typ.Kind() == reflect.Slice && !(typ.Elem().Kind() == reflect.Uint8)
}
func (r valueReflect) IsBool() bool {
return r.isKind(reflect.Bool)
}
func (r valueReflect) IsInt() bool {
// Uint64 deliberately excluded, see valueUnstructured.Int.
return r.isKind(reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8)
}
func (r valueReflect) IsFloat() bool {
return r.isKind(reflect.Float64, reflect.Float32)
}
func (r valueReflect) IsString() bool {
kind := r.Value.Kind()
if kind == reflect.String {
return true
}
if kind == reflect.Slice && r.Value.Type().Elem().Kind() == reflect.Uint8 {
return true
}
return false
}
func (r valueReflect) IsNull() bool {
return safeIsNil(r.Value)
}
func (r valueReflect) isKind(kinds ...reflect.Kind) bool {
kind := r.Value.Kind()
for _, k := range kinds {
if kind == k {
return true
}
}
return false
}
// TODO find a cleaner way to avoid panics from reflect.IsNil()
func safeIsNil(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return v.IsNil()
}
return false
}
func (r valueReflect) AsMap() Map {
val := r.Value
switch val.Kind() {
case reflect.Struct:
return structReflect{r}
case reflect.Map:
return mapReflect{r}
default:
panic("value is not a map or struct")
}
}
func (r *valueReflect) Recycle() {
reflectPool.Put(r)
}
func (r valueReflect) AsList() List {
if r.IsList() {
return listReflect{r.Value}
}
panic("value is not a list")
}
func (r valueReflect) AsBool() bool {
if r.IsBool() {
return r.Value.Bool()
}
panic("value is not a bool")
}
func (r valueReflect) AsInt() int64 {
if r.isKind(reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8) {
return r.Value.Int()
}
if r.isKind(reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8) {
return int64(r.Value.Uint())
}
panic("value is not an int")
}
func (r valueReflect) AsFloat() float64 {
if r.IsFloat() {
return r.Value.Float()
}
panic("value is not a float")
}
func (r valueReflect) AsString() string {
kind := r.Value.Kind()
if kind == reflect.String {
return r.Value.String()
}
if kind == reflect.Slice && r.Value.Type().Elem().Kind() == reflect.Uint8 {
return base64.StdEncoding.EncodeToString(r.Value.Bytes())
}
panic("value is not a string")
}
func (r valueReflect) Unstructured() interface{} {
val := r.Value
switch {
case r.IsNull():
return nil
case val.Kind() == reflect.Struct:
return structReflect{r}.Unstructured()
case val.Kind() == reflect.Map:
return mapReflect{r}.Unstructured()
case r.IsList():
return listReflect{Value: r.Value}.Unstructured()
case r.IsString():
return r.AsString()
case r.IsInt():
return r.AsInt()
case r.IsBool():
return r.AsBool()
case r.IsFloat():
return r.AsFloat()
default:
panic(fmt.Sprintf("value of type %s is not a supported by value reflector", val.Type()))
}
}
// The below getMarshaler and toUnstructured functions are based on
// https://github.com/kubernetes/kubernetes/blob/40df9f82d0572a123f5ad13f48312978a2ff5877/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L509
// and should somehow be consolidated with it
var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem()
func getMarshaler(v reflect.Value) (json.Marshaler, bool) {
// Check value receivers if v is not a pointer and pointer receivers if v is a pointer
if v.Type().Implements(marshalerType) {
return v.Interface().(json.Marshaler), true
}
// Check pointer receivers if v is not a pointer
if v.Kind() != reflect.Ptr && v.CanAddr() {
v = v.Addr()
if v.Type().Implements(marshalerType) {
return v.Interface().(json.Marshaler), true
}
}
return nil, false
}
var (
nullBytes = []byte("null")
trueBytes = []byte("true")
falseBytes = []byte("false")
)
func toUnstructured(marshaler json.Marshaler, sv reflect.Value) (Value, error) {
data, err := marshaler.MarshalJSON()
if err != nil {
return nil, err
}
switch {
case len(data) == 0:
return nil, fmt.Errorf("error decoding from json: empty value")
case bytes.Equal(data, nullBytes):
// We're done - we don't need to store anything.
return NewValueInterface(nil), nil
case bytes.Equal(data, trueBytes):
return NewValueInterface(true), nil
case bytes.Equal(data, falseBytes):
return NewValueInterface(false), nil
case data[0] == '"':
var result string
err := json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("error decoding string from json: %v", err)
}
return NewValueInterface(result), nil
case data[0] == '{':
result := make(map[string]interface{})
err := json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("error decoding object from json: %v", err)
}
return NewValueInterface(result), nil
case data[0] == '[':
result := make([]interface{}, 0)
err := json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("error decoding array from json: %v", err)
}
return NewValueInterface(result), nil
default:
var (
resultInt int64
resultFloat float64
err error
)
if err = json.Unmarshal(data, &resultInt); err == nil {
return NewValueInterface(resultInt), nil
}
if err = json.Unmarshal(data, &resultFloat); err == nil {
return NewValueInterface(resultFloat), nil
}
return nil, fmt.Errorf("error decoding number from json: %v", err)
}
}