2022-07-07 20:11:50 +00:00
/ *
Copyright 2020 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/json"
"fmt"
"reflect"
"sort"
"sync"
"sync/atomic"
)
// UnstructuredConverter defines how a type can be converted directly to unstructured.
// Types that implement json.Marshaler may also optionally implement this interface to provide a more
// direct and more efficient conversion. All types that choose to implement this interface must still
// implement this same conversion via json.Marshaler.
type UnstructuredConverter interface {
json . Marshaler // require that json.Marshaler is implemented
// ToUnstructured returns the unstructured representation.
ToUnstructured ( ) interface { }
}
// TypeReflectCacheEntry keeps data gathered using reflection about how a type is converted to/from unstructured.
type TypeReflectCacheEntry struct {
isJsonMarshaler bool
ptrIsJsonMarshaler bool
isJsonUnmarshaler bool
ptrIsJsonUnmarshaler bool
isStringConvertable bool
ptrIsStringConvertable bool
structFields map [ string ] * FieldCacheEntry
orderedStructFields [ ] * FieldCacheEntry
}
// FieldCacheEntry keeps data gathered using reflection about how the field of a struct is converted to/from
// unstructured.
type FieldCacheEntry struct {
// JsonName returns the name of the field according to the json tags on the struct field.
JsonName string
// isOmitEmpty is true if the field has the json 'omitempty' tag.
isOmitEmpty bool
// fieldPath is a list of field indices (see FieldByIndex) to lookup the value of
// a field in a reflect.Value struct. The field indices in the list form a path used
// to traverse through intermediary 'inline' fields.
fieldPath [ ] [ ] int
fieldType reflect . Type
TypeEntry * TypeReflectCacheEntry
}
func ( f * FieldCacheEntry ) CanOmit ( fieldVal reflect . Value ) bool {
return f . isOmitEmpty && ( safeIsNil ( fieldVal ) || isZero ( fieldVal ) )
}
// GetFrom returns the field identified by this FieldCacheEntry from the provided struct.
func ( f * FieldCacheEntry ) GetFrom ( structVal reflect . Value ) reflect . Value {
// field might be nested within 'inline' structs
for _ , elem := range f . fieldPath {
structVal = dereference ( structVal ) . FieldByIndex ( elem )
}
return structVal
}
var marshalerType = reflect . TypeOf ( new ( json . Marshaler ) ) . Elem ( )
var unmarshalerType = reflect . TypeOf ( new ( json . Unmarshaler ) ) . Elem ( )
var unstructuredConvertableType = reflect . TypeOf ( new ( UnstructuredConverter ) ) . Elem ( )
var defaultReflectCache = newReflectCache ( )
// TypeReflectEntryOf returns the TypeReflectCacheEntry of the provided reflect.Type.
func TypeReflectEntryOf ( t reflect . Type ) * TypeReflectCacheEntry {
cm := defaultReflectCache . get ( )
if record , ok := cm [ t ] ; ok {
return record
}
updates := reflectCacheMap { }
result := typeReflectEntryOf ( cm , t , updates )
if len ( updates ) > 0 {
defaultReflectCache . update ( updates )
}
return result
}
// TypeReflectEntryOf returns all updates needed to add provided reflect.Type, and the types its fields transitively
// depend on, to the cache.
func typeReflectEntryOf ( cm reflectCacheMap , t reflect . Type , updates reflectCacheMap ) * TypeReflectCacheEntry {
if record , ok := cm [ t ] ; ok {
return record
}
if record , ok := updates [ t ] ; ok {
return record
}
typeEntry := & TypeReflectCacheEntry {
isJsonMarshaler : t . Implements ( marshalerType ) ,
ptrIsJsonMarshaler : reflect . PtrTo ( t ) . Implements ( marshalerType ) ,
isJsonUnmarshaler : reflect . PtrTo ( t ) . Implements ( unmarshalerType ) ,
isStringConvertable : t . Implements ( unstructuredConvertableType ) ,
ptrIsStringConvertable : reflect . PtrTo ( t ) . Implements ( unstructuredConvertableType ) ,
}
if t . Kind ( ) == reflect . Struct {
fieldEntries := map [ string ] * FieldCacheEntry { }
buildStructCacheEntry ( t , fieldEntries , nil )
typeEntry . structFields = fieldEntries
sortedByJsonName := make ( [ ] * FieldCacheEntry , len ( fieldEntries ) )
i := 0
for _ , entry := range fieldEntries {
sortedByJsonName [ i ] = entry
i ++
}
sort . Slice ( sortedByJsonName , func ( i , j int ) bool {
return sortedByJsonName [ i ] . JsonName < sortedByJsonName [ j ] . JsonName
} )
typeEntry . orderedStructFields = sortedByJsonName
}
// cyclic type references are allowed, so we must add the typeEntry to the updates map before resolving
// the field.typeEntry references, or creating them if they are not already in the cache
updates [ t ] = typeEntry
for _ , field := range typeEntry . structFields {
if field . TypeEntry == nil {
field . TypeEntry = typeReflectEntryOf ( cm , field . fieldType , updates )
}
}
return typeEntry
}
func buildStructCacheEntry ( t reflect . Type , infos map [ string ] * FieldCacheEntry , fieldPath [ ] [ ] int ) {
for i := 0 ; i < t . NumField ( ) ; i ++ {
field := t . Field ( i )
jsonName , omit , isInline , isOmitempty := lookupJsonTags ( field )
if omit {
continue
}
if isInline {
e := field . Type
if field . Type . Kind ( ) == reflect . Ptr {
e = field . Type . Elem ( )
}
2024-02-24 00:34:55 +00:00
if e . Kind ( ) == reflect . Struct {
buildStructCacheEntry ( e , infos , append ( fieldPath , field . Index ) )
}
2022-07-07 20:11:50 +00:00
continue
}
info := & FieldCacheEntry { JsonName : jsonName , isOmitEmpty : isOmitempty , fieldPath : append ( fieldPath , field . Index ) , fieldType : field . Type }
infos [ jsonName ] = info
}
}
// Fields returns a map of JSON field name to FieldCacheEntry for structs, or nil for non-structs.
func ( e TypeReflectCacheEntry ) Fields ( ) map [ string ] * FieldCacheEntry {
return e . structFields
}
// Fields returns a map of JSON field name to FieldCacheEntry for structs, or nil for non-structs.
func ( e TypeReflectCacheEntry ) OrderedFields ( ) [ ] * FieldCacheEntry {
return e . orderedStructFields
}
// CanConvertToUnstructured returns true if this TypeReflectCacheEntry can convert values of its type to unstructured.
func ( e TypeReflectCacheEntry ) CanConvertToUnstructured ( ) bool {
return e . isJsonMarshaler || e . ptrIsJsonMarshaler || e . isStringConvertable || e . ptrIsStringConvertable
}
// ToUnstructured converts the provided value to unstructured and returns it.
func ( e TypeReflectCacheEntry ) ToUnstructured ( sv reflect . Value ) ( interface { } , error ) {
// This is based on https://github.com/kubernetes/kubernetes/blob/82c9e5c814eb7acc6cc0a090c057294d0667ad66/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L505
// and is intended to replace it.
// Check if the object has a custom string converter and use it if available, since it is much more efficient
// than round tripping through json.
if converter , ok := e . getUnstructuredConverter ( sv ) ; ok {
return converter . ToUnstructured ( ) , nil
}
// Check if the object has a custom JSON marshaller/unmarshaller.
if marshaler , ok := e . getJsonMarshaler ( sv ) ; ok {
if sv . Kind ( ) == reflect . Ptr && sv . IsNil ( ) {
// We're done - we don't need to store anything.
return nil , nil
}
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 nil , nil
case bytes . Equal ( data , trueBytes ) :
return true , nil
case bytes . Equal ( data , falseBytes ) :
return false , nil
case data [ 0 ] == '"' :
var result string
err := unmarshal ( data , & result )
if err != nil {
return nil , fmt . Errorf ( "error decoding string from json: %v" , err )
}
return result , nil
case data [ 0 ] == '{' :
result := make ( map [ string ] interface { } )
err := unmarshal ( data , & result )
if err != nil {
return nil , fmt . Errorf ( "error decoding object from json: %v" , err )
}
return result , nil
case data [ 0 ] == '[' :
result := make ( [ ] interface { } , 0 )
err := unmarshal ( data , & result )
if err != nil {
return nil , fmt . Errorf ( "error decoding array from json: %v" , err )
}
return result , nil
default :
var (
resultInt int64
resultFloat float64
err error
)
if err = unmarshal ( data , & resultInt ) ; err == nil {
return resultInt , nil
} else if err = unmarshal ( data , & resultFloat ) ; err == nil {
return resultFloat , nil
} else {
return nil , fmt . Errorf ( "error decoding number from json: %v" , err )
}
}
}
return nil , fmt . Errorf ( "provided type cannot be converted: %v" , sv . Type ( ) )
}
// CanConvertFromUnstructured returns true if this TypeReflectCacheEntry can convert objects of the type from unstructured.
func ( e TypeReflectCacheEntry ) CanConvertFromUnstructured ( ) bool {
return e . isJsonUnmarshaler
}
// FromUnstructured converts the provided source value from unstructured into the provided destination value.
func ( e TypeReflectCacheEntry ) FromUnstructured ( sv , dv reflect . Value ) error {
// TODO: this could be made much more efficient using direct conversions like
// UnstructuredConverter.ToUnstructured provides.
st := dv . Type ( )
data , err := json . Marshal ( sv . Interface ( ) )
if err != nil {
return fmt . Errorf ( "error encoding %s to json: %v" , st . String ( ) , err )
}
if unmarshaler , ok := e . getJsonUnmarshaler ( dv ) ; ok {
return unmarshaler . UnmarshalJSON ( data )
}
return fmt . Errorf ( "unable to unmarshal %v into %v" , sv . Type ( ) , dv . Type ( ) )
}
var (
nullBytes = [ ] byte ( "null" )
trueBytes = [ ] byte ( "true" )
falseBytes = [ ] byte ( "false" )
)
func ( e TypeReflectCacheEntry ) getJsonMarshaler ( v reflect . Value ) ( json . Marshaler , bool ) {
if e . isJsonMarshaler {
return v . Interface ( ) . ( json . Marshaler ) , true
}
if e . ptrIsJsonMarshaler {
// Check pointer receivers if v is not a pointer
if v . Kind ( ) != reflect . Ptr && v . CanAddr ( ) {
v = v . Addr ( )
return v . Interface ( ) . ( json . Marshaler ) , true
}
}
return nil , false
}
func ( e TypeReflectCacheEntry ) getJsonUnmarshaler ( v reflect . Value ) ( json . Unmarshaler , bool ) {
if ! e . isJsonUnmarshaler {
return nil , false
}
return v . Addr ( ) . Interface ( ) . ( json . Unmarshaler ) , true
}
func ( e TypeReflectCacheEntry ) getUnstructuredConverter ( v reflect . Value ) ( UnstructuredConverter , bool ) {
if e . isStringConvertable {
return v . Interface ( ) . ( UnstructuredConverter ) , true
}
if e . ptrIsStringConvertable {
// Check pointer receivers if v is not a pointer
if v . CanAddr ( ) {
v = v . Addr ( )
return v . Interface ( ) . ( UnstructuredConverter ) , true
}
}
return nil , false
}
type typeReflectCache struct {
// use an atomic and copy-on-write since there are a fixed (typically very small) number of structs compiled into any
// go program using this cache
value atomic . Value
// mu is held by writers when performing load/modify/store operations on the cache, readers do not need to hold a
// read-lock since the atomic value is always read-only
mu sync . Mutex
}
func newReflectCache ( ) * typeReflectCache {
cache := & typeReflectCache { }
cache . value . Store ( make ( reflectCacheMap ) )
return cache
}
type reflectCacheMap map [ reflect . Type ] * TypeReflectCacheEntry
// get returns the reflectCacheMap.
func ( c * typeReflectCache ) get ( ) reflectCacheMap {
return c . value . Load ( ) . ( reflectCacheMap )
}
// update merges the provided updates into the cache.
func ( c * typeReflectCache ) update ( updates reflectCacheMap ) {
c . mu . Lock ( )
defer c . mu . Unlock ( )
currentCacheMap := c . value . Load ( ) . ( reflectCacheMap )
hasNewEntries := false
for t := range updates {
if _ , ok := currentCacheMap [ t ] ; ! ok {
hasNewEntries = true
break
}
}
if ! hasNewEntries {
// Bail if the updates have been set while waiting for lock acquisition.
// This is safe since setting entries is idempotent.
return
}
newCacheMap := make ( reflectCacheMap , len ( currentCacheMap ) + len ( updates ) )
for k , v := range currentCacheMap {
newCacheMap [ k ] = v
}
for t , update := range updates {
newCacheMap [ t ] = update
}
c . value . Store ( newCacheMap )
}
// Below json Unmarshal is fromk8s.io/apimachinery/pkg/util/json
// to handle number conversions as expected by Kubernetes
// limit recursive depth to prevent stack overflow errors
const maxDepth = 10000
// unmarshal unmarshals the given data
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
func unmarshal ( data [ ] byte , v interface { } ) error {
switch v := v . ( type ) {
case * map [ string ] interface { } :
// Build a decoder from the given data
decoder := json . NewDecoder ( bytes . NewBuffer ( data ) )
// Preserve numbers, rather than casting to float64 automatically
decoder . UseNumber ( )
// Run the decode
if err := decoder . Decode ( v ) ; err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertMapNumbers ( * v , 0 )
case * [ ] interface { } :
// Build a decoder from the given data
decoder := json . NewDecoder ( bytes . NewBuffer ( data ) )
// Preserve numbers, rather than casting to float64 automatically
decoder . UseNumber ( )
// Run the decode
if err := decoder . Decode ( v ) ; err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertSliceNumbers ( * v , 0 )
default :
return json . Unmarshal ( data , v )
}
}
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
// values which are map[string]interface{} or []interface{} are recursively visited
func convertMapNumbers ( m map [ string ] interface { } , depth int ) error {
if depth > maxDepth {
return fmt . Errorf ( "exceeded max depth of %d" , maxDepth )
}
var err error
for k , v := range m {
switch v := v . ( type ) {
case json . Number :
m [ k ] , err = convertNumber ( v )
case map [ string ] interface { } :
err = convertMapNumbers ( v , depth + 1 )
case [ ] interface { } :
err = convertSliceNumbers ( v , depth + 1 )
}
if err != nil {
return err
}
}
return nil
}
// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
// values which are map[string]interface{} or []interface{} are recursively visited
func convertSliceNumbers ( s [ ] interface { } , depth int ) error {
if depth > maxDepth {
return fmt . Errorf ( "exceeded max depth of %d" , maxDepth )
}
var err error
for i , v := range s {
switch v := v . ( type ) {
case json . Number :
s [ i ] , err = convertNumber ( v )
case map [ string ] interface { } :
err = convertMapNumbers ( v , depth + 1 )
case [ ] interface { } :
err = convertSliceNumbers ( v , depth + 1 )
}
if err != nil {
return err
}
}
return nil
}
// convertNumber converts a json.Number to an int64 or float64, or returns an error
func convertNumber ( n json . Number ) ( interface { } , error ) {
// Attempt to convert to an int64 first
if i , err := n . Int64 ( ) ; err == nil {
return i , nil
}
// Return a float64 (default json.Decode() behavior)
// An overflow will return an error
return n . Float64 ( )
}