2023-10-24 11:12:36 +00:00
|
|
|
package convert
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2023-11-27 10:06:29 +00:00
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
"github.com/databricks/cli/libs/dyn"
|
2024-05-31 09:42:25 +00:00
|
|
|
"github.com/databricks/cli/libs/textutil"
|
2023-10-24 11:12:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// structInfo holds the type information we need to efficiently
|
2023-12-22 13:20:45 +00:00
|
|
|
// convert data from a [dyn.Value] to a Go struct.
|
2023-10-24 11:12:36 +00:00
|
|
|
type structInfo struct {
|
|
|
|
// Fields maps the JSON-name of the field to the field's index for use with [FieldByIndex].
|
|
|
|
Fields map[string][]int
|
2023-11-27 10:06:29 +00:00
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
// ValueField maps to the field with a [dyn.Value].
|
2023-11-27 10:06:29 +00:00
|
|
|
// The underlying type is expected to only have one of these.
|
|
|
|
ValueField []int
|
2023-10-24 11:12:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// structInfoCache caches type information.
|
|
|
|
var structInfoCache = make(map[reflect.Type]structInfo)
|
|
|
|
|
|
|
|
// structInfoCacheLock guards concurrent access to structInfoCache.
|
|
|
|
var structInfoCacheLock sync.Mutex
|
|
|
|
|
|
|
|
// getStructInfo returns the [structInfo] for the given type.
|
|
|
|
// It lazily populates a cache, so the first call for a given
|
|
|
|
// type is slower than subsequent calls for that same type.
|
|
|
|
func getStructInfo(typ reflect.Type) structInfo {
|
|
|
|
structInfoCacheLock.Lock()
|
|
|
|
defer structInfoCacheLock.Unlock()
|
|
|
|
|
|
|
|
si, ok := structInfoCache[typ]
|
|
|
|
if !ok {
|
|
|
|
si = buildStructInfo(typ)
|
|
|
|
structInfoCache[typ] = si
|
|
|
|
}
|
|
|
|
|
|
|
|
return si
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildStructInfo populates a new [structInfo] for the given type.
|
|
|
|
func buildStructInfo(typ reflect.Type) structInfo {
|
|
|
|
var out = structInfo{
|
|
|
|
Fields: make(map[string][]int),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Queue holds the indexes of the structs to visit.
|
|
|
|
// It is initialized with a single empty slice to visit the top level struct.
|
|
|
|
var queue [][]int = [][]int{{}}
|
|
|
|
for i := 0; i < len(queue); i++ {
|
|
|
|
prefix := queue[i]
|
|
|
|
|
|
|
|
// Traverse embedded anonymous types (if prefix is non-empty).
|
|
|
|
styp := typ
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
styp = styp.FieldByIndex(prefix).Type
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dereference pointer type.
|
|
|
|
if styp.Kind() == reflect.Pointer {
|
|
|
|
styp = styp.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
nf := styp.NumField()
|
|
|
|
for j := 0; j < nf; j++ {
|
|
|
|
sf := styp.Field(j)
|
|
|
|
|
|
|
|
// Recurse into anonymous fields.
|
|
|
|
if sf.Anonymous {
|
|
|
|
queue = append(queue, append(prefix, sf.Index...))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
// If this field has type [dyn.Value], we populate it with the source [dyn.Value] from [ToTyped].
|
2023-11-27 10:06:29 +00:00
|
|
|
if sf.IsExported() && sf.Type == configValueType {
|
|
|
|
if out.ValueField != nil {
|
2023-12-22 13:20:45 +00:00
|
|
|
panic("multiple dyn.Value fields")
|
2023-11-27 10:06:29 +00:00
|
|
|
}
|
|
|
|
out.ValueField = append(prefix, sf.Index...)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-10-24 11:12:36 +00:00
|
|
|
name, _, _ := strings.Cut(sf.Tag.Get("json"), ",")
|
2024-05-31 09:42:25 +00:00
|
|
|
if typ.Name() == "QualityMonitor" && name == "-" {
|
|
|
|
urlName, _, _ := strings.Cut(sf.Tag.Get("url"), ",")
|
|
|
|
if urlName == "" || urlName == "-" {
|
|
|
|
name = textutil.CamelToSnakeCase(sf.Name)
|
|
|
|
} else {
|
|
|
|
name = urlName
|
|
|
|
}
|
|
|
|
}
|
2023-10-24 11:12:36 +00:00
|
|
|
if name == "" || name == "-" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Top level fields always take precedence.
|
|
|
|
// Therefore, if it is already set, we ignore it.
|
|
|
|
if _, ok := out.Fields[name]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
out.Fields[name] = append(prefix, sf.Index...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
2023-11-15 09:19:51 +00:00
|
|
|
|
|
|
|
func (s *structInfo) FieldValues(v reflect.Value) map[string]reflect.Value {
|
|
|
|
var out = make(map[string]reflect.Value)
|
|
|
|
|
|
|
|
for k, index := range s.Fields {
|
|
|
|
fv := v
|
|
|
|
|
|
|
|
// Locate value in struct (it could be an embedded type).
|
|
|
|
for i, x := range index {
|
|
|
|
if i > 0 {
|
|
|
|
if fv.Kind() == reflect.Pointer && fv.Type().Elem().Kind() == reflect.Struct {
|
|
|
|
if fv.IsNil() {
|
|
|
|
fv = reflect.Value{}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
fv = fv.Elem()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fv = fv.Field(x)
|
|
|
|
}
|
|
|
|
|
|
|
|
if fv.IsValid() {
|
|
|
|
out[k] = fv
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
2023-11-27 10:06:29 +00:00
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
// Type of [dyn.Value].
|
|
|
|
var configValueType = reflect.TypeOf((*dyn.Value)(nil)).Elem()
|