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 (
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/databricks/cli/libs/dyn"
|
2024-03-25 11:01:09 +00:00
|
|
|
assert "github.com/databricks/cli/libs/dyn/dynassert"
|
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 TestGetWithEmptyPath(t *testing.T) {
|
|
|
|
// An empty path means to return the value itself.
|
|
|
|
vin := dyn.V(42)
|
|
|
|
vout, err := dyn.GetByPath(vin, dyn.NewPath())
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, vin, vout)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetOnNilValue(t *testing.T) {
|
|
|
|
var err error
|
|
|
|
_, err = dyn.GetByPath(dyn.NilValue, dyn.NewPath(dyn.Key("foo")))
|
|
|
|
assert.ErrorContains(t, err, `expected a map to index "foo", found nil`)
|
|
|
|
_, err = dyn.GetByPath(dyn.NilValue, dyn.NewPath(dyn.Index(42)))
|
|
|
|
assert.ErrorContains(t, err, `expected a sequence to index "[42]", found nil`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetOnMap(t *testing.T) {
|
|
|
|
vin := dyn.V(map[string]dyn.Value{
|
|
|
|
"foo": dyn.V(42),
|
|
|
|
"bar": dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
_, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Index(42)))
|
|
|
|
assert.ErrorContains(t, err, `expected a sequence to index "[42]", found map`)
|
|
|
|
|
|
|
|
_, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Key("baz")))
|
|
|
|
assert.True(t, dyn.IsNoSuchKeyError(err))
|
|
|
|
assert.ErrorContains(t, err, `key not found at "baz"`)
|
|
|
|
|
|
|
|
vfoo, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Key("foo")))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, dyn.V(42), vfoo)
|
|
|
|
|
|
|
|
vbar, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Key("bar")))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, dyn.V(43), vbar)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetOnSequence(t *testing.T) {
|
|
|
|
vin := dyn.V([]dyn.Value{
|
|
|
|
dyn.V(42),
|
|
|
|
dyn.V(43),
|
|
|
|
})
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
_, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Key("foo")))
|
|
|
|
assert.ErrorContains(t, err, `expected a map to index "foo", found sequence`)
|
|
|
|
|
|
|
|
_, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Index(-1)))
|
|
|
|
assert.True(t, dyn.IsIndexOutOfBoundsError(err))
|
|
|
|
assert.ErrorContains(t, err, `index out of bounds at "[-1]"`)
|
|
|
|
|
|
|
|
_, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Index(2)))
|
|
|
|
assert.True(t, dyn.IsIndexOutOfBoundsError(err))
|
|
|
|
assert.ErrorContains(t, err, `index out of bounds at "[2]"`)
|
|
|
|
|
|
|
|
v0, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Index(0)))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, dyn.V(42), v0)
|
|
|
|
|
|
|
|
v1, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Index(1)))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, dyn.V(43), v1)
|
|
|
|
}
|