2023-10-25 11:56:42 +00:00
|
|
|
package convert
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/databricks/cli/libs/diag"
|
2023-12-22 13:20:45 +00:00
|
|
|
"github.com/databricks/cli/libs/dyn"
|
2023-10-25 11:56:42 +00:00
|
|
|
)
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func Normalize(dst any, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
return normalizeType(reflect.TypeOf(dst), src)
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeType(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
for typ.Kind() == reflect.Pointer {
|
|
|
|
typ = typ.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch typ.Kind() {
|
|
|
|
case reflect.Struct:
|
|
|
|
return normalizeStruct(typ, src)
|
|
|
|
case reflect.Map:
|
|
|
|
return normalizeMap(typ, src)
|
|
|
|
case reflect.Slice:
|
|
|
|
return normalizeSlice(typ, src)
|
|
|
|
case reflect.String:
|
|
|
|
return normalizeString(typ, src)
|
|
|
|
case reflect.Bool:
|
|
|
|
return normalizeBool(typ, src)
|
|
|
|
case reflect.Int, reflect.Int32, reflect.Int64:
|
|
|
|
return normalizeInt(typ, src)
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
|
|
return normalizeFloat(typ, src)
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diag.Errorf("unsupported type: %s", typ.Kind())
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func typeMismatch(expected dyn.Kind, src dyn.Value) diag.Diagnostic {
|
2023-10-25 11:56:42 +00:00
|
|
|
return diag.Diagnostic{
|
|
|
|
Severity: diag.Error,
|
|
|
|
Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()),
|
|
|
|
Location: src.Location(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeStruct(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindMap:
|
|
|
|
out := make(map[string]dyn.Value)
|
2023-10-25 11:56:42 +00:00
|
|
|
info := getStructInfo(typ)
|
|
|
|
for k, v := range src.MustMap() {
|
|
|
|
index, ok := info.Fields[k]
|
|
|
|
if !ok {
|
|
|
|
diags = diags.Append(diag.Diagnostic{
|
|
|
|
Severity: diag.Warning,
|
|
|
|
Summary: fmt.Sprintf("unknown field: %s", k),
|
|
|
|
Location: src.Location(),
|
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize the value according to the field type.
|
|
|
|
v, err := normalizeType(typ.FieldByIndex(index).Type, v)
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Extend(err)
|
|
|
|
// Skip the element if it cannot be normalized.
|
|
|
|
if err.HasError() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out[k] = v
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
|
|
|
case dyn.KindNil:
|
2023-10-25 11:56:42 +00:00
|
|
|
return src, diags
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindMap, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeMap(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindMap:
|
|
|
|
out := make(map[string]dyn.Value)
|
2023-10-25 11:56:42 +00:00
|
|
|
for k, v := range src.MustMap() {
|
|
|
|
// Normalize the value according to the map element type.
|
|
|
|
v, err := normalizeType(typ.Elem(), v)
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Extend(err)
|
|
|
|
// Skip the element if it cannot be normalized.
|
|
|
|
if err.HasError() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out[k] = v
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
|
|
|
case dyn.KindNil:
|
2023-10-25 11:56:42 +00:00
|
|
|
return src, diags
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindMap, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeSlice(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindSequence:
|
|
|
|
out := make([]dyn.Value, 0, len(src.MustSequence()))
|
2023-10-25 11:56:42 +00:00
|
|
|
for _, v := range src.MustSequence() {
|
|
|
|
// Normalize the value according to the slice element type.
|
|
|
|
v, err := normalizeType(typ.Elem(), v)
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Extend(err)
|
|
|
|
// Skip the element if it cannot be normalized.
|
|
|
|
if err.HasError() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out = append(out, v)
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
|
|
|
case dyn.KindNil:
|
2023-10-25 11:56:42 +00:00
|
|
|
return src, diags
|
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindSequence, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeString(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
var out string
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindString:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = src.MustString()
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindBool:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = strconv.FormatBool(src.MustBool())
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindInt:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = strconv.FormatInt(src.MustInt(), 10)
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindFloat:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = strconv.FormatFloat(src.MustFloat(), 'f', -1, 64)
|
|
|
|
default:
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindString, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeBool(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
var out bool
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindBool:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = src.MustBool()
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindString:
|
2023-10-25 11:56:42 +00:00
|
|
|
// See https://github.com/go-yaml/yaml/blob/f6f7691b1fdeb513f56608cd2c32c51f8194bf51/decode.go#L684-L693.
|
|
|
|
switch src.MustString() {
|
|
|
|
case "true", "y", "Y", "yes", "Yes", "YES", "on", "On", "ON":
|
|
|
|
out = true
|
|
|
|
case "false", "n", "N", "no", "No", "NO", "off", "Off", "OFF":
|
|
|
|
out = false
|
|
|
|
default:
|
|
|
|
// Cannot interpret as a boolean.
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindBool, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
default:
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindBool, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeInt(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
var out int64
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindInt:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = src.MustInt()
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindString:
|
2023-10-25 11:56:42 +00:00
|
|
|
var err error
|
|
|
|
out, err = strconv.ParseInt(src.MustString(), 10, 64)
|
|
|
|
if err != nil {
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(diag.Diagnostic{
|
2023-10-25 11:56:42 +00:00
|
|
|
Severity: diag.Error,
|
|
|
|
Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()),
|
|
|
|
Location: src.Location(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
default:
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindInt, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
func normalizeFloat(typ reflect.Type, src dyn.Value) (dyn.Value, diag.Diagnostics) {
|
2023-10-25 11:56:42 +00:00
|
|
|
var diags diag.Diagnostics
|
|
|
|
var out float64
|
|
|
|
|
|
|
|
switch src.Kind() {
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindFloat:
|
2023-10-25 11:56:42 +00:00
|
|
|
out = src.MustFloat()
|
2023-12-22 13:20:45 +00:00
|
|
|
case dyn.KindString:
|
2023-10-25 11:56:42 +00:00
|
|
|
var err error
|
|
|
|
out, err = strconv.ParseFloat(src.MustString(), 64)
|
|
|
|
if err != nil {
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(diag.Diagnostic{
|
2023-10-25 11:56:42 +00:00
|
|
|
Severity: diag.Error,
|
|
|
|
Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()),
|
|
|
|
Location: src.Location(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
default:
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NilValue, diags.Append(typeMismatch(dyn.KindFloat, src))
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 13:20:45 +00:00
|
|
|
return dyn.NewValue(out, src.Location()), diags
|
2023-10-25 11:56:42 +00:00
|
|
|
}
|