| /* |
| 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)) |
| } |
| |
| // GetUsing 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 = 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 { |
| buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index)) |
| 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() |
| } |