databricks-cli/libs/dyn/convert/normalize_test.go

682 lines
16 KiB
Go

package convert
import (
"testing"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn"
assert "github.com/databricks/cli/libs/dyn/dynassert"
)
func TestNormalizeStruct(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
Bar string `json:"bar"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"bar": dyn.V("baz"),
})
vout, err := Normalize(typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeStructElementDiagnostic(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
Bar string `json:"bar"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"bar": dyn.V(map[string]dyn.Value{"an": dyn.V("error")}),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Path: dyn.NewPath(dyn.Key("bar")),
}, err[0])
// Elements that encounter an error during normalization are dropped.
assert.Equal(t, map[string]any{
"foo": "bar",
}, vout.AsAny())
}
func TestNormalizeStructUnknownField(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"bar": dyn.V("baz"),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `unknown field: bar`,
Location: vin.Get("foo").Location(),
Path: dyn.EmptyPath,
}, err[0])
// The field that can be mapped to the struct field is retained.
assert.Equal(t, map[string]any{
"foo": "bar",
}, vout.AsAny())
}
func TestNormalizeStructNil(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
}
var typ Tmp
vin := dyn.NilValue
vout, err := Normalize(typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeStructError(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
}
var typ Tmp
vin := dyn.V("string")
_, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found string`,
Location: vin.Get("foo").Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeStructNestedError(t *testing.T) {
type Nested struct {
F1 int `json:"f1"`
F2 int `json:"f2"`
}
type Tmp struct {
Foo Nested `json:"foo"`
Bar Nested `json:"bar"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V(map[string]dyn.Value{
"f1": dyn.V("error"),
"f2": dyn.V(1),
}),
"bar": dyn.V(map[string]dyn.Value{
"f1": dyn.V(1),
"f2": dyn.V("error"),
}),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 2)
// Verify that valid fields are retained.
assert.Equal(t,
dyn.V(map[string]dyn.Value{
"foo": dyn.V(map[string]dyn.Value{
"f2": dyn.V(int64(1)),
}),
"bar": dyn.V(map[string]dyn.Value{
"f1": dyn.V(int64(1)),
}),
}),
vout,
)
}
func TestNormalizeStructIncludeMissingFields(t *testing.T) {
type Nested struct {
String string `json:"string"`
}
type Tmp struct {
// Verify that fields that are already set in the dynamic value are not overridden.
Existing string `json:"existing"`
// Verify that structs are recursively normalized if not set.
Nested Nested `json:"nested"`
Ptr *Nested `json:"ptr"`
// Verify that containers are also zero-initialized if not set.
Map map[string]string `json:"map"`
Slice []string `json:"slice"`
// Verify that primitive types are zero-initialized if not set.
String string `json:"string"`
Bool bool `json:"bool"`
Int int `json:"int"`
Float float64 `json:"float"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"existing": dyn.V("already set"),
})
vout, err := Normalize(typ, vin, IncludeMissingFields)
assert.Empty(t, err)
assert.Equal(t, dyn.V(map[string]dyn.Value{
"existing": dyn.V("already set"),
"nested": dyn.V(map[string]dyn.Value{
"string": dyn.V(""),
}),
"ptr": dyn.V(map[string]dyn.Value{
"string": dyn.V(""),
}),
"map": dyn.V(map[string]dyn.Value{}),
"slice": dyn.V([]dyn.Value{}),
"string": dyn.V(""),
"bool": dyn.V(false),
"int": dyn.V(int64(0)),
"float": dyn.V(float64(0)),
}), vout)
}
func TestNormalizeStructIncludeMissingFieldsOnRecursiveType(t *testing.T) {
type Tmp struct {
// Verify that structs are recursively normalized if not set.
Ptr *Tmp `json:"ptr"`
// Verify that primitive types are zero-initialized if not set.
String string `json:"string"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"ptr": dyn.V(map[string]dyn.Value{
"ptr": dyn.V(map[string]dyn.Value{
"string": dyn.V("already set"),
}),
}),
})
vout, err := Normalize(typ, vin, IncludeMissingFields)
assert.Empty(t, err)
assert.Equal(t, dyn.V(map[string]dyn.Value{
"ptr": dyn.V(map[string]dyn.Value{
"ptr": dyn.V(map[string]dyn.Value{
// Note: the ptr field is not zero-initialized because that would recurse.
"string": dyn.V("already set"),
}),
"string": dyn.V(""),
}),
"string": dyn.V(""),
}), vout)
}
func TestNormalizeMap(t *testing.T) {
var typ map[string]string
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"bar": dyn.V("baz"),
})
vout, err := Normalize(typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeMapElementDiagnostic(t *testing.T) {
var typ map[string]string
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"bar": dyn.V(map[string]dyn.Value{"an": dyn.V("error")}),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Path: dyn.NewPath(dyn.Key("bar")),
}, err[0])
// Elements that encounter an error during normalization are dropped.
assert.Equal(t, map[string]any{
"foo": "bar",
}, vout.AsAny())
}
func TestNormalizeMapNil(t *testing.T) {
var typ map[string]string
vin := dyn.NilValue
vout, err := Normalize(typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeMapError(t *testing.T) {
var typ map[string]string
vin := dyn.V("string")
_, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found string`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeMapNestedError(t *testing.T) {
type Nested struct {
F1 int `json:"f1"`
F2 int `json:"f2"`
}
var typ map[string]Nested
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V(map[string]dyn.Value{
"f1": dyn.V("error"),
"f2": dyn.V(1),
}),
"bar": dyn.V(map[string]dyn.Value{
"f1": dyn.V(1),
"f2": dyn.V("error"),
}),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 2)
// Verify that valid fields are retained.
assert.Equal(t,
dyn.V(map[string]dyn.Value{
"foo": dyn.V(map[string]dyn.Value{
"f2": dyn.V(int64(1)),
}),
"bar": dyn.V(map[string]dyn.Value{
"f1": dyn.V(int64(1)),
}),
}),
vout,
)
}
func TestNormalizeSlice(t *testing.T) {
var typ []string
vin := dyn.V([]dyn.Value{
dyn.V("foo"),
dyn.V("bar"),
})
vout, err := Normalize(typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeSliceElementDiagnostic(t *testing.T) {
var typ []string
vin := dyn.V([]dyn.Value{
dyn.V("foo"),
dyn.V("bar"),
dyn.V(map[string]dyn.Value{"an": dyn.V("error")}),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Path: dyn.NewPath(dyn.Index(2)),
}, err[0])
// Elements that encounter an error during normalization are dropped.
assert.Equal(t, []any{"foo", "bar"}, vout.AsAny())
}
func TestNormalizeSliceNil(t *testing.T) {
var typ []string
vin := dyn.NilValue
vout, err := Normalize(typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeSliceError(t *testing.T) {
var typ []string
vin := dyn.V("string")
_, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected sequence, found string`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeSliceNestedError(t *testing.T) {
type Nested struct {
F1 int `json:"f1"`
F2 int `json:"f2"`
}
var typ []Nested
vin := dyn.V([]dyn.Value{
dyn.V(map[string]dyn.Value{
"f1": dyn.V("error"),
"f2": dyn.V(1),
}),
dyn.V(map[string]dyn.Value{
"f1": dyn.V(1),
"f2": dyn.V("error"),
}),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 2)
// Verify that valid fields are retained.
assert.Equal(t,
dyn.V([]dyn.Value{
dyn.V(map[string]dyn.Value{
"f2": dyn.V(int64(1)),
}),
dyn.V(map[string]dyn.Value{
"f1": dyn.V(int64(1)),
}),
}),
vout,
)
}
func TestNormalizeString(t *testing.T) {
var typ string
vin := dyn.V("string")
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeStringNil(t *testing.T) {
var typ string
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a string value, found null`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeStringFromBool(t *testing.T) {
var typ string
vin := dyn.NewValue(true, dyn.Location{File: "file", Line: 1, Column: 1})
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.NewValue("true", vin.Location()), vout)
}
func TestNormalizeStringFromInt(t *testing.T) {
var typ string
vin := dyn.NewValue(123, dyn.Location{File: "file", Line: 1, Column: 1})
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.NewValue("123", vin.Location()), vout)
}
func TestNormalizeStringFromFloat(t *testing.T) {
var typ string
vin := dyn.NewValue(1.20, dyn.Location{File: "file", Line: 1, Column: 1})
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.NewValue("1.2", vin.Location()), vout)
}
func TestNormalizeStringError(t *testing.T) {
var typ string
vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeBool(t *testing.T) {
var typ bool
vin := dyn.V(true)
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.V(true), vout)
}
func TestNormalizeBoolNil(t *testing.T) {
var typ bool
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a bool value, found null`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeBoolFromString(t *testing.T) {
var typ bool
for _, c := range []struct {
Input string
Output bool
}{
{"true", true},
{"false", false},
{"Y", true},
{"N", false},
{"on", true},
{"off", false},
} {
vin := dyn.V(c.Input)
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.V(c.Output), vout)
}
}
func TestNormalizeBoolFromStringVariableReference(t *testing.T) {
var typ bool
vin := dyn.V("${var.foo}")
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeBoolFromStringError(t *testing.T) {
var typ bool
vin := dyn.V("abc")
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected bool, found string`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeBoolError(t *testing.T) {
var typ bool
vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected bool, found map`,
Location: dyn.Location{},
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeInt(t *testing.T) {
var typ int
vin := dyn.V(123)
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.V(int64(123)), vout)
}
func TestNormalizeIntNil(t *testing.T) {
var typ int
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a int value, found null`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeIntFromString(t *testing.T) {
var typ int
vin := dyn.V("123")
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.V(int64(123)), vout)
}
func TestNormalizeIntFromStringVariableReference(t *testing.T) {
var typ int
vin := dyn.V("${var.foo}")
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeIntFromStringError(t *testing.T) {
var typ int
vin := dyn.V("abc")
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `cannot parse "abc" as an integer`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeIntError(t *testing.T) {
var typ int
vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected int, found map`,
Location: dyn.Location{},
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeFloat(t *testing.T) {
var typ float64
vin := dyn.V(1.2)
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.V(1.2), vout)
}
func TestNormalizeFloatNil(t *testing.T) {
var typ float64
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a float value, found null`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeFloatFromString(t *testing.T) {
var typ float64
vin := dyn.V("1.2")
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, dyn.V(1.2), vout)
}
func TestNormalizeFloatFromStringVariableReference(t *testing.T) {
var typ float64
vin := dyn.V("${var.foo}")
vout, err := Normalize(&typ, vin)
assert.Empty(t, err)
assert.Equal(t, vin, vout)
}
func TestNormalizeFloatFromStringError(t *testing.T) {
var typ float64
vin := dyn.V("abc")
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `cannot parse "abc" as a floating point number`,
Location: vin.Location(),
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeFloatError(t *testing.T) {
var typ float64
vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")})
_, err := Normalize(&typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected float, found map`,
Location: dyn.Location{},
Path: dyn.EmptyPath,
}, err[0])
}
func TestNormalizeAnchors(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"anchor": dyn.V("anchor").MarkAnchor(),
})
vout, err := Normalize(typ, vin)
assert.Len(t, err, 0)
// The field that can be mapped to the struct field is retained.
assert.Equal(t, map[string]any{
"foo": "bar",
}, vout.AsAny())
}