mirror of https://github.com/databricks/cli.git
Track multiple locations associated with a `dyn.Value` (#1510)
## Changes This PR changes the location metadata associated with a `dyn.Value` to a slice of locations. This will allow us to keep track of location metadata across merges and overrides. The convention is to treat the first location in the slice as the primary location. Also, the semantics are the same as before if there's only one location associated with a value, that is: 1. For complex values (maps, sequences) the location of the v1 is primary in Merge(v1, v2) 2. For primitive values the location of v2 is primary in Merge(v1, v2) ## Tests Modifying existing merge unit tests. Other existing unit tests and integration tests pass. --------- Co-authored-by: Pieter Noordhuis <pieter.noordhuis@databricks.com>
This commit is contained in:
parent
39c2633773
commit
8ed9964482
|
@ -22,7 +22,7 @@ func ConvertJobToValue(job *jobs.Job) (dyn.Value, error) {
|
|||
tasks = append(tasks, v)
|
||||
}
|
||||
// We're using location lines to define the order of keys in exported YAML.
|
||||
value["tasks"] = dyn.NewValue(tasks, dyn.Location{Line: jobOrder.Get("tasks")})
|
||||
value["tasks"] = dyn.NewValue(tasks, []dyn.Location{{Line: jobOrder.Get("tasks")}})
|
||||
}
|
||||
|
||||
return yamlsaver.ConvertToMapValue(job.Settings, jobOrder, []string{"format", "new_cluster", "existing_cluster_id"}, value)
|
||||
|
|
|
@ -59,7 +59,7 @@ func (m *expandPipelineGlobPaths) expandLibrary(v dyn.Value) ([]dyn.Value, error
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nv, err := dyn.SetByPath(v, p, dyn.NewValue(m, pv.Location()))
|
||||
nv, err := dyn.SetByPath(v, p, dyn.NewValue(m, pv.Locations()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func (m *expandPipelineGlobPaths) expandSequence(p dyn.Path, v dyn.Value) (dyn.V
|
|||
vs = append(vs, v...)
|
||||
}
|
||||
|
||||
return dyn.NewValue(vs, v.Location()), nil
|
||||
return dyn.NewValue(vs, v.Locations()), nil
|
||||
}
|
||||
|
||||
func (m *expandPipelineGlobPaths) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||
|
|
|
@ -305,8 +305,8 @@ type createOverrideVisitorTestCase struct {
|
|||
}
|
||||
|
||||
func TestCreateOverrideVisitor(t *testing.T) {
|
||||
left := dyn.NewValue(42, dyn.Location{})
|
||||
right := dyn.NewValue(1337, dyn.Location{})
|
||||
left := dyn.V(42)
|
||||
right := dyn.V(1337)
|
||||
|
||||
testCases := []createOverrideVisitorTestCase{
|
||||
{
|
||||
|
@ -470,21 +470,21 @@ func TestCreateOverrideVisitor_omitempty(t *testing.T) {
|
|||
// this is not happening, but adding for completeness
|
||||
name: "undo delete of empty variables",
|
||||
path: dyn.MustPathFromString("variables"),
|
||||
left: dyn.NewValue([]dyn.Value{}, location),
|
||||
left: dyn.NewValue([]dyn.Value{}, []dyn.Location{location}),
|
||||
expectedErr: merge.ErrOverrideUndoDelete,
|
||||
phases: allPhases,
|
||||
},
|
||||
{
|
||||
name: "undo delete of empty job clusters",
|
||||
path: dyn.MustPathFromString("resources.jobs.job0.job_clusters"),
|
||||
left: dyn.NewValue([]dyn.Value{}, location),
|
||||
left: dyn.NewValue([]dyn.Value{}, []dyn.Location{location}),
|
||||
expectedErr: merge.ErrOverrideUndoDelete,
|
||||
phases: allPhases,
|
||||
},
|
||||
{
|
||||
name: "allow delete of non-empty job clusters",
|
||||
path: dyn.MustPathFromString("resources.jobs.job0.job_clusters"),
|
||||
left: dyn.NewValue([]dyn.Value{dyn.NewValue("abc", location)}, location),
|
||||
left: dyn.NewValue([]dyn.Value{dyn.NewValue("abc", []dyn.Location{location})}, []dyn.Location{location}),
|
||||
expectedErr: nil,
|
||||
// deletions aren't allowed in 'load' phase
|
||||
phases: []phase{PythonMutatorPhaseInit},
|
||||
|
@ -492,17 +492,15 @@ func TestCreateOverrideVisitor_omitempty(t *testing.T) {
|
|||
{
|
||||
name: "undo delete of empty tags",
|
||||
path: dyn.MustPathFromString("resources.jobs.job0.tags"),
|
||||
left: dyn.NewValue(map[string]dyn.Value{}, location),
|
||||
left: dyn.NewValue(map[string]dyn.Value{}, []dyn.Location{location}),
|
||||
expectedErr: merge.ErrOverrideUndoDelete,
|
||||
phases: allPhases,
|
||||
},
|
||||
{
|
||||
name: "allow delete of non-empty tags",
|
||||
path: dyn.MustPathFromString("resources.jobs.job0.tags"),
|
||||
left: dyn.NewValue(
|
||||
map[string]dyn.Value{"dev": dyn.NewValue("true", location)},
|
||||
location,
|
||||
),
|
||||
left: dyn.NewValue(map[string]dyn.Value{"dev": dyn.NewValue("true", []dyn.Location{location})}, []dyn.Location{location}),
|
||||
|
||||
expectedErr: nil,
|
||||
// deletions aren't allowed in 'load' phase
|
||||
phases: []phase{PythonMutatorPhaseInit},
|
||||
|
@ -510,7 +508,7 @@ func TestCreateOverrideVisitor_omitempty(t *testing.T) {
|
|||
{
|
||||
name: "undo delete of nil",
|
||||
path: dyn.MustPathFromString("resources.jobs.job0.tags"),
|
||||
left: dyn.NilValue.WithLocation(location),
|
||||
left: dyn.NilValue.WithLocations([]dyn.Location{location}),
|
||||
expectedErr: merge.ErrOverrideUndoDelete,
|
||||
phases: allPhases,
|
||||
},
|
||||
|
|
|
@ -38,7 +38,7 @@ func (m *rewriteSyncPaths) makeRelativeTo(root string) dyn.MapFunc {
|
|||
return dyn.InvalidValue, err
|
||||
}
|
||||
|
||||
return dyn.NewValue(filepath.Join(rel, v.MustString()), v.Location()), nil
|
||||
return dyn.NewValue(filepath.Join(rel, v.MustString()), v.Locations()), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ func (t *translateContext) rewriteValue(p dyn.Path, v dyn.Value, fn rewriteFunc,
|
|||
return dyn.InvalidValue, err
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, v.Location()), nil
|
||||
return dyn.NewValue(out, v.Locations()), nil
|
||||
}
|
||||
|
||||
func (t *translateContext) rewriteRelativeTo(p dyn.Path, v dyn.Value, fn rewriteFunc, dir, fallback string) (dyn.Value, error) {
|
||||
|
|
|
@ -378,7 +378,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
|||
|
||||
// Below, we're setting fields on the bundle key, so make sure it exists.
|
||||
if root.Get("bundle").Kind() == dyn.KindInvalid {
|
||||
root, err = dyn.Set(root, "bundle", dyn.NewValue(map[string]dyn.Value{}, dyn.Location{}))
|
||||
root, err = dyn.Set(root, "bundle", dyn.V(map[string]dyn.Value{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -404,7 +404,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
|||
if v := target.Get("git"); v.Kind() != dyn.KindInvalid {
|
||||
ref, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("git")))
|
||||
if err != nil {
|
||||
ref = dyn.NewValue(map[string]dyn.Value{}, dyn.Location{})
|
||||
ref = dyn.V(map[string]dyn.Value{})
|
||||
}
|
||||
|
||||
// Merge the override into the reference.
|
||||
|
@ -415,7 +415,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
|||
|
||||
// If the branch was overridden, we need to clear the inferred flag.
|
||||
if branch := v.Get("branch"); branch.Kind() != dyn.KindInvalid {
|
||||
out, err = dyn.SetByPath(out, dyn.NewPath(dyn.Key("inferred")), dyn.NewValue(false, dyn.Location{}))
|
||||
out, err = dyn.SetByPath(out, dyn.NewPath(dyn.Key("inferred")), dyn.V(false))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -456,7 +456,7 @@ func rewriteShorthands(v dyn.Value) (dyn.Value, error) {
|
|||
// configuration will convert this to a string if necessary.
|
||||
return dyn.NewValue(map[string]dyn.Value{
|
||||
"default": variable,
|
||||
}, variable.Location()), nil
|
||||
}, variable.Locations()), nil
|
||||
|
||||
case dyn.KindMap, dyn.KindSequence:
|
||||
// Check if the original definition of variable has a type field.
|
||||
|
@ -469,7 +469,7 @@ func rewriteShorthands(v dyn.Value) (dyn.Value, error) {
|
|||
return dyn.NewValue(map[string]dyn.Value{
|
||||
"type": typeV,
|
||||
"default": variable,
|
||||
}, variable.Location()), nil
|
||||
}, variable.Locations()), nil
|
||||
}
|
||||
|
||||
return variable, nil
|
||||
|
|
|
@ -14,9 +14,9 @@ func SetLocation(b *bundle.Bundle, prefix string, filePath string) {
|
|||
return dyn.Walk(root, func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
||||
// If the path has the given prefix, set the location.
|
||||
if p.HasPrefix(start) {
|
||||
return v.WithLocation(dyn.Location{
|
||||
return v.WithLocations([]dyn.Location{{
|
||||
File: filePath,
|
||||
}), nil
|
||||
}}), nil
|
||||
}
|
||||
|
||||
// The path is not nested under the given prefix.
|
||||
|
|
|
@ -42,7 +42,7 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
|
|||
// Dereference pointer if necessary
|
||||
for srcv.Kind() == reflect.Pointer {
|
||||
if srcv.IsNil() {
|
||||
return dyn.NilValue.WithLocation(ref.Location()), nil
|
||||
return dyn.NilValue.WithLocations(ref.Locations()), nil
|
||||
}
|
||||
srcv = srcv.Elem()
|
||||
|
||||
|
@ -83,7 +83,7 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
|
|||
if err != nil {
|
||||
return dyn.InvalidValue, err
|
||||
}
|
||||
return v.WithLocation(ref.Location()), err
|
||||
return v.WithLocations(ref.Locations()), err
|
||||
}
|
||||
|
||||
func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
||||
|
|
|
@ -115,16 +115,16 @@ func TestFromTypedStructSetFieldsRetainLocation(t *testing.T) {
|
|||
}
|
||||
|
||||
ref := dyn.V(map[string]dyn.Value{
|
||||
"foo": dyn.NewValue("bar", dyn.Location{File: "foo"}),
|
||||
"bar": dyn.NewValue("baz", dyn.Location{File: "bar"}),
|
||||
"foo": dyn.NewValue("bar", []dyn.Location{{File: "foo"}}),
|
||||
"bar": dyn.NewValue("baz", []dyn.Location{{File: "bar"}}),
|
||||
})
|
||||
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert foo and bar have retained their location.
|
||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv.Get("foo"))
|
||||
assert.Equal(t, dyn.NewValue("qux", dyn.Location{File: "bar"}), nv.Get("bar"))
|
||||
assert.Equal(t, dyn.NewValue("bar", []dyn.Location{{File: "foo"}}), nv.Get("foo"))
|
||||
assert.Equal(t, dyn.NewValue("qux", []dyn.Location{{File: "bar"}}), nv.Get("bar"))
|
||||
}
|
||||
|
||||
func TestFromTypedStringMapWithZeroValue(t *testing.T) {
|
||||
|
@ -359,16 +359,16 @@ func TestFromTypedMapNonEmptyRetainLocation(t *testing.T) {
|
|||
}
|
||||
|
||||
ref := dyn.V(map[string]dyn.Value{
|
||||
"foo": dyn.NewValue("bar", dyn.Location{File: "foo"}),
|
||||
"bar": dyn.NewValue("baz", dyn.Location{File: "bar"}),
|
||||
"foo": dyn.NewValue("bar", []dyn.Location{{File: "foo"}}),
|
||||
"bar": dyn.NewValue("baz", []dyn.Location{{File: "bar"}}),
|
||||
})
|
||||
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert foo and bar have retained their locations.
|
||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv.Get("foo"))
|
||||
assert.Equal(t, dyn.NewValue("qux", dyn.Location{File: "bar"}), nv.Get("bar"))
|
||||
assert.Equal(t, dyn.NewValue("bar", []dyn.Location{{File: "foo"}}), nv.Get("foo"))
|
||||
assert.Equal(t, dyn.NewValue("qux", []dyn.Location{{File: "bar"}}), nv.Get("bar"))
|
||||
}
|
||||
|
||||
func TestFromTypedMapFieldWithZeroValue(t *testing.T) {
|
||||
|
@ -432,16 +432,16 @@ func TestFromTypedSliceNonEmptyRetainLocation(t *testing.T) {
|
|||
}
|
||||
|
||||
ref := dyn.V([]dyn.Value{
|
||||
dyn.NewValue("foo", dyn.Location{File: "foo"}),
|
||||
dyn.NewValue("bar", dyn.Location{File: "bar"}),
|
||||
dyn.NewValue("foo", []dyn.Location{{File: "foo"}}),
|
||||
dyn.NewValue("bar", []dyn.Location{{File: "bar"}}),
|
||||
})
|
||||
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert foo and bar have retained their locations.
|
||||
assert.Equal(t, dyn.NewValue("foo", dyn.Location{File: "foo"}), nv.Index(0))
|
||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "bar"}), nv.Index(1))
|
||||
assert.Equal(t, dyn.NewValue("foo", []dyn.Location{{File: "foo"}}), nv.Index(0))
|
||||
assert.Equal(t, dyn.NewValue("bar", []dyn.Location{{File: "bar"}}), nv.Index(1))
|
||||
}
|
||||
|
||||
func TestFromTypedStringEmpty(t *testing.T) {
|
||||
|
@ -477,19 +477,19 @@ func TestFromTypedStringNonEmptyOverwrite(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFromTypedStringRetainsLocations(t *testing.T) {
|
||||
var ref = dyn.NewValue("foo", dyn.Location{File: "foo"})
|
||||
var ref = dyn.NewValue("foo", []dyn.Location{{File: "foo"}})
|
||||
|
||||
// case: value has not been changed
|
||||
var src string = "foo"
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue("foo", dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue("foo", []dyn.Location{{File: "foo"}}), nv)
|
||||
|
||||
// case: value has been changed
|
||||
src = "bar"
|
||||
nv, err = FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue("bar", []dyn.Location{{File: "foo"}}), nv)
|
||||
}
|
||||
|
||||
func TestFromTypedStringTypeError(t *testing.T) {
|
||||
|
@ -532,19 +532,19 @@ func TestFromTypedBoolNonEmptyOverwrite(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFromTypedBoolRetainsLocations(t *testing.T) {
|
||||
var ref = dyn.NewValue(true, dyn.Location{File: "foo"})
|
||||
var ref = dyn.NewValue(true, []dyn.Location{{File: "foo"}})
|
||||
|
||||
// case: value has not been changed
|
||||
var src bool = true
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(true, dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(true, []dyn.Location{{File: "foo"}}), nv)
|
||||
|
||||
// case: value has been changed
|
||||
src = false
|
||||
nv, err = FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(false, dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(false, []dyn.Location{{File: "foo"}}), nv)
|
||||
}
|
||||
|
||||
func TestFromTypedBoolVariableReference(t *testing.T) {
|
||||
|
@ -595,19 +595,19 @@ func TestFromTypedIntNonEmptyOverwrite(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFromTypedIntRetainsLocations(t *testing.T) {
|
||||
var ref = dyn.NewValue(1234, dyn.Location{File: "foo"})
|
||||
var ref = dyn.NewValue(1234, []dyn.Location{{File: "foo"}})
|
||||
|
||||
// case: value has not been changed
|
||||
var src int = 1234
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(1234, dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(1234, []dyn.Location{{File: "foo"}}), nv)
|
||||
|
||||
// case: value has been changed
|
||||
src = 1235
|
||||
nv, err = FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(int64(1235), dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(int64(1235), []dyn.Location{{File: "foo"}}), nv)
|
||||
}
|
||||
|
||||
func TestFromTypedIntVariableReference(t *testing.T) {
|
||||
|
@ -659,19 +659,19 @@ func TestFromTypedFloatNonEmptyOverwrite(t *testing.T) {
|
|||
|
||||
func TestFromTypedFloatRetainsLocations(t *testing.T) {
|
||||
var src float64
|
||||
var ref = dyn.NewValue(1.23, dyn.Location{File: "foo"})
|
||||
var ref = dyn.NewValue(1.23, []dyn.Location{{File: "foo"}})
|
||||
|
||||
// case: value has not been changed
|
||||
src = 1.23
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(1.23, dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(1.23, []dyn.Location{{File: "foo"}}), nv)
|
||||
|
||||
// case: value has been changed
|
||||
src = 1.24
|
||||
nv, err = FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(1.24, dyn.Location{File: "foo"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(1.24, []dyn.Location{{File: "foo"}}), nv)
|
||||
}
|
||||
|
||||
func TestFromTypedFloatVariableReference(t *testing.T) {
|
||||
|
@ -740,27 +740,27 @@ func TestFromTypedNilPointerRetainsLocations(t *testing.T) {
|
|||
}
|
||||
|
||||
var src *Tmp
|
||||
ref := dyn.NewValue(nil, dyn.Location{File: "foobar"})
|
||||
ref := dyn.NewValue(nil, []dyn.Location{{File: "foobar"}})
|
||||
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(nil, dyn.Location{File: "foobar"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(nil, []dyn.Location{{File: "foobar"}}), nv)
|
||||
}
|
||||
|
||||
func TestFromTypedNilMapRetainsLocation(t *testing.T) {
|
||||
var src map[string]string
|
||||
ref := dyn.NewValue(nil, dyn.Location{File: "foobar"})
|
||||
ref := dyn.NewValue(nil, []dyn.Location{{File: "foobar"}})
|
||||
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(nil, dyn.Location{File: "foobar"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(nil, []dyn.Location{{File: "foobar"}}), nv)
|
||||
}
|
||||
|
||||
func TestFromTypedNilSliceRetainsLocation(t *testing.T) {
|
||||
var src []string
|
||||
ref := dyn.NewValue(nil, dyn.Location{File: "foobar"})
|
||||
ref := dyn.NewValue(nil, []dyn.Location{{File: "foobar"}})
|
||||
|
||||
nv, err := FromTyped(src, ref)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dyn.NewValue(nil, dyn.Location{File: "foobar"}), nv)
|
||||
assert.Equal(t, dyn.NewValue(nil, []dyn.Location{{File: "foobar"}}), nv)
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
|
|||
|
||||
// Return the normalized value if missing fields are not included.
|
||||
if !n.includeMissingFields {
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
}
|
||||
|
||||
// Populate missing fields with their zero values.
|
||||
|
@ -165,7 +165,7 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
|
|||
}
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
case dyn.KindNil:
|
||||
return src, diags
|
||||
|
||||
|
@ -203,7 +203,7 @@ func (n normalizeOptions) normalizeMap(typ reflect.Type, src dyn.Value, seen []r
|
|||
out.Set(pk, nv)
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
case dyn.KindNil:
|
||||
return src, diags
|
||||
|
||||
|
@ -238,7 +238,7 @@ func (n normalizeOptions) normalizeSlice(typ reflect.Type, src dyn.Value, seen [
|
|||
out = append(out, v)
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
case dyn.KindNil:
|
||||
return src, diags
|
||||
|
||||
|
@ -273,7 +273,7 @@ func (n normalizeOptions) normalizeString(typ reflect.Type, src dyn.Value, path
|
|||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindString, src, path))
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
}
|
||||
|
||||
func (n normalizeOptions) normalizeBool(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
|
||||
|
@ -306,7 +306,7 @@ func (n normalizeOptions) normalizeBool(typ reflect.Type, src dyn.Value, path dy
|
|||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindBool, src, path))
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
}
|
||||
|
||||
func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
|
||||
|
@ -349,7 +349,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
|
|||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindInt, src, path))
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
}
|
||||
|
||||
func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
|
||||
|
@ -392,7 +392,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
|
|||
return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindFloat, src, path))
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, src.Location()), diags
|
||||
return dyn.NewValue(out, src.Locations()), diags
|
||||
}
|
||||
|
||||
func (n normalizeOptions) normalizeInterface(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) {
|
||||
|
|
|
@ -229,7 +229,7 @@ func TestNormalizeStructVariableReference(t *testing.T) {
|
|||
}
|
||||
|
||||
var typ Tmp
|
||||
vin := dyn.NewValue("${var.foo}", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue("${var.foo}", []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vout, err := Normalize(typ, vin)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, vin, vout)
|
||||
|
@ -241,7 +241,7 @@ func TestNormalizeStructRandomStringError(t *testing.T) {
|
|||
}
|
||||
|
||||
var typ Tmp
|
||||
vin := dyn.NewValue("var foo", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue("var foo", []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
|
@ -258,7 +258,7 @@ func TestNormalizeStructIntError(t *testing.T) {
|
|||
}
|
||||
|
||||
var typ Tmp
|
||||
vin := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue(1, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
|
@ -360,7 +360,7 @@ func TestNormalizeMapNestedError(t *testing.T) {
|
|||
|
||||
func TestNormalizeMapVariableReference(t *testing.T) {
|
||||
var typ map[string]string
|
||||
vin := dyn.NewValue("${var.foo}", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue("${var.foo}", []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vout, err := Normalize(typ, vin)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, vin, vout)
|
||||
|
@ -368,7 +368,7 @@ func TestNormalizeMapVariableReference(t *testing.T) {
|
|||
|
||||
func TestNormalizeMapRandomStringError(t *testing.T) {
|
||||
var typ map[string]string
|
||||
vin := dyn.NewValue("var foo", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue("var foo", []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
|
@ -381,7 +381,7 @@ func TestNormalizeMapRandomStringError(t *testing.T) {
|
|||
|
||||
func TestNormalizeMapIntError(t *testing.T) {
|
||||
var typ map[string]string
|
||||
vin := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue(1, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
|
@ -482,7 +482,7 @@ func TestNormalizeSliceNestedError(t *testing.T) {
|
|||
|
||||
func TestNormalizeSliceVariableReference(t *testing.T) {
|
||||
var typ []string
|
||||
vin := dyn.NewValue("${var.foo}", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue("${var.foo}", []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vout, err := Normalize(typ, vin)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, vin, vout)
|
||||
|
@ -490,7 +490,7 @@ func TestNormalizeSliceVariableReference(t *testing.T) {
|
|||
|
||||
func TestNormalizeSliceRandomStringError(t *testing.T) {
|
||||
var typ []string
|
||||
vin := dyn.NewValue("var foo", dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue("var foo", []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
|
@ -503,7 +503,7 @@ func TestNormalizeSliceRandomStringError(t *testing.T) {
|
|||
|
||||
func TestNormalizeSliceIntError(t *testing.T) {
|
||||
var typ []string
|
||||
vin := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue(1, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
|
@ -524,7 +524,7 @@ func TestNormalizeString(t *testing.T) {
|
|||
|
||||
func TestNormalizeStringNil(t *testing.T) {
|
||||
var typ string
|
||||
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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{
|
||||
|
@ -537,26 +537,26 @@ func TestNormalizeStringNil(t *testing.T) {
|
|||
|
||||
func TestNormalizeStringFromBool(t *testing.T) {
|
||||
var typ string
|
||||
vin := dyn.NewValue(true, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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)
|
||||
assert.Equal(t, dyn.NewValue("true", vin.Locations()), vout)
|
||||
}
|
||||
|
||||
func TestNormalizeStringFromInt(t *testing.T) {
|
||||
var typ string
|
||||
vin := dyn.NewValue(123, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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)
|
||||
assert.Equal(t, dyn.NewValue("123", vin.Locations()), vout)
|
||||
}
|
||||
|
||||
func TestNormalizeStringFromFloat(t *testing.T) {
|
||||
var typ string
|
||||
vin := dyn.NewValue(1.20, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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)
|
||||
assert.Equal(t, dyn.NewValue("1.2", vin.Locations()), vout)
|
||||
}
|
||||
|
||||
func TestNormalizeStringError(t *testing.T) {
|
||||
|
@ -582,7 +582,7 @@ func TestNormalizeBool(t *testing.T) {
|
|||
|
||||
func TestNormalizeBoolNil(t *testing.T) {
|
||||
var typ bool
|
||||
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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{
|
||||
|
@ -658,7 +658,7 @@ func TestNormalizeInt(t *testing.T) {
|
|||
|
||||
func TestNormalizeIntNil(t *testing.T) {
|
||||
var typ int
|
||||
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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{
|
||||
|
@ -742,7 +742,7 @@ func TestNormalizeFloat(t *testing.T) {
|
|||
|
||||
func TestNormalizeFloatNil(t *testing.T) {
|
||||
var typ float64
|
||||
vin := dyn.NewValue(nil, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
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{
|
||||
|
@ -842,26 +842,26 @@ func TestNormalizeAnchors(t *testing.T) {
|
|||
|
||||
func TestNormalizeBoolToAny(t *testing.T) {
|
||||
var typ any
|
||||
vin := dyn.NewValue(false, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue(false, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vout, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 0)
|
||||
assert.Equal(t, dyn.NewValue(false, dyn.Location{File: "file", Line: 1, Column: 1}), vout)
|
||||
assert.Equal(t, dyn.NewValue(false, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
|
||||
}
|
||||
|
||||
func TestNormalizeIntToAny(t *testing.T) {
|
||||
var typ any
|
||||
vin := dyn.NewValue(10, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue(10, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vout, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 0)
|
||||
assert.Equal(t, dyn.NewValue(10, dyn.Location{File: "file", Line: 1, Column: 1}), vout)
|
||||
assert.Equal(t, dyn.NewValue(10, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
|
||||
}
|
||||
|
||||
func TestNormalizeSliceToAny(t *testing.T) {
|
||||
var typ any
|
||||
v1 := dyn.NewValue(1, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
v2 := dyn.NewValue(2, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
vin := dyn.NewValue([]dyn.Value{v1, v2}, dyn.Location{File: "file", Line: 1, Column: 1})
|
||||
v1 := dyn.NewValue(1, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
v2 := dyn.NewValue(2, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vin := dyn.NewValue([]dyn.Value{v1, v2}, []dyn.Location{{File: "file", Line: 1, Column: 1}})
|
||||
vout, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 0)
|
||||
assert.Equal(t, dyn.NewValue([]dyn.Value{v1, v2}, dyn.Location{File: "file", Line: 1, Column: 1}), vout)
|
||||
assert.Equal(t, dyn.NewValue([]dyn.Value{v1, v2}, []dyn.Location{{File: "file", Line: 1, Column: 1}}), vout)
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ func (r *resolver) resolveRef(ref ref, seen []string) (dyn.Value, error) {
|
|||
// of where it is used. This also means that relative path resolution is done
|
||||
// relative to where a variable is used, not where it is defined.
|
||||
//
|
||||
return dyn.NewValue(resolved[0].Value(), ref.value.Location()), nil
|
||||
return dyn.NewValue(resolved[0].Value(), ref.value.Locations()), nil
|
||||
}
|
||||
|
||||
// Not pure; perform string interpolation.
|
||||
|
@ -178,7 +178,7 @@ func (r *resolver) resolveRef(ref ref, seen []string) (dyn.Value, error) {
|
|||
ref.str = strings.Replace(ref.str, ref.matches[j][0], s, 1)
|
||||
}
|
||||
|
||||
return dyn.NewValue(ref.str, ref.value.Location()), nil
|
||||
return dyn.NewValue(ref.str, ref.value.Locations()), nil
|
||||
}
|
||||
|
||||
func (r *resolver) resolveKey(key string, seen []string) (dyn.Value, error) {
|
||||
|
|
|
@ -52,7 +52,7 @@ func (e elementsByKey) Map(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|||
out = append(out, nv)
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, v.Location()), nil
|
||||
return dyn.NewValue(out, v.Locations()), nil
|
||||
}
|
||||
|
||||
// ElementsByKey returns a [dyn.MapFunc] that operates on a sequence
|
||||
|
|
|
@ -12,6 +12,26 @@ import (
|
|||
// * Merging x with nil or nil with x always yields x.
|
||||
// * Merging maps a and b means entries from map b take precedence.
|
||||
// * Merging sequences a and b means concatenating them.
|
||||
//
|
||||
// Merging retains and accumulates the locations metadata associated with the values.
|
||||
// This allows users of the module to track the provenance of values across merging of
|
||||
// configuration trees, which is useful for reporting errors and warnings.
|
||||
//
|
||||
// Semantics for location metadata in the merged value are similar to the semantics
|
||||
// for the values themselves:
|
||||
//
|
||||
// - When merging x with nil or nil with x, the location of x is retained.
|
||||
//
|
||||
// - When merging maps or sequences, the combined value retains the location of a and
|
||||
// accumulates the location of b. The individual elements of the map or sequence retain
|
||||
// their original locations, i.e., whether they were originally defined in a or b.
|
||||
//
|
||||
// The rationale for retaining location of a is that we would like to return
|
||||
// the first location a bit of configuration showed up when reporting errors and warnings.
|
||||
//
|
||||
// - Merging primitive values means using the incoming value `b`. The location of the
|
||||
// incoming value is retained and the location of the existing value `a` is accumulated.
|
||||
// This is because the incoming value overwrites the existing value.
|
||||
func Merge(a, b dyn.Value) (dyn.Value, error) {
|
||||
return merge(a, b)
|
||||
}
|
||||
|
@ -22,12 +42,12 @@ func merge(a, b dyn.Value) (dyn.Value, error) {
|
|||
|
||||
// If a is nil, return b.
|
||||
if ak == dyn.KindNil {
|
||||
return b, nil
|
||||
return b.AppendLocationsFromValue(a), nil
|
||||
}
|
||||
|
||||
// If b is nil, return a.
|
||||
if bk == dyn.KindNil {
|
||||
return a, nil
|
||||
return a.AppendLocationsFromValue(b), nil
|
||||
}
|
||||
|
||||
// Call the appropriate merge function based on the kind of a and b.
|
||||
|
@ -75,8 +95,8 @@ func mergeMap(a, b dyn.Value) (dyn.Value, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Preserve the location of the first value.
|
||||
return dyn.NewValue(out, a.Location()), nil
|
||||
// Preserve the location of the first value. Accumulate the locations of the second value.
|
||||
return dyn.NewValue(out, a.Locations()).AppendLocationsFromValue(b), nil
|
||||
}
|
||||
|
||||
func mergeSequence(a, b dyn.Value) (dyn.Value, error) {
|
||||
|
@ -88,11 +108,10 @@ func mergeSequence(a, b dyn.Value) (dyn.Value, error) {
|
|||
copy(out[:], as)
|
||||
copy(out[len(as):], bs)
|
||||
|
||||
// Preserve the location of the first value.
|
||||
return dyn.NewValue(out, a.Location()), nil
|
||||
// Preserve the location of the first value. Accumulate the locations of the second value.
|
||||
return dyn.NewValue(out, a.Locations()).AppendLocationsFromValue(b), nil
|
||||
}
|
||||
|
||||
func mergePrimitive(a, b dyn.Value) (dyn.Value, error) {
|
||||
// Merging primitive values means using the incoming value.
|
||||
return b, nil
|
||||
return b.AppendLocationsFromValue(a), nil
|
||||
}
|
||||
|
|
|
@ -8,15 +8,17 @@ import (
|
|||
)
|
||||
|
||||
func TestMergeMaps(t *testing.T) {
|
||||
v1 := dyn.V(map[string]dyn.Value{
|
||||
"foo": dyn.V("bar"),
|
||||
"bar": dyn.V("baz"),
|
||||
})
|
||||
l1 := dyn.Location{File: "file1", Line: 1, Column: 2}
|
||||
v1 := dyn.NewValue(map[string]dyn.Value{
|
||||
"foo": dyn.NewValue("bar", []dyn.Location{l1}),
|
||||
"bar": dyn.NewValue("baz", []dyn.Location{l1}),
|
||||
}, []dyn.Location{l1})
|
||||
|
||||
v2 := dyn.V(map[string]dyn.Value{
|
||||
"bar": dyn.V("qux"),
|
||||
"qux": dyn.V("foo"),
|
||||
})
|
||||
l2 := dyn.Location{File: "file2", Line: 3, Column: 4}
|
||||
v2 := dyn.NewValue(map[string]dyn.Value{
|
||||
"bar": dyn.NewValue("qux", []dyn.Location{l2}),
|
||||
"qux": dyn.NewValue("foo", []dyn.Location{l2}),
|
||||
}, []dyn.Location{l2})
|
||||
|
||||
// Merge v2 into v1.
|
||||
{
|
||||
|
@ -27,6 +29,23 @@ func TestMergeMaps(t *testing.T) {
|
|||
"bar": "qux",
|
||||
"qux": "foo",
|
||||
}, out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l1, l2}, out.Locations())
|
||||
assert.Equal(t, []dyn.Location{l2, l1}, out.Get("bar").Locations())
|
||||
assert.Equal(t, []dyn.Location{l1}, out.Get("foo").Locations())
|
||||
assert.Equal(t, []dyn.Location{l2}, out.Get("qux").Locations())
|
||||
|
||||
// Location of the merged value should be the location of v1.
|
||||
assert.Equal(t, l1, out.Location())
|
||||
|
||||
// Value of bar is "qux" which comes from v2. This .Location() should
|
||||
// return the location of v2.
|
||||
assert.Equal(t, l2, out.Get("bar").Location())
|
||||
|
||||
// Original locations of keys that were not overwritten should be preserved.
|
||||
assert.Equal(t, l1, out.Get("foo").Location())
|
||||
assert.Equal(t, l2, out.Get("qux").Location())
|
||||
}
|
||||
|
||||
// Merge v1 into v2.
|
||||
|
@ -38,30 +57,64 @@ func TestMergeMaps(t *testing.T) {
|
|||
"bar": "baz",
|
||||
"qux": "foo",
|
||||
}, out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l2, l1}, out.Locations())
|
||||
assert.Equal(t, []dyn.Location{l1, l2}, out.Get("bar").Locations())
|
||||
assert.Equal(t, []dyn.Location{l1}, out.Get("foo").Locations())
|
||||
assert.Equal(t, []dyn.Location{l2}, out.Get("qux").Locations())
|
||||
|
||||
// Location of the merged value should be the location of v2.
|
||||
assert.Equal(t, l2, out.Location())
|
||||
|
||||
// Value of bar is "baz" which comes from v1. This .Location() should
|
||||
// return the location of v1.
|
||||
assert.Equal(t, l1, out.Get("bar").Location())
|
||||
|
||||
// Original locations of keys that were not overwritten should be preserved.
|
||||
assert.Equal(t, l1, out.Get("foo").Location())
|
||||
assert.Equal(t, l2, out.Get("qux").Location())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMergeMapsNil(t *testing.T) {
|
||||
v := dyn.V(map[string]dyn.Value{
|
||||
l := dyn.Location{File: "file", Line: 1, Column: 2}
|
||||
v := dyn.NewValue(map[string]dyn.Value{
|
||||
"foo": dyn.V("bar"),
|
||||
})
|
||||
}, []dyn.Location{l})
|
||||
|
||||
nilL := dyn.Location{File: "file", Line: 3, Column: 4}
|
||||
nilV := dyn.NewValue(nil, []dyn.Location{nilL})
|
||||
|
||||
// Merge nil into v.
|
||||
{
|
||||
out, err := Merge(v, dyn.NilValue)
|
||||
out, err := Merge(v, nilV)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]any{
|
||||
"foo": "bar",
|
||||
}, out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l, nilL}, out.Locations())
|
||||
|
||||
// Location of the non-nil value should be returned by .Location().
|
||||
assert.Equal(t, l, out.Location())
|
||||
}
|
||||
|
||||
// Merge v into nil.
|
||||
{
|
||||
out, err := Merge(dyn.NilValue, v)
|
||||
out, err := Merge(nilV, v)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]any{
|
||||
"foo": "bar",
|
||||
}, out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l, nilL}, out.Locations())
|
||||
|
||||
// Location of the non-nil value should be returned by .Location().
|
||||
assert.Equal(t, l, out.Location())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,15 +134,18 @@ func TestMergeMapsError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMergeSequences(t *testing.T) {
|
||||
v1 := dyn.V([]dyn.Value{
|
||||
dyn.V("bar"),
|
||||
dyn.V("baz"),
|
||||
})
|
||||
l1 := dyn.Location{File: "file1", Line: 1, Column: 2}
|
||||
v1 := dyn.NewValue([]dyn.Value{
|
||||
dyn.NewValue("bar", []dyn.Location{l1}),
|
||||
dyn.NewValue("baz", []dyn.Location{l1}),
|
||||
}, []dyn.Location{l1})
|
||||
|
||||
v2 := dyn.V([]dyn.Value{
|
||||
dyn.V("qux"),
|
||||
dyn.V("foo"),
|
||||
})
|
||||
l2 := dyn.Location{File: "file2", Line: 3, Column: 4}
|
||||
l3 := dyn.Location{File: "file3", Line: 5, Column: 6}
|
||||
v2 := dyn.NewValue([]dyn.Value{
|
||||
dyn.NewValue("qux", []dyn.Location{l2}),
|
||||
dyn.NewValue("foo", []dyn.Location{l3}),
|
||||
}, []dyn.Location{l2, l3})
|
||||
|
||||
// Merge v2 into v1.
|
||||
{
|
||||
|
@ -101,6 +157,18 @@ func TestMergeSequences(t *testing.T) {
|
|||
"qux",
|
||||
"foo",
|
||||
}, out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l1, l2, l3}, out.Locations())
|
||||
|
||||
// Location of the merged value should be the location of v1.
|
||||
assert.Equal(t, l1, out.Location())
|
||||
|
||||
// Location of the individual values should be preserved.
|
||||
assert.Equal(t, l1, out.Index(0).Location()) // "bar"
|
||||
assert.Equal(t, l1, out.Index(1).Location()) // "baz"
|
||||
assert.Equal(t, l2, out.Index(2).Location()) // "qux"
|
||||
assert.Equal(t, l3, out.Index(3).Location()) // "foo"
|
||||
}
|
||||
|
||||
// Merge v1 into v2.
|
||||
|
@ -113,6 +181,18 @@ func TestMergeSequences(t *testing.T) {
|
|||
"bar",
|
||||
"baz",
|
||||
}, out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l2, l3, l1}, out.Locations())
|
||||
|
||||
// Location of the merged value should be the location of v2.
|
||||
assert.Equal(t, l2, out.Location())
|
||||
|
||||
// Location of the individual values should be preserved.
|
||||
assert.Equal(t, l2, out.Index(0).Location()) // "qux"
|
||||
assert.Equal(t, l3, out.Index(1).Location()) // "foo"
|
||||
assert.Equal(t, l1, out.Index(2).Location()) // "bar"
|
||||
assert.Equal(t, l1, out.Index(3).Location()) // "baz"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,14 +236,22 @@ func TestMergeSequencesError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMergePrimitives(t *testing.T) {
|
||||
v1 := dyn.V("bar")
|
||||
v2 := dyn.V("baz")
|
||||
l1 := dyn.Location{File: "file1", Line: 1, Column: 2}
|
||||
l2 := dyn.Location{File: "file2", Line: 3, Column: 4}
|
||||
v1 := dyn.NewValue("bar", []dyn.Location{l1})
|
||||
v2 := dyn.NewValue("baz", []dyn.Location{l2})
|
||||
|
||||
// Merge v2 into v1.
|
||||
{
|
||||
out, err := Merge(v1, v2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "baz", out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l2, l1}, out.Locations())
|
||||
|
||||
// Location of the merged value should be the location of v2, the second value.
|
||||
assert.Equal(t, l2, out.Location())
|
||||
}
|
||||
|
||||
// Merge v1 into v2.
|
||||
|
@ -171,6 +259,12 @@ func TestMergePrimitives(t *testing.T) {
|
|||
out, err := Merge(v2, v1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "bar", out.AsAny())
|
||||
|
||||
// Locations of both values should be preserved.
|
||||
assert.Equal(t, []dyn.Location{l1, l2}, out.Locations())
|
||||
|
||||
// Location of the merged value should be the location of v1, the second value.
|
||||
assert.Equal(t, l1, out.Location())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ func override(basePath dyn.Path, left dyn.Value, right dyn.Value, visitor Overri
|
|||
return dyn.InvalidValue, err
|
||||
}
|
||||
|
||||
return dyn.NewValue(merged, left.Location()), nil
|
||||
return dyn.NewValue(merged, left.Locations()), nil
|
||||
|
||||
case dyn.KindSequence:
|
||||
// some sequences are keyed, and we can detect which elements are added/removed/updated,
|
||||
|
@ -62,7 +62,7 @@ func override(basePath dyn.Path, left dyn.Value, right dyn.Value, visitor Overri
|
|||
return dyn.InvalidValue, err
|
||||
}
|
||||
|
||||
return dyn.NewValue(merged, left.Location()), nil
|
||||
return dyn.NewValue(merged, left.Locations()), nil
|
||||
|
||||
case dyn.KindString:
|
||||
if left.MustString() == right.MustString() {
|
||||
|
|
|
@ -27,79 +27,79 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
{
|
||||
name: "string (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue("a", leftLocation),
|
||||
right: dyn.NewValue("b", rightLocation),
|
||||
expected: dyn.NewValue("b", rightLocation),
|
||||
left: dyn.NewValue("a", []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue("b", []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue("b", []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "string (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NewValue("a", leftLocation),
|
||||
right: dyn.NewValue("a", rightLocation),
|
||||
expected: dyn.NewValue("a", leftLocation),
|
||||
left: dyn.NewValue("a", []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue("a", []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue("a", []dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "bool (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue(true, leftLocation),
|
||||
right: dyn.NewValue(false, rightLocation),
|
||||
expected: dyn.NewValue(false, rightLocation),
|
||||
left: dyn.NewValue(true, []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(false, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(false, []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "bool (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NewValue(true, leftLocation),
|
||||
right: dyn.NewValue(true, rightLocation),
|
||||
expected: dyn.NewValue(true, leftLocation),
|
||||
left: dyn.NewValue(true, []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(true, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(true, []dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "int (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue(1, leftLocation),
|
||||
right: dyn.NewValue(2, rightLocation),
|
||||
expected: dyn.NewValue(2, rightLocation),
|
||||
left: dyn.NewValue(1, []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(2, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(2, []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "int (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NewValue(int32(1), leftLocation),
|
||||
right: dyn.NewValue(int64(1), rightLocation),
|
||||
expected: dyn.NewValue(int32(1), leftLocation),
|
||||
left: dyn.NewValue(int32(1), []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(int64(1), []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(int32(1), []dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "float (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue(1.0, leftLocation),
|
||||
right: dyn.NewValue(2.0, rightLocation),
|
||||
expected: dyn.NewValue(2.0, rightLocation),
|
||||
left: dyn.NewValue(1.0, []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(2.0, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(2.0, []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "float (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NewValue(float32(1.0), leftLocation),
|
||||
right: dyn.NewValue(float64(1.0), rightLocation),
|
||||
expected: dyn.NewValue(float32(1.0), leftLocation),
|
||||
left: dyn.NewValue(float32(1.0), []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(float64(1.0), []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(float32(1.0), []dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "time (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue(time.UnixMilli(10000), leftLocation),
|
||||
right: dyn.NewValue(time.UnixMilli(10001), rightLocation),
|
||||
expected: dyn.NewValue(time.UnixMilli(10001), rightLocation),
|
||||
left: dyn.NewValue(time.UnixMilli(10000), []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(time.UnixMilli(10001), []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(time.UnixMilli(10001), []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "time (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NewValue(time.UnixMilli(10000), leftLocation),
|
||||
right: dyn.NewValue(time.UnixMilli(10000), rightLocation),
|
||||
expected: dyn.NewValue(time.UnixMilli(10000), leftLocation),
|
||||
left: dyn.NewValue(time.UnixMilli(10000), []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(time.UnixMilli(10000), []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(time.UnixMilli(10000), []dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "different types (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue("a", leftLocation),
|
||||
right: dyn.NewValue(42, rightLocation),
|
||||
expected: dyn.NewValue(42, rightLocation),
|
||||
left: dyn.NewValue("a", []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "map - remove 'a', update 'b'",
|
||||
|
@ -109,23 +109,22 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
},
|
||||
left: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"a": dyn.NewValue(42, leftLocation),
|
||||
"b": dyn.NewValue(10, leftLocation),
|
||||
"a": dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
"b": dyn.NewValue(10, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
),
|
||||
[]dyn.Location{leftLocation}),
|
||||
|
||||
right: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"b": dyn.NewValue(20, rightLocation),
|
||||
"b": dyn.NewValue(20, []dyn.Location{rightLocation}),
|
||||
},
|
||||
rightLocation,
|
||||
),
|
||||
[]dyn.Location{rightLocation}),
|
||||
|
||||
expected: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"b": dyn.NewValue(20, rightLocation),
|
||||
"b": dyn.NewValue(20, []dyn.Location{rightLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
),
|
||||
[]dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "map - add 'a'",
|
||||
|
@ -134,24 +133,26 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
},
|
||||
left: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"b": dyn.NewValue(10, leftLocation),
|
||||
"b": dyn.NewValue(10, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
|
||||
right: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"a": dyn.NewValue(42, rightLocation),
|
||||
"b": dyn.NewValue(10, rightLocation),
|
||||
"a": dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
"b": dyn.NewValue(10, []dyn.Location{rightLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
|
||||
expected: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"a": dyn.NewValue(42, rightLocation),
|
||||
"a": dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
// location hasn't changed because value hasn't changed
|
||||
"b": dyn.NewValue(10, leftLocation),
|
||||
"b": dyn.NewValue(10, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -161,23 +162,25 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
},
|
||||
left: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"a": dyn.NewValue(42, leftLocation),
|
||||
"b": dyn.NewValue(10, leftLocation),
|
||||
"a": dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
"b": dyn.NewValue(10, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
|
||||
right: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"b": dyn.NewValue(10, rightLocation),
|
||||
"b": dyn.NewValue(10, []dyn.Location{rightLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
|
||||
expected: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
// location hasn't changed because value hasn't changed
|
||||
"b": dyn.NewValue(10, leftLocation),
|
||||
"b": dyn.NewValue(10, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -189,36 +192,38 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
map[string]dyn.Value{
|
||||
"jobs": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"job_0": dyn.NewValue(42, leftLocation),
|
||||
"job_0": dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
|
||||
right: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"jobs": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"job_0": dyn.NewValue(42, rightLocation),
|
||||
"job_1": dyn.NewValue(1337, rightLocation),
|
||||
"job_0": dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
"job_1": dyn.NewValue(1337, []dyn.Location{rightLocation}),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
|
||||
expected: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"jobs": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"job_0": dyn.NewValue(42, leftLocation),
|
||||
"job_1": dyn.NewValue(1337, rightLocation),
|
||||
"job_0": dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
"job_1": dyn.NewValue(1337, []dyn.Location{rightLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -228,35 +233,35 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
map[string]dyn.Value{
|
||||
"jobs": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"job_0": dyn.NewValue(42, leftLocation),
|
||||
"job_1": dyn.NewValue(1337, rightLocation),
|
||||
"job_0": dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
"job_1": dyn.NewValue(1337, []dyn.Location{rightLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
right: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"jobs": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"job_0": dyn.NewValue(42, rightLocation),
|
||||
"job_0": dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
expected: dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"jobs": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"job_0": dyn.NewValue(42, leftLocation),
|
||||
"job_0": dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -264,23 +269,23 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
state: visitorState{added: []string{"root[1]"}},
|
||||
left: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, leftLocation),
|
||||
dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
right: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, rightLocation),
|
||||
dyn.NewValue(10, rightLocation),
|
||||
dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
dyn.NewValue(10, []dyn.Location{rightLocation}),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
expected: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, leftLocation),
|
||||
dyn.NewValue(10, rightLocation),
|
||||
dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
dyn.NewValue(10, []dyn.Location{rightLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -288,67 +293,67 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
state: visitorState{removed: []string{"root[1]"}},
|
||||
left: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, leftLocation),
|
||||
dyn.NewValue(10, leftLocation),
|
||||
dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
dyn.NewValue(10, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
right: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, rightLocation),
|
||||
dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
expected: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
// location hasn't changed because value hasn't changed
|
||||
dyn.NewValue(42, leftLocation),
|
||||
dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
// location hasn't changed because value hasn't changed
|
||||
},
|
||||
{
|
||||
name: "sequence (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, leftLocation),
|
||||
dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
right: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, rightLocation),
|
||||
dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
},
|
||||
rightLocation,
|
||||
[]dyn.Location{rightLocation},
|
||||
),
|
||||
expected: dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue(42, leftLocation),
|
||||
dyn.NewValue(42, []dyn.Location{leftLocation}),
|
||||
},
|
||||
leftLocation,
|
||||
[]dyn.Location{leftLocation},
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "nil (not updated)",
|
||||
state: visitorState{},
|
||||
left: dyn.NilValue.WithLocation(leftLocation),
|
||||
right: dyn.NilValue.WithLocation(rightLocation),
|
||||
expected: dyn.NilValue.WithLocation(leftLocation),
|
||||
left: dyn.NilValue.WithLocations([]dyn.Location{leftLocation}),
|
||||
right: dyn.NilValue.WithLocations([]dyn.Location{rightLocation}),
|
||||
expected: dyn.NilValue.WithLocations([]dyn.Location{leftLocation}),
|
||||
},
|
||||
{
|
||||
name: "nil (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NilValue,
|
||||
right: dyn.NewValue(42, rightLocation),
|
||||
expected: dyn.NewValue(42, rightLocation),
|
||||
right: dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
},
|
||||
{
|
||||
name: "change kind (updated)",
|
||||
state: visitorState{updated: []string{"root"}},
|
||||
left: dyn.NewValue(42.0, leftLocation),
|
||||
right: dyn.NewValue(42, rightLocation),
|
||||
expected: dyn.NewValue(42, rightLocation),
|
||||
left: dyn.NewValue(42.0, []dyn.Location{leftLocation}),
|
||||
right: dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
expected: dyn.NewValue(42, []dyn.Location{rightLocation}),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -375,7 +380,7 @@ func TestOverride_Primitive(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run(tc.name+" - visitor overrides value", func(t *testing.T) {
|
||||
expected := dyn.NewValue("return value", dyn.Location{})
|
||||
expected := dyn.V("return value")
|
||||
s, visitor := createVisitor(visitorOpts{returnValue: &expected})
|
||||
out, err := override(dyn.EmptyPath, tc.left, tc.right, visitor)
|
||||
|
||||
|
@ -427,17 +432,17 @@ func TestOverride_PreserveMappingKeys(t *testing.T) {
|
|||
rightValueLocation := dyn.Location{File: "right.yml", Line: 3, Column: 1}
|
||||
|
||||
left := dyn.NewMapping()
|
||||
left.Set(dyn.NewValue("a", leftKeyLocation), dyn.NewValue(42, leftValueLocation))
|
||||
left.Set(dyn.NewValue("a", []dyn.Location{leftKeyLocation}), dyn.NewValue(42, []dyn.Location{leftValueLocation}))
|
||||
|
||||
right := dyn.NewMapping()
|
||||
right.Set(dyn.NewValue("a", rightKeyLocation), dyn.NewValue(7, rightValueLocation))
|
||||
right.Set(dyn.NewValue("a", []dyn.Location{rightKeyLocation}), dyn.NewValue(7, []dyn.Location{rightValueLocation}))
|
||||
|
||||
state, visitor := createVisitor(visitorOpts{})
|
||||
|
||||
out, err := override(
|
||||
dyn.EmptyPath,
|
||||
dyn.NewValue(left, leftLocation),
|
||||
dyn.NewValue(right, rightLocation),
|
||||
dyn.NewValue(left, []dyn.Location{leftLocation}),
|
||||
dyn.NewValue(right, []dyn.Location{rightLocation}),
|
||||
visitor,
|
||||
)
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ func (c anyKeyComponent) visit(v Value, prefix Path, suffix Pattern, opts visitO
|
|||
m.Set(pk, nv)
|
||||
}
|
||||
|
||||
return NewValue(m, v.Location()), nil
|
||||
return NewValue(m, v.Locations()), nil
|
||||
}
|
||||
|
||||
type anyIndexComponent struct{}
|
||||
|
@ -103,5 +103,5 @@ func (c anyIndexComponent) visit(v Value, prefix Path, suffix Pattern, opts visi
|
|||
s[i] = nv
|
||||
}
|
||||
|
||||
return NewValue(s, v.Location()), nil
|
||||
return NewValue(s, v.Locations()), nil
|
||||
}
|
||||
|
|
|
@ -2,13 +2,18 @@ package dyn
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Value struct {
|
||||
v any
|
||||
|
||||
k Kind
|
||||
l Location
|
||||
|
||||
// List of locations this value is defined at. The first location in the slice
|
||||
// is the location returned by the `.Location()` method and is typically used
|
||||
// for reporting errors and warnings associated with the value.
|
||||
l []Location
|
||||
|
||||
// Whether or not this value is an anchor.
|
||||
// If this node doesn't map to a type, we don't need to warn about it.
|
||||
|
@ -27,11 +32,11 @@ var NilValue = Value{
|
|||
|
||||
// V constructs a new Value with the given value.
|
||||
func V(v any) Value {
|
||||
return NewValue(v, Location{})
|
||||
return NewValue(v, []Location{})
|
||||
}
|
||||
|
||||
// NewValue constructs a new Value with the given value and location.
|
||||
func NewValue(v any, loc Location) Value {
|
||||
func NewValue(v any, loc []Location) Value {
|
||||
switch vin := v.(type) {
|
||||
case map[string]Value:
|
||||
v = newMappingFromGoMap(vin)
|
||||
|
@ -40,16 +45,30 @@ func NewValue(v any, loc Location) Value {
|
|||
return Value{
|
||||
v: v,
|
||||
k: kindOf(v),
|
||||
l: loc,
|
||||
|
||||
// create a copy of the locations, so that mutations to the original slice
|
||||
// don't affect new value.
|
||||
l: slices.Clone(loc),
|
||||
}
|
||||
}
|
||||
|
||||
// WithLocation returns a new Value with its location set to the given value.
|
||||
func (v Value) WithLocation(loc Location) Value {
|
||||
// WithLocations returns a new Value with its location set to the given value.
|
||||
func (v Value) WithLocations(loc []Location) Value {
|
||||
return Value{
|
||||
v: v.v,
|
||||
k: v.k,
|
||||
l: loc,
|
||||
|
||||
// create a copy of the locations, so that mutations to the original slice
|
||||
// don't affect new value.
|
||||
l: slices.Clone(loc),
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) AppendLocationsFromValue(w Value) Value {
|
||||
return Value{
|
||||
v: v.v,
|
||||
k: v.k,
|
||||
l: append(v.l, w.l...),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,10 +80,18 @@ func (v Value) Value() any {
|
|||
return v.v
|
||||
}
|
||||
|
||||
func (v Value) Location() Location {
|
||||
func (v Value) Locations() []Location {
|
||||
return v.l
|
||||
}
|
||||
|
||||
func (v Value) Location() Location {
|
||||
if len(v.l) == 0 {
|
||||
return Location{}
|
||||
}
|
||||
|
||||
return v.l[0]
|
||||
}
|
||||
|
||||
func (v Value) IsValid() bool {
|
||||
return v.k != KindInvalid
|
||||
}
|
||||
|
@ -153,7 +180,10 @@ func (v Value) IsAnchor() bool {
|
|||
// We need a custom implementation because maps and slices
|
||||
// cannot be compared with the regular == operator.
|
||||
func (v Value) eq(w Value) bool {
|
||||
if v.k != w.k || v.l != w.l {
|
||||
if v.k != w.k {
|
||||
return false
|
||||
}
|
||||
if !slices.Equal(v.l, w.l) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -25,16 +25,19 @@ func TestValueAsMap(t *testing.T) {
|
|||
_, ok := zeroValue.AsMap()
|
||||
assert.False(t, ok)
|
||||
|
||||
var intValue = dyn.NewValue(1, dyn.Location{})
|
||||
var intValue = dyn.V(1)
|
||||
_, ok = intValue.AsMap()
|
||||
assert.False(t, ok)
|
||||
|
||||
var mapValue = dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"key": dyn.NewValue("value", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
"key": dyn.NewValue(
|
||||
"value",
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
},
|
||||
dyn.Location{File: "file", Line: 1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}},
|
||||
)
|
||||
|
||||
m, ok := mapValue.AsMap()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, m.Len())
|
||||
|
@ -43,6 +46,6 @@ func TestValueAsMap(t *testing.T) {
|
|||
func TestValueIsValid(t *testing.T) {
|
||||
var zeroValue dyn.Value
|
||||
assert.False(t, zeroValue.IsValid())
|
||||
var intValue = dyn.NewValue(1, dyn.Location{})
|
||||
var intValue = dyn.V(1)
|
||||
assert.True(t, intValue.IsValid())
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
func TestValueUnderlyingMap(t *testing.T) {
|
||||
v := dyn.V(
|
||||
map[string]dyn.Value{
|
||||
"key": dyn.NewValue("value", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
"key": dyn.NewValue("value", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -33,7 +33,7 @@ func TestValueUnderlyingMap(t *testing.T) {
|
|||
func TestValueUnderlyingSequence(t *testing.T) {
|
||||
v := dyn.V(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue("value", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
dyn.NewValue("value", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ func Foreach(fn MapFunc) MapFunc {
|
|||
}
|
||||
m.Set(pk, nv)
|
||||
}
|
||||
return NewValue(m, v.Location()), nil
|
||||
return NewValue(m, v.Locations()), nil
|
||||
case KindSequence:
|
||||
s := slices.Clone(v.MustSequence())
|
||||
for i, value := range s {
|
||||
|
@ -37,7 +37,7 @@ func Foreach(fn MapFunc) MapFunc {
|
|||
return InvalidValue, err
|
||||
}
|
||||
}
|
||||
return NewValue(s, v.Location()), nil
|
||||
return NewValue(s, v.Locations()), nil
|
||||
default:
|
||||
return InvalidValue, fmt.Errorf("expected a map or sequence, found %s", v.Kind())
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ func (d *loader) loadSequence(node *yaml.Node, loc dyn.Location) (dyn.Value, err
|
|||
acc[i] = v
|
||||
}
|
||||
|
||||
return dyn.NewValue(acc, loc), nil
|
||||
return dyn.NewValue(acc, []dyn.Location{loc}), nil
|
||||
}
|
||||
|
||||
func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
|
||||
|
@ -130,7 +130,7 @@ func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, erro
|
|||
}
|
||||
|
||||
if merge == nil {
|
||||
return dyn.NewValue(acc, loc), nil
|
||||
return dyn.NewValue(acc, []dyn.Location{loc}), nil
|
||||
}
|
||||
|
||||
// Build location for the merge node.
|
||||
|
@ -171,20 +171,20 @@ func (d *loader) loadMapping(node *yaml.Node, loc dyn.Location) (dyn.Value, erro
|
|||
out.Merge(m)
|
||||
}
|
||||
|
||||
return dyn.NewValue(out, loc), nil
|
||||
return dyn.NewValue(out, []dyn.Location{loc}), nil
|
||||
}
|
||||
|
||||
func (d *loader) loadScalar(node *yaml.Node, loc dyn.Location) (dyn.Value, error) {
|
||||
st := node.ShortTag()
|
||||
switch st {
|
||||
case "!!str":
|
||||
return dyn.NewValue(node.Value, loc), nil
|
||||
return dyn.NewValue(node.Value, []dyn.Location{loc}), nil
|
||||
case "!!bool":
|
||||
switch strings.ToLower(node.Value) {
|
||||
case "true":
|
||||
return dyn.NewValue(true, loc), nil
|
||||
return dyn.NewValue(true, []dyn.Location{loc}), nil
|
||||
case "false":
|
||||
return dyn.NewValue(false, loc), nil
|
||||
return dyn.NewValue(false, []dyn.Location{loc}), nil
|
||||
default:
|
||||
return dyn.InvalidValue, errorf(loc, "invalid bool value: %v", node.Value)
|
||||
}
|
||||
|
@ -195,17 +195,17 @@ func (d *loader) loadScalar(node *yaml.Node, loc dyn.Location) (dyn.Value, error
|
|||
}
|
||||
// Use regular int type instead of int64 if possible.
|
||||
if i64 >= math.MinInt32 && i64 <= math.MaxInt32 {
|
||||
return dyn.NewValue(int(i64), loc), nil
|
||||
return dyn.NewValue(int(i64), []dyn.Location{loc}), nil
|
||||
}
|
||||
return dyn.NewValue(i64, loc), nil
|
||||
return dyn.NewValue(i64, []dyn.Location{loc}), nil
|
||||
case "!!float":
|
||||
f64, err := strconv.ParseFloat(node.Value, 64)
|
||||
if err != nil {
|
||||
return dyn.InvalidValue, errorf(loc, "invalid float value: %v", node.Value)
|
||||
}
|
||||
return dyn.NewValue(f64, loc), nil
|
||||
return dyn.NewValue(f64, []dyn.Location{loc}), nil
|
||||
case "!!null":
|
||||
return dyn.NewValue(nil, loc), nil
|
||||
return dyn.NewValue(nil, []dyn.Location{loc}), nil
|
||||
case "!!timestamp":
|
||||
// Try a couple of layouts
|
||||
for _, layout := range []string{
|
||||
|
@ -216,7 +216,7 @@ func (d *loader) loadScalar(node *yaml.Node, loc dyn.Location) (dyn.Value, error
|
|||
} {
|
||||
t, terr := time.Parse(layout, node.Value)
|
||||
if terr == nil {
|
||||
return dyn.NewValue(t, loc), nil
|
||||
return dyn.NewValue(t, []dyn.Location{loc}), nil
|
||||
}
|
||||
}
|
||||
return dyn.InvalidValue, errorf(loc, "invalid timestamp value: %v", node.Value)
|
||||
|
|
|
@ -19,7 +19,7 @@ func TestMarshalNilValue(t *testing.T) {
|
|||
|
||||
func TestMarshalIntValue(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var intValue = dyn.NewValue(1, dyn.Location{})
|
||||
var intValue = dyn.V(1)
|
||||
v, err := s.toYamlNode(intValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1", v.Value)
|
||||
|
@ -28,7 +28,7 @@ func TestMarshalIntValue(t *testing.T) {
|
|||
|
||||
func TestMarshalFloatValue(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var floatValue = dyn.NewValue(1.0, dyn.Location{})
|
||||
var floatValue = dyn.V(1.0)
|
||||
v, err := s.toYamlNode(floatValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1", v.Value)
|
||||
|
@ -37,7 +37,7 @@ func TestMarshalFloatValue(t *testing.T) {
|
|||
|
||||
func TestMarshalBoolValue(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var boolValue = dyn.NewValue(true, dyn.Location{})
|
||||
var boolValue = dyn.V(true)
|
||||
v, err := s.toYamlNode(boolValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "true", v.Value)
|
||||
|
@ -46,7 +46,7 @@ func TestMarshalBoolValue(t *testing.T) {
|
|||
|
||||
func TestMarshalTimeValue(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var timeValue = dyn.NewValue(time.Unix(0, 0), dyn.Location{})
|
||||
var timeValue = dyn.V(time.Unix(0, 0))
|
||||
v, err := s.toYamlNode(timeValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", v.Value)
|
||||
|
@ -57,10 +57,10 @@ func TestMarshalSequenceValue(t *testing.T) {
|
|||
s := NewSaver()
|
||||
var sequenceValue = dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.NewValue("value1", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
dyn.NewValue("value2", dyn.Location{File: "file", Line: 2, Column: 2}),
|
||||
dyn.NewValue("value1", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
dyn.NewValue("value2", []dyn.Location{{File: "file", Line: 2, Column: 2}}),
|
||||
},
|
||||
dyn.Location{File: "file", Line: 1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}},
|
||||
)
|
||||
v, err := s.toYamlNode(sequenceValue)
|
||||
assert.NoError(t, err)
|
||||
|
@ -71,7 +71,7 @@ func TestMarshalSequenceValue(t *testing.T) {
|
|||
|
||||
func TestMarshalStringValue(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var stringValue = dyn.NewValue("value", dyn.Location{})
|
||||
var stringValue = dyn.V("value")
|
||||
v, err := s.toYamlNode(stringValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "value", v.Value)
|
||||
|
@ -82,12 +82,13 @@ func TestMarshalMapValue(t *testing.T) {
|
|||
s := NewSaver()
|
||||
var mapValue = dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"key3": dyn.NewValue("value3", dyn.Location{File: "file", Line: 3, Column: 2}),
|
||||
"key2": dyn.NewValue("value2", dyn.Location{File: "file", Line: 2, Column: 2}),
|
||||
"key1": dyn.NewValue("value1", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
"key3": dyn.NewValue("value3", []dyn.Location{{File: "file", Line: 3, Column: 2}}),
|
||||
"key2": dyn.NewValue("value2", []dyn.Location{{File: "file", Line: 2, Column: 2}}),
|
||||
"key1": dyn.NewValue("value1", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
},
|
||||
dyn.Location{File: "file", Line: 1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}},
|
||||
)
|
||||
|
||||
v, err := s.toYamlNode(mapValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, yaml.MappingNode, v.Kind)
|
||||
|
@ -107,12 +108,12 @@ func TestMarshalNestedValues(t *testing.T) {
|
|||
map[string]dyn.Value{
|
||||
"key1": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"key2": dyn.NewValue("value", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
"key2": dyn.NewValue("value", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
},
|
||||
dyn.Location{File: "file", Line: 1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}},
|
||||
),
|
||||
},
|
||||
dyn.Location{File: "file", Line: 1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}},
|
||||
)
|
||||
v, err := s.toYamlNode(mapValue)
|
||||
assert.NoError(t, err)
|
||||
|
@ -125,14 +126,14 @@ func TestMarshalNestedValues(t *testing.T) {
|
|||
|
||||
func TestMarshalHexadecimalValueIsQuoted(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var hexValue = dyn.NewValue(0x123, dyn.Location{})
|
||||
var hexValue = dyn.V(0x123)
|
||||
v, err := s.toYamlNode(hexValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "291", v.Value)
|
||||
assert.Equal(t, yaml.Style(0), v.Style)
|
||||
assert.Equal(t, yaml.ScalarNode, v.Kind)
|
||||
|
||||
var stringValue = dyn.NewValue("0x123", dyn.Location{})
|
||||
var stringValue = dyn.V("0x123")
|
||||
v, err = s.toYamlNode(stringValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0x123", v.Value)
|
||||
|
@ -142,14 +143,14 @@ func TestMarshalHexadecimalValueIsQuoted(t *testing.T) {
|
|||
|
||||
func TestMarshalBinaryValueIsQuoted(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var binaryValue = dyn.NewValue(0b101, dyn.Location{})
|
||||
var binaryValue = dyn.V(0b101)
|
||||
v, err := s.toYamlNode(binaryValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "5", v.Value)
|
||||
assert.Equal(t, yaml.Style(0), v.Style)
|
||||
assert.Equal(t, yaml.ScalarNode, v.Kind)
|
||||
|
||||
var stringValue = dyn.NewValue("0b101", dyn.Location{})
|
||||
var stringValue = dyn.V("0b101")
|
||||
v, err = s.toYamlNode(stringValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0b101", v.Value)
|
||||
|
@ -159,14 +160,14 @@ func TestMarshalBinaryValueIsQuoted(t *testing.T) {
|
|||
|
||||
func TestMarshalOctalValueIsQuoted(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var octalValue = dyn.NewValue(0123, dyn.Location{})
|
||||
var octalValue = dyn.V(0123)
|
||||
v, err := s.toYamlNode(octalValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "83", v.Value)
|
||||
assert.Equal(t, yaml.Style(0), v.Style)
|
||||
assert.Equal(t, yaml.ScalarNode, v.Kind)
|
||||
|
||||
var stringValue = dyn.NewValue("0123", dyn.Location{})
|
||||
var stringValue = dyn.V("0123")
|
||||
v, err = s.toYamlNode(stringValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0123", v.Value)
|
||||
|
@ -176,14 +177,14 @@ func TestMarshalOctalValueIsQuoted(t *testing.T) {
|
|||
|
||||
func TestMarshalFloatValueIsQuoted(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var floatValue = dyn.NewValue(1.0, dyn.Location{})
|
||||
var floatValue = dyn.V(1.0)
|
||||
v, err := s.toYamlNode(floatValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1", v.Value)
|
||||
assert.Equal(t, yaml.Style(0), v.Style)
|
||||
assert.Equal(t, yaml.ScalarNode, v.Kind)
|
||||
|
||||
var stringValue = dyn.NewValue("1.0", dyn.Location{})
|
||||
var stringValue = dyn.V("1.0")
|
||||
v, err = s.toYamlNode(stringValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1.0", v.Value)
|
||||
|
@ -193,14 +194,14 @@ func TestMarshalFloatValueIsQuoted(t *testing.T) {
|
|||
|
||||
func TestMarshalBoolValueIsQuoted(t *testing.T) {
|
||||
s := NewSaver()
|
||||
var boolValue = dyn.NewValue(true, dyn.Location{})
|
||||
var boolValue = dyn.V(true)
|
||||
v, err := s.toYamlNode(boolValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "true", v.Value)
|
||||
assert.Equal(t, yaml.Style(0), v.Style)
|
||||
assert.Equal(t, yaml.ScalarNode, v.Kind)
|
||||
|
||||
var stringValue = dyn.NewValue("true", dyn.Location{})
|
||||
var stringValue = dyn.V("true")
|
||||
v, err = s.toYamlNode(stringValue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "true", v.Value)
|
||||
|
@ -215,18 +216,18 @@ func TestCustomStylingWithNestedMap(t *testing.T) {
|
|||
|
||||
var styledMap = dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"key1": dyn.NewValue("value1", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
"key2": dyn.NewValue("value2", dyn.Location{File: "file", Line: 2, Column: 2}),
|
||||
"key1": dyn.NewValue("value1", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
"key2": dyn.NewValue("value2", []dyn.Location{{File: "file", Line: 2, Column: 2}}),
|
||||
},
|
||||
dyn.Location{File: "file", Line: -2, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: -2, Column: 2}},
|
||||
)
|
||||
|
||||
var unstyledMap = dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"key3": dyn.NewValue("value3", dyn.Location{File: "file", Line: 1, Column: 2}),
|
||||
"key4": dyn.NewValue("value4", dyn.Location{File: "file", Line: 2, Column: 2}),
|
||||
"key3": dyn.NewValue("value3", []dyn.Location{{File: "file", Line: 1, Column: 2}}),
|
||||
"key4": dyn.NewValue("value4", []dyn.Location{{File: "file", Line: 2, Column: 2}}),
|
||||
},
|
||||
dyn.Location{File: "file", Line: -1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: -1, Column: 2}},
|
||||
)
|
||||
|
||||
var val = dyn.NewValue(
|
||||
|
@ -234,7 +235,7 @@ func TestCustomStylingWithNestedMap(t *testing.T) {
|
|||
"styled": styledMap,
|
||||
"unstyled": unstyledMap,
|
||||
},
|
||||
dyn.Location{File: "file", Line: 1, Column: 2},
|
||||
[]dyn.Location{{File: "file", Line: 1, Column: 2}},
|
||||
)
|
||||
|
||||
mv, err := s.toYamlNode(val)
|
||||
|
|
|
@ -44,7 +44,7 @@ func skipAndOrder(mv dyn.Value, order *Order, skipFields []string, dst map[strin
|
|||
continue
|
||||
}
|
||||
|
||||
dst[k] = dyn.NewValue(v.Value(), dyn.Location{Line: order.Get(k)})
|
||||
dst[k] = dyn.NewValue(v.Value(), []dyn.Location{{Line: order.Get(k)}})
|
||||
}
|
||||
|
||||
return dyn.V(dst), nil
|
||||
|
|
|
@ -33,16 +33,25 @@ func TestConvertToMapValueWithOrder(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, dyn.V(map[string]dyn.Value{
|
||||
"list": dyn.NewValue([]dyn.Value{
|
||||
"list": dyn.NewValue(
|
||||
[]dyn.Value{
|
||||
dyn.V("a"),
|
||||
dyn.V("b"),
|
||||
dyn.V("c"),
|
||||
}, dyn.Location{Line: -3}),
|
||||
"name": dyn.NewValue("test", dyn.Location{Line: -2}),
|
||||
"map": dyn.NewValue(map[string]dyn.Value{
|
||||
},
|
||||
[]dyn.Location{{Line: -3}},
|
||||
),
|
||||
"name": dyn.NewValue(
|
||||
"test",
|
||||
[]dyn.Location{{Line: -2}},
|
||||
),
|
||||
"map": dyn.NewValue(
|
||||
map[string]dyn.Value{
|
||||
"key1": dyn.V("value1"),
|
||||
"key2": dyn.V("value2"),
|
||||
}, dyn.Location{Line: -1}),
|
||||
"long_name_field": dyn.NewValue("long name goes here", dyn.Location{Line: 1}),
|
||||
},
|
||||
[]dyn.Location{{Line: -1}},
|
||||
),
|
||||
"long_name_field": dyn.NewValue("long name goes here", []dyn.Location{{Line: 1}}),
|
||||
}), result)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue