blob: 49e6dd1690e0a029003d72f12f2c13378c17aa8c [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2020 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package value
18
19import (
20 "bytes"
21 "encoding/json"
22 "fmt"
23 "reflect"
24 "sort"
25 "sync"
26 "sync/atomic"
27)
28
29// UnstructuredConverter defines how a type can be converted directly to unstructured.
30// Types that implement json.Marshaler may also optionally implement this interface to provide a more
31// direct and more efficient conversion. All types that choose to implement this interface must still
32// implement this same conversion via json.Marshaler.
33type UnstructuredConverter interface {
34 json.Marshaler // require that json.Marshaler is implemented
35
36 // ToUnstructured returns the unstructured representation.
37 ToUnstructured() interface{}
38}
39
40// TypeReflectCacheEntry keeps data gathered using reflection about how a type is converted to/from unstructured.
41type TypeReflectCacheEntry struct {
42 isJsonMarshaler bool
43 ptrIsJsonMarshaler bool
44 isJsonUnmarshaler bool
45 ptrIsJsonUnmarshaler bool
46 isStringConvertable bool
47 ptrIsStringConvertable bool
48
49 structFields map[string]*FieldCacheEntry
50 orderedStructFields []*FieldCacheEntry
51}
52
53// FieldCacheEntry keeps data gathered using reflection about how the field of a struct is converted to/from
54// unstructured.
55type FieldCacheEntry struct {
56 // JsonName returns the name of the field according to the json tags on the struct field.
57 JsonName string
58 // isOmitEmpty is true if the field has the json 'omitempty' tag.
59 isOmitEmpty bool
60 // fieldPath is a list of field indices (see FieldByIndex) to lookup the value of
61 // a field in a reflect.Value struct. The field indices in the list form a path used
62 // to traverse through intermediary 'inline' fields.
63 fieldPath [][]int
64
65 fieldType reflect.Type
66 TypeEntry *TypeReflectCacheEntry
67}
68
69func (f *FieldCacheEntry) CanOmit(fieldVal reflect.Value) bool {
70 return f.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal))
71}
72
73// GetUsing returns the field identified by this FieldCacheEntry from the provided struct.
74func (f *FieldCacheEntry) GetFrom(structVal reflect.Value) reflect.Value {
75 // field might be nested within 'inline' structs
76 for _, elem := range f.fieldPath {
77 structVal = structVal.FieldByIndex(elem)
78 }
79 return structVal
80}
81
82var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem()
83var unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
84var unstructuredConvertableType = reflect.TypeOf(new(UnstructuredConverter)).Elem()
85var defaultReflectCache = newReflectCache()
86
87// TypeReflectEntryOf returns the TypeReflectCacheEntry of the provided reflect.Type.
88func TypeReflectEntryOf(t reflect.Type) *TypeReflectCacheEntry {
89 cm := defaultReflectCache.get()
90 if record, ok := cm[t]; ok {
91 return record
92 }
93 updates := reflectCacheMap{}
94 result := typeReflectEntryOf(cm, t, updates)
95 if len(updates) > 0 {
96 defaultReflectCache.update(updates)
97 }
98 return result
99}
100
101// TypeReflectEntryOf returns all updates needed to add provided reflect.Type, and the types its fields transitively
102// depend on, to the cache.
103func typeReflectEntryOf(cm reflectCacheMap, t reflect.Type, updates reflectCacheMap) *TypeReflectCacheEntry {
104 if record, ok := cm[t]; ok {
105 return record
106 }
107 if record, ok := updates[t]; ok {
108 return record
109 }
110 typeEntry := &TypeReflectCacheEntry{
111 isJsonMarshaler: t.Implements(marshalerType),
112 ptrIsJsonMarshaler: reflect.PtrTo(t).Implements(marshalerType),
113 isJsonUnmarshaler: reflect.PtrTo(t).Implements(unmarshalerType),
114 isStringConvertable: t.Implements(unstructuredConvertableType),
115 ptrIsStringConvertable: reflect.PtrTo(t).Implements(unstructuredConvertableType),
116 }
117 if t.Kind() == reflect.Struct {
118 fieldEntries := map[string]*FieldCacheEntry{}
119 buildStructCacheEntry(t, fieldEntries, nil)
120 typeEntry.structFields = fieldEntries
121 sortedByJsonName := make([]*FieldCacheEntry, len(fieldEntries))
122 i := 0
123 for _, entry := range fieldEntries {
124 sortedByJsonName[i] = entry
125 i++
126 }
127 sort.Slice(sortedByJsonName, func(i, j int) bool {
128 return sortedByJsonName[i].JsonName < sortedByJsonName[j].JsonName
129 })
130 typeEntry.orderedStructFields = sortedByJsonName
131 }
132
133 // cyclic type references are allowed, so we must add the typeEntry to the updates map before resolving
134 // the field.typeEntry references, or creating them if they are not already in the cache
135 updates[t] = typeEntry
136
137 for _, field := range typeEntry.structFields {
138 if field.TypeEntry == nil {
139 field.TypeEntry = typeReflectEntryOf(cm, field.fieldType, updates)
140 }
141 }
142 return typeEntry
143}
144
145func buildStructCacheEntry(t reflect.Type, infos map[string]*FieldCacheEntry, fieldPath [][]int) {
146 for i := 0; i < t.NumField(); i++ {
147 field := t.Field(i)
148 jsonName, omit, isInline, isOmitempty := lookupJsonTags(field)
149 if omit {
150 continue
151 }
152 if isInline {
153 buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index))
154 continue
155 }
156 info := &FieldCacheEntry{JsonName: jsonName, isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index), fieldType: field.Type}
157 infos[jsonName] = info
158 }
159}
160
161// Fields returns a map of JSON field name to FieldCacheEntry for structs, or nil for non-structs.
162func (e TypeReflectCacheEntry) Fields() map[string]*FieldCacheEntry {
163 return e.structFields
164}
165
166// Fields returns a map of JSON field name to FieldCacheEntry for structs, or nil for non-structs.
167func (e TypeReflectCacheEntry) OrderedFields() []*FieldCacheEntry {
168 return e.orderedStructFields
169}
170
171// CanConvertToUnstructured returns true if this TypeReflectCacheEntry can convert values of its type to unstructured.
172func (e TypeReflectCacheEntry) CanConvertToUnstructured() bool {
173 return e.isJsonMarshaler || e.ptrIsJsonMarshaler || e.isStringConvertable || e.ptrIsStringConvertable
174}
175
176// ToUnstructured converts the provided value to unstructured and returns it.
177func (e TypeReflectCacheEntry) ToUnstructured(sv reflect.Value) (interface{}, error) {
178 // This is based on https://github.com/kubernetes/kubernetes/blob/82c9e5c814eb7acc6cc0a090c057294d0667ad66/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L505
179 // and is intended to replace it.
180
181 // Check if the object has a custom string converter and use it if available, since it is much more efficient
182 // than round tripping through json.
183 if converter, ok := e.getUnstructuredConverter(sv); ok {
184 return converter.ToUnstructured(), nil
185 }
186 // Check if the object has a custom JSON marshaller/unmarshaller.
187 if marshaler, ok := e.getJsonMarshaler(sv); ok {
188 if sv.Kind() == reflect.Ptr && sv.IsNil() {
189 // We're done - we don't need to store anything.
190 return nil, nil
191 }
192
193 data, err := marshaler.MarshalJSON()
194 if err != nil {
195 return nil, err
196 }
197 switch {
198 case len(data) == 0:
199 return nil, fmt.Errorf("error decoding from json: empty value")
200
201 case bytes.Equal(data, nullBytes):
202 // We're done - we don't need to store anything.
203 return nil, nil
204
205 case bytes.Equal(data, trueBytes):
206 return true, nil
207
208 case bytes.Equal(data, falseBytes):
209 return false, nil
210
211 case data[0] == '"':
212 var result string
213 err := unmarshal(data, &result)
214 if err != nil {
215 return nil, fmt.Errorf("error decoding string from json: %v", err)
216 }
217 return result, nil
218
219 case data[0] == '{':
220 result := make(map[string]interface{})
221 err := unmarshal(data, &result)
222 if err != nil {
223 return nil, fmt.Errorf("error decoding object from json: %v", err)
224 }
225 return result, nil
226
227 case data[0] == '[':
228 result := make([]interface{}, 0)
229 err := unmarshal(data, &result)
230 if err != nil {
231 return nil, fmt.Errorf("error decoding array from json: %v", err)
232 }
233 return result, nil
234
235 default:
236 var (
237 resultInt int64
238 resultFloat float64
239 err error
240 )
241 if err = unmarshal(data, &resultInt); err == nil {
242 return resultInt, nil
243 } else if err = unmarshal(data, &resultFloat); err == nil {
244 return resultFloat, nil
245 } else {
246 return nil, fmt.Errorf("error decoding number from json: %v", err)
247 }
248 }
249 }
250
251 return nil, fmt.Errorf("provided type cannot be converted: %v", sv.Type())
252}
253
254// CanConvertFromUnstructured returns true if this TypeReflectCacheEntry can convert objects of the type from unstructured.
255func (e TypeReflectCacheEntry) CanConvertFromUnstructured() bool {
256 return e.isJsonUnmarshaler
257}
258
259// FromUnstructured converts the provided source value from unstructured into the provided destination value.
260func (e TypeReflectCacheEntry) FromUnstructured(sv, dv reflect.Value) error {
261 // TODO: this could be made much more efficient using direct conversions like
262 // UnstructuredConverter.ToUnstructured provides.
263 st := dv.Type()
264 data, err := json.Marshal(sv.Interface())
265 if err != nil {
266 return fmt.Errorf("error encoding %s to json: %v", st.String(), err)
267 }
268 if unmarshaler, ok := e.getJsonUnmarshaler(dv); ok {
269 return unmarshaler.UnmarshalJSON(data)
270 }
271 return fmt.Errorf("unable to unmarshal %v into %v", sv.Type(), dv.Type())
272}
273
274var (
275 nullBytes = []byte("null")
276 trueBytes = []byte("true")
277 falseBytes = []byte("false")
278)
279
280func (e TypeReflectCacheEntry) getJsonMarshaler(v reflect.Value) (json.Marshaler, bool) {
281 if e.isJsonMarshaler {
282 return v.Interface().(json.Marshaler), true
283 }
284 if e.ptrIsJsonMarshaler {
285 // Check pointer receivers if v is not a pointer
286 if v.Kind() != reflect.Ptr && v.CanAddr() {
287 v = v.Addr()
288 return v.Interface().(json.Marshaler), true
289 }
290 }
291 return nil, false
292}
293
294func (e TypeReflectCacheEntry) getJsonUnmarshaler(v reflect.Value) (json.Unmarshaler, bool) {
295 if !e.isJsonUnmarshaler {
296 return nil, false
297 }
298 return v.Addr().Interface().(json.Unmarshaler), true
299}
300
301func (e TypeReflectCacheEntry) getUnstructuredConverter(v reflect.Value) (UnstructuredConverter, bool) {
302 if e.isStringConvertable {
303 return v.Interface().(UnstructuredConverter), true
304 }
305 if e.ptrIsStringConvertable {
306 // Check pointer receivers if v is not a pointer
307 if v.CanAddr() {
308 v = v.Addr()
309 return v.Interface().(UnstructuredConverter), true
310 }
311 }
312 return nil, false
313}
314
315type typeReflectCache struct {
316 // use an atomic and copy-on-write since there are a fixed (typically very small) number of structs compiled into any
317 // go program using this cache
318 value atomic.Value
319 // mu is held by writers when performing load/modify/store operations on the cache, readers do not need to hold a
320 // read-lock since the atomic value is always read-only
321 mu sync.Mutex
322}
323
324func newReflectCache() *typeReflectCache {
325 cache := &typeReflectCache{}
326 cache.value.Store(make(reflectCacheMap))
327 return cache
328}
329
330type reflectCacheMap map[reflect.Type]*TypeReflectCacheEntry
331
332// get returns the reflectCacheMap.
333func (c *typeReflectCache) get() reflectCacheMap {
334 return c.value.Load().(reflectCacheMap)
335}
336
337// update merges the provided updates into the cache.
338func (c *typeReflectCache) update(updates reflectCacheMap) {
339 c.mu.Lock()
340 defer c.mu.Unlock()
341
342 currentCacheMap := c.value.Load().(reflectCacheMap)
343
344 hasNewEntries := false
345 for t := range updates {
346 if _, ok := currentCacheMap[t]; !ok {
347 hasNewEntries = true
348 break
349 }
350 }
351 if !hasNewEntries {
352 // Bail if the updates have been set while waiting for lock acquisition.
353 // This is safe since setting entries is idempotent.
354 return
355 }
356
357 newCacheMap := make(reflectCacheMap, len(currentCacheMap)+len(updates))
358 for k, v := range currentCacheMap {
359 newCacheMap[k] = v
360 }
361 for t, update := range updates {
362 newCacheMap[t] = update
363 }
364 c.value.Store(newCacheMap)
365}
366
367// Below json Unmarshal is fromk8s.io/apimachinery/pkg/util/json
368// to handle number conversions as expected by Kubernetes
369
370// limit recursive depth to prevent stack overflow errors
371const maxDepth = 10000
372
373// unmarshal unmarshals the given data
374// If v is a *map[string]interface{}, numbers are converted to int64 or float64
375func unmarshal(data []byte, v interface{}) error {
376 switch v := v.(type) {
377 case *map[string]interface{}:
378 // Build a decoder from the given data
379 decoder := json.NewDecoder(bytes.NewBuffer(data))
380 // Preserve numbers, rather than casting to float64 automatically
381 decoder.UseNumber()
382 // Run the decode
383 if err := decoder.Decode(v); err != nil {
384 return err
385 }
386 // If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
387 return convertMapNumbers(*v, 0)
388
389 case *[]interface{}:
390 // Build a decoder from the given data
391 decoder := json.NewDecoder(bytes.NewBuffer(data))
392 // Preserve numbers, rather than casting to float64 automatically
393 decoder.UseNumber()
394 // Run the decode
395 if err := decoder.Decode(v); err != nil {
396 return err
397 }
398 // If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
399 return convertSliceNumbers(*v, 0)
400
401 default:
402 return json.Unmarshal(data, v)
403 }
404}
405
406// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
407// values which are map[string]interface{} or []interface{} are recursively visited
408func convertMapNumbers(m map[string]interface{}, depth int) error {
409 if depth > maxDepth {
410 return fmt.Errorf("exceeded max depth of %d", maxDepth)
411 }
412
413 var err error
414 for k, v := range m {
415 switch v := v.(type) {
416 case json.Number:
417 m[k], err = convertNumber(v)
418 case map[string]interface{}:
419 err = convertMapNumbers(v, depth+1)
420 case []interface{}:
421 err = convertSliceNumbers(v, depth+1)
422 }
423 if err != nil {
424 return err
425 }
426 }
427 return nil
428}
429
430// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
431// values which are map[string]interface{} or []interface{} are recursively visited
432func convertSliceNumbers(s []interface{}, depth int) error {
433 if depth > maxDepth {
434 return fmt.Errorf("exceeded max depth of %d", maxDepth)
435 }
436
437 var err error
438 for i, v := range s {
439 switch v := v.(type) {
440 case json.Number:
441 s[i], err = convertNumber(v)
442 case map[string]interface{}:
443 err = convertMapNumbers(v, depth+1)
444 case []interface{}:
445 err = convertSliceNumbers(v, depth+1)
446 }
447 if err != nil {
448 return err
449 }
450 }
451 return nil
452}
453
454// convertNumber converts a json.Number to an int64 or float64, or returns an error
455func convertNumber(n json.Number) (interface{}, error) {
456 // Attempt to convert to an int64 first
457 if i, err := n.Int64(); err == nil {
458 return i, nil
459 }
460 // Return a float64 (default json.Decode() behavior)
461 // An overflow will return an error
462 return n.Float64()
463}