320 lines
8.3 KiB
Go
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)
|
|
}
|
|
}
|