Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
package dyn_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/databricks/cli/libs/dyn"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestMapWithEmptyPath(t *testing.T) {
|
|
|
|
// An empty path means to return the value itself.
|
|
|
|
vin := dyn.V(42)
|
2024-03-07 13:56:50 +00:00
|
|
|
vout, err := dyn.MapByPath(dyn.InvalidValue, dyn.EmptyPath, func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
return vin, nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, vin, vout)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapOnNilValue(t *testing.T) {
|
|
|
|
var err error
|
|
|
|
_, err = dyn.MapByPath(dyn.NilValue, dyn.NewPath(dyn.Key("foo")), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a map to index "foo", found nil`)
|
|
|
|
_, err = dyn.MapByPath(dyn.NilValue, dyn.NewPath(dyn.Index(42)), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a sequence to index "[42]", found nil`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapFuncOnMap(t *testing.T) {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"foo": dyn.V(42),
|
|
|
|
"bar": dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
_, err = dyn.MapByPath(vin, dyn.NewPath(dyn.Index(42)), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a sequence to index "[42]", found map`)
|
|
|
|
|
|
|
|
// A key that does not exist is not an error.
|
|
|
|
vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("baz")), nil)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, vin, vout)
|
|
|
|
|
|
|
|
// Note: in the test cases below we implicitly test that the original
|
|
|
|
// value is not modified as we repeatedly set values on it.
|
2024-03-08 10:48:40 +00:00
|
|
|
vfoo, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("foo")), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
|
|
assert.Equal(t, dyn.NewPath(dyn.Key("foo")), p)
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
assert.Equal(t, dyn.V(42), v)
|
|
|
|
return dyn.V(44), nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, map[string]any{
|
|
|
|
"foo": 44,
|
|
|
|
"bar": 43,
|
|
|
|
}, vfoo.AsAny())
|
|
|
|
|
2024-03-08 10:48:40 +00:00
|
|
|
vbar, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("bar")), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
|
|
assert.Equal(t, dyn.NewPath(dyn.Key("bar")), p)
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
assert.Equal(t, dyn.V(43), v)
|
|
|
|
return dyn.V(45), nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, map[string]any{
|
|
|
|
"foo": 42,
|
|
|
|
"bar": 45,
|
|
|
|
}, vbar.AsAny())
|
|
|
|
|
|
|
|
// Return error from map function.
|
|
|
|
var ref = fmt.Errorf("error")
|
2024-03-07 13:56:50 +00:00
|
|
|
verr, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("foo")), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
return dyn.InvalidValue, ref
|
|
|
|
})
|
|
|
|
assert.Equal(t, dyn.InvalidValue, verr)
|
|
|
|
assert.ErrorIs(t, err, ref)
|
|
|
|
}
|
|
|
|
|
2024-02-05 16:54:41 +00:00
|
|
|
func TestMapFuncOnMapWithEmptySequence(t *testing.T) {
|
|
|
|
variants := []dyn.Value{
|
|
|
|
// empty sequence
|
|
|
|
dyn.V([]dyn.Value{}),
|
|
|
|
// non-empty sequence
|
|
|
|
dyn.V([]dyn.Value{dyn.V(42)}),
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(variants); i++ {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"key": variants[i],
|
|
|
|
})
|
|
|
|
|
|
|
|
for j := 0; j < len(variants); j++ {
|
2024-03-07 13:56:50 +00:00
|
|
|
vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("key")), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
2024-02-05 16:54:41 +00:00
|
|
|
return variants[j], nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, variants[j], vout.Get("key"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
func TestMapFuncOnSequence(t *testing.T) {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
dyn.V(42),
|
|
|
|
dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
_, err = dyn.MapByPath(vin, dyn.NewPath(dyn.Key("foo")), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a map to index "foo", found sequence`)
|
|
|
|
|
|
|
|
// An index that does not exist is not an error.
|
|
|
|
vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(2)), nil)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, vin, vout)
|
|
|
|
|
|
|
|
// Note: in the test cases below we implicitly test that the original
|
|
|
|
// value is not modified as we repeatedly set values on it.
|
2024-03-08 10:48:40 +00:00
|
|
|
v0, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(0)), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
|
|
assert.Equal(t, dyn.NewPath(dyn.Index(0)), p)
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
assert.Equal(t, dyn.V(42), v)
|
|
|
|
return dyn.V(44), nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []any{44, 43}, v0.AsAny())
|
|
|
|
|
2024-03-08 10:48:40 +00:00
|
|
|
v1, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(1)), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
|
|
assert.Equal(t, dyn.NewPath(dyn.Index(1)), p)
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
assert.Equal(t, dyn.V(43), v)
|
|
|
|
return dyn.V(45), nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []any{42, 45}, v1.AsAny())
|
|
|
|
|
|
|
|
// Return error from map function.
|
|
|
|
var ref = fmt.Errorf("error")
|
2024-03-07 13:56:50 +00:00
|
|
|
verr, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(0)), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
return dyn.InvalidValue, ref
|
|
|
|
})
|
|
|
|
assert.Equal(t, dyn.InvalidValue, verr)
|
|
|
|
assert.ErrorIs(t, err, ref)
|
|
|
|
}
|
|
|
|
|
2024-02-05 16:54:41 +00:00
|
|
|
func TestMapFuncOnSequenceWithEmptySequence(t *testing.T) {
|
|
|
|
variants := []dyn.Value{
|
|
|
|
// empty sequence
|
|
|
|
dyn.V([]dyn.Value{}),
|
|
|
|
// non-empty sequence
|
|
|
|
dyn.V([]dyn.Value{dyn.V(42)}),
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(variants); i++ {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
variants[i],
|
|
|
|
})
|
|
|
|
|
|
|
|
for j := 0; j < len(variants); j++ {
|
2024-03-07 13:56:50 +00:00
|
|
|
vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(0)), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
2024-02-05 16:54:41 +00:00
|
|
|
return variants[j], nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, variants[j], vout.Index(0))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
func TestMapForeachOnMap(t *testing.T) {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"foo": dyn.V(42),
|
|
|
|
"bar": dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Run foreach, adding 1 to each of the elements.
|
2024-03-07 13:56:50 +00:00
|
|
|
vout, err := dyn.Map(vin, ".", dyn.Foreach(func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
i, ok := v.AsInt()
|
|
|
|
require.True(t, ok, "expected an integer")
|
2024-03-07 13:56:50 +00:00
|
|
|
switch p[0].Key() {
|
|
|
|
case "foo":
|
|
|
|
assert.EqualValues(t, 42, i)
|
|
|
|
return dyn.V(43), nil
|
|
|
|
case "bar":
|
|
|
|
assert.EqualValues(t, 43, i)
|
|
|
|
return dyn.V(44), nil
|
|
|
|
default:
|
|
|
|
return dyn.InvalidValue, fmt.Errorf("unexpected key %q", p[0].Key())
|
|
|
|
}
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
}))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, map[string]any{
|
|
|
|
"foo": 43,
|
|
|
|
"bar": 44,
|
|
|
|
}, vout.AsAny())
|
|
|
|
|
|
|
|
// Check that the original has not been modified.
|
|
|
|
assert.Equal(t, map[string]any{
|
|
|
|
"foo": 42,
|
|
|
|
"bar": 43,
|
|
|
|
}, vin.AsAny())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapForeachOnMapError(t *testing.T) {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"foo": dyn.V(42),
|
|
|
|
"bar": dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
// Check that an error from the map function propagates.
|
|
|
|
var ref = fmt.Errorf("error")
|
2024-03-07 13:56:50 +00:00
|
|
|
_, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
return dyn.InvalidValue, ref
|
|
|
|
}))
|
|
|
|
assert.ErrorIs(t, err, ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapForeachOnSequence(t *testing.T) {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
dyn.V(42),
|
|
|
|
dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Run foreach, adding 1 to each of the elements.
|
2024-03-07 13:56:50 +00:00
|
|
|
vout, err := dyn.Map(vin, ".", dyn.Foreach(func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
i, ok := v.AsInt()
|
|
|
|
require.True(t, ok, "expected an integer")
|
2024-03-07 13:56:50 +00:00
|
|
|
switch p[0].Index() {
|
|
|
|
case 0:
|
|
|
|
assert.EqualValues(t, 42, i)
|
|
|
|
return dyn.V(43), nil
|
|
|
|
case 1:
|
|
|
|
assert.EqualValues(t, 43, i)
|
|
|
|
return dyn.V(44), nil
|
|
|
|
default:
|
|
|
|
return dyn.InvalidValue, fmt.Errorf("unexpected index %d", p[0].Index())
|
|
|
|
}
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
}))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []any{43, 44}, vout.AsAny())
|
|
|
|
|
|
|
|
// Check that the original has not been modified.
|
|
|
|
assert.Equal(t, []any{42, 43}, vin.AsAny())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapForeachOnSequenceError(t *testing.T) {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
dyn.V(42),
|
|
|
|
dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
// Check that an error from the map function propagates.
|
|
|
|
var ref = fmt.Errorf("error")
|
2024-03-07 13:56:50 +00:00
|
|
|
_, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
return dyn.InvalidValue, ref
|
|
|
|
}))
|
|
|
|
assert.ErrorIs(t, err, ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapForeachOnOtherError(t *testing.T) {
|
|
|
|
vin := dyn.V(42)
|
|
|
|
|
|
|
|
// Check that if foreach is applied to something other than a map or a sequence, it returns an error.
|
2024-03-07 13:56:50 +00:00
|
|
|
_, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) {
|
Add functionality to visit values in `dyn.Value` tree (#1142)
## Changes
This change adds the following functions:
* `dyn.Get(value, "foo.bar") -> (dyn.Value, error)`
* `dyn.Set(value, "foo.bar", newValue) -> (dyn.Value, error)`
* `dyn.Map(value, "foo.bar", func) -> (dyn.Value, error)`
And equivalent functions that take a previously constructed `dyn.Path`:
* `dyn.GetByPath(value, dyn.Path) -> (dyn.Value, error)`
* `dyn.SetByPath(value, dyn.Path, newValue) -> (dyn.Value, error)`
* `dyn.MapByPath(value, dyn.Path, func) -> (dyn.Value, error)`
Changes made by the "set" and "map" functions are never reflected in the
input argument; they return new `dyn.Value` instances for all nodes in
the path leading up to the changed value.
## Tests
New unit tests cover all critical paths.
2024-01-24 18:38:46 +00:00
|
|
|
return dyn.InvalidValue, nil
|
|
|
|
}))
|
|
|
|
assert.ErrorContains(t, err, "expected a map or sequence, found int")
|
|
|
|
}
|
2024-03-08 14:33:01 +00:00
|
|
|
|
|
|
|
func TestMapByPatternOnNilValue(t *testing.T) {
|
|
|
|
var err error
|
|
|
|
_, err = dyn.MapByPattern(dyn.NilValue, dyn.NewPattern(dyn.AnyKey()), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a map at "", found nil`)
|
|
|
|
_, err = dyn.MapByPattern(dyn.NilValue, dyn.NewPattern(dyn.AnyIndex()), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a sequence at "", found nil`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapByPatternOnMap(t *testing.T) {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"a": dyn.V(map[string]dyn.Value{
|
|
|
|
"b": dyn.V(42),
|
|
|
|
}),
|
|
|
|
"b": dyn.V(map[string]dyn.Value{
|
|
|
|
"c": dyn.V(43),
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Expect an error if the pattern structure doesn't match the value structure.
|
|
|
|
_, err = dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Index(0)), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a sequence to index`)
|
|
|
|
|
|
|
|
// Apply function to pattern "*.b".
|
|
|
|
vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Key("b")), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
|
|
assert.Equal(t, dyn.NewPath(dyn.Key("a"), dyn.Key("b")), p)
|
|
|
|
assert.Equal(t, dyn.V(42), v)
|
|
|
|
return dyn.V(44), nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, map[string]any{
|
|
|
|
"a": map[string]any{
|
|
|
|
"b": 44,
|
|
|
|
},
|
|
|
|
"b": map[string]any{
|
|
|
|
"c": 43,
|
|
|
|
},
|
|
|
|
}, vout.AsAny())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapByPatternOnMapWithoutMatch(t *testing.T) {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"a": dyn.V(map[string]dyn.Value{
|
|
|
|
"b": dyn.V(42),
|
|
|
|
}),
|
|
|
|
"b": dyn.V(map[string]dyn.Value{
|
|
|
|
"c": dyn.V(43),
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
// Apply function to pattern "*.zzz".
|
|
|
|
vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Key("zzz")), nil)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, vin, vout)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapByPatternOnSequence(t *testing.T) {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
dyn.V([]dyn.Value{
|
|
|
|
dyn.V(42),
|
|
|
|
}),
|
|
|
|
dyn.V([]dyn.Value{
|
|
|
|
dyn.V(43),
|
|
|
|
dyn.V(44),
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Expect an error if the pattern structure doesn't match the value structure.
|
|
|
|
_, err = dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyIndex(), dyn.Key("a")), nil)
|
|
|
|
assert.ErrorContains(t, err, `expected a map to index`)
|
|
|
|
|
|
|
|
// Apply function to pattern "*.c".
|
|
|
|
vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyIndex(), dyn.Index(1)), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
|
|
|
assert.Equal(t, dyn.NewPath(dyn.Index(1), dyn.Index(1)), p)
|
|
|
|
assert.Equal(t, dyn.V(44), v)
|
|
|
|
return dyn.V(45), nil
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, []any{
|
|
|
|
[]any{42},
|
|
|
|
[]any{43, 45},
|
|
|
|
}, vout.AsAny())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMapByPatternOnSequenceWithoutMatch(t *testing.T) {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
dyn.V([]dyn.Value{
|
|
|
|
dyn.V(42),
|
|
|
|
}),
|
|
|
|
dyn.V([]dyn.Value{
|
|
|
|
dyn.V(43),
|
|
|
|
dyn.V(44),
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
// Apply function to pattern "*.zzz".
|
|
|
|
vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyIndex(), dyn.Index(42)), nil)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, vin, vout)
|
|
|
|
}
|