mirror of https://github.com/databricks/cli.git
Functionality to walk a `config.Value` tree (#1081)
## Changes This change adds: * A `config.Walk` function to walk a configuration tree * A `config.Path` type to represent a value's path inside a tree * Functions to create a `config.Path` from a string, or convert one to a string ## Tests Additional unit tests with full coverage.
This commit is contained in:
parent
ac37a592f1
commit
a1297d71fd
|
@ -0,0 +1,96 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type pathComponent struct {
|
||||
key string
|
||||
index int
|
||||
}
|
||||
|
||||
// Path represents a path to a value in a [Value] configuration tree.
|
||||
type Path []pathComponent
|
||||
|
||||
// EmptyPath is the empty path.
|
||||
// It is defined for convenience and clarity.
|
||||
var EmptyPath = Path{}
|
||||
|
||||
// Key returns a path component for a key.
|
||||
func Key(k string) pathComponent {
|
||||
return pathComponent{key: k}
|
||||
}
|
||||
|
||||
// Index returns a path component for an index.
|
||||
func Index(i int) pathComponent {
|
||||
return pathComponent{index: i}
|
||||
}
|
||||
|
||||
// NewPath returns a new path from the given components.
|
||||
// The individual components may be created with [Key] or [Index].
|
||||
func NewPath(cs ...pathComponent) Path {
|
||||
return cs
|
||||
}
|
||||
|
||||
// Join joins the given paths.
|
||||
func (p Path) Join(qs ...Path) Path {
|
||||
for _, q := range qs {
|
||||
p = p.Append(q...)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Append appends the given components to the path.
|
||||
func (p Path) Append(cs ...pathComponent) Path {
|
||||
return append(p, cs...)
|
||||
}
|
||||
|
||||
// Equal returns true if the paths are equal.
|
||||
func (p Path) Equal(q Path) bool {
|
||||
pl := len(p)
|
||||
ql := len(q)
|
||||
if pl != ql {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < pl; i++ {
|
||||
if p[i] != q[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HasPrefix returns true if the path has the specified prefix.
|
||||
// The empty path is a prefix of all paths.
|
||||
func (p Path) HasPrefix(q Path) bool {
|
||||
pl := len(p)
|
||||
ql := len(q)
|
||||
if pl < ql {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < ql; i++ {
|
||||
if p[i] != q[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns a string representation of the path.
|
||||
func (p Path) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for i, c := range p {
|
||||
if i > 0 && c.key != "" {
|
||||
buf.WriteRune('.')
|
||||
}
|
||||
if c.key != "" {
|
||||
buf.WriteString(c.key)
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("[%d]", c.index))
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MustPathFromString is like NewPathFromString but panics on error.
|
||||
func MustPathFromString(input string) Path {
|
||||
p, err := NewPathFromString(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// NewPathFromString parses a path from a string.
|
||||
//
|
||||
// The string must be a sequence of keys and indices separated by dots.
|
||||
// Indices must be enclosed in square brackets.
|
||||
// The string may include a leading dot.
|
||||
//
|
||||
// Examples:
|
||||
// - foo.bar
|
||||
// - foo[1].bar
|
||||
// - foo.bar[1]
|
||||
// - foo.bar[1][2]
|
||||
// - .
|
||||
func NewPathFromString(input string) (Path, error) {
|
||||
var path Path
|
||||
|
||||
p := input
|
||||
|
||||
// Trim leading dot.
|
||||
if p != "" && p[0] == '.' {
|
||||
p = p[1:]
|
||||
}
|
||||
|
||||
for p != "" {
|
||||
// Every component may have a leading dot.
|
||||
if p != "" && p[0] == '.' {
|
||||
p = p[1:]
|
||||
}
|
||||
|
||||
if p == "" {
|
||||
return nil, fmt.Errorf("invalid path: %s", input)
|
||||
}
|
||||
|
||||
if p[0] == '[' {
|
||||
// Find next ]
|
||||
i := strings.Index(p, "]")
|
||||
if i < 0 {
|
||||
return nil, fmt.Errorf("invalid path: %s", input)
|
||||
}
|
||||
|
||||
// Parse index
|
||||
j, err := strconv.Atoi(p[1:i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid path: %s", input)
|
||||
}
|
||||
|
||||
// Append index
|
||||
path = append(path, Index(j))
|
||||
p = p[i+1:]
|
||||
|
||||
// The next character must be a . or [
|
||||
if p != "" && strings.IndexAny(p, ".[") != 0 {
|
||||
return nil, fmt.Errorf("invalid path: %s", input)
|
||||
}
|
||||
} else {
|
||||
// Find next . or [
|
||||
i := strings.IndexAny(p, ".[")
|
||||
if i < 0 {
|
||||
i = len(p)
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
return nil, fmt.Errorf("invalid path: %s", input)
|
||||
}
|
||||
|
||||
// Append key
|
||||
path = append(path, Key(p[:i]))
|
||||
p = p[i:]
|
||||
}
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/databricks/cli/libs/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewPathFromString(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
input string
|
||||
output Path
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
output: NewPath(),
|
||||
},
|
||||
{
|
||||
input: ".",
|
||||
output: NewPath(),
|
||||
},
|
||||
{
|
||||
input: "foo.bar",
|
||||
output: NewPath(Key("foo"), Key("bar")),
|
||||
},
|
||||
{
|
||||
input: "[1]",
|
||||
output: NewPath(Index(1)),
|
||||
},
|
||||
{
|
||||
input: "foo[1].bar",
|
||||
output: NewPath(Key("foo"), Index(1), Key("bar")),
|
||||
},
|
||||
{
|
||||
input: "foo.bar[1]",
|
||||
output: NewPath(Key("foo"), Key("bar"), Index(1)),
|
||||
},
|
||||
{
|
||||
input: "foo.bar[1][2]",
|
||||
output: NewPath(Key("foo"), Key("bar"), Index(1), Index(2)),
|
||||
},
|
||||
{
|
||||
input: "foo.bar[1][2][3]",
|
||||
output: NewPath(Key("foo"), Key("bar"), Index(1), Index(2), Index(3)),
|
||||
},
|
||||
{
|
||||
input: "foo[1234]",
|
||||
output: NewPath(Key("foo"), Index(1234)),
|
||||
},
|
||||
{
|
||||
input: "foo[123",
|
||||
err: fmt.Errorf("invalid path: foo[123"),
|
||||
},
|
||||
{
|
||||
input: "foo[123]]",
|
||||
err: fmt.Errorf("invalid path: foo[123]]"),
|
||||
},
|
||||
{
|
||||
input: "foo[[123]",
|
||||
err: fmt.Errorf("invalid path: foo[[123]"),
|
||||
},
|
||||
{
|
||||
input: "foo[[123]]",
|
||||
err: fmt.Errorf("invalid path: foo[[123]]"),
|
||||
},
|
||||
{
|
||||
input: "foo[foo]",
|
||||
err: fmt.Errorf("invalid path: foo[foo]"),
|
||||
},
|
||||
{
|
||||
input: "foo..bar",
|
||||
err: fmt.Errorf("invalid path: foo..bar"),
|
||||
},
|
||||
{
|
||||
input: "foo.bar.",
|
||||
err: fmt.Errorf("invalid path: foo.bar."),
|
||||
},
|
||||
{
|
||||
// Every component may have a leading dot.
|
||||
input: ".foo.[1].bar",
|
||||
output: NewPath(Key("foo"), Index(1), Key("bar")),
|
||||
},
|
||||
{
|
||||
// But after an index there must be a dot.
|
||||
input: "foo[1]bar",
|
||||
err: fmt.Errorf("invalid path: foo[1]bar"),
|
||||
},
|
||||
} {
|
||||
p, err := NewPathFromString(tc.input)
|
||||
if tc.err != nil {
|
||||
assert.EqualError(t, err, tc.err.Error(), tc.input)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.output, p)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/databricks/cli/libs/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPathAppend(t *testing.T) {
|
||||
p := config.NewPath(config.Key("foo"))
|
||||
|
||||
// Single arg.
|
||||
p1 := p.Append(config.Key("bar"))
|
||||
assert.True(t, p1.Equal(config.NewPath(config.Key("foo"), config.Key("bar"))))
|
||||
|
||||
// Multiple args.
|
||||
p2 := p.Append(config.Key("bar"), config.Index(1))
|
||||
assert.True(t, p2.Equal(config.NewPath(config.Key("foo"), config.Key("bar"), config.Index(1))))
|
||||
}
|
||||
|
||||
func TestPathJoin(t *testing.T) {
|
||||
p := config.NewPath(config.Key("foo"))
|
||||
|
||||
// Single arg.
|
||||
p1 := p.Join(config.NewPath(config.Key("bar")))
|
||||
assert.True(t, p1.Equal(config.NewPath(config.Key("foo"), config.Key("bar"))))
|
||||
|
||||
// Multiple args.
|
||||
p2 := p.Join(config.NewPath(config.Key("bar")), config.NewPath(config.Index(1)))
|
||||
assert.True(t, p2.Equal(config.NewPath(config.Key("foo"), config.Key("bar"), config.Index(1))))
|
||||
}
|
||||
|
||||
func TestPathEqualEmpty(t *testing.T) {
|
||||
assert.True(t, config.EmptyPath.Equal(config.EmptyPath))
|
||||
}
|
||||
|
||||
func TestPathEqual(t *testing.T) {
|
||||
p1 := config.NewPath(config.Key("foo"), config.Index(1))
|
||||
p2 := config.NewPath(config.Key("bar"), config.Index(2))
|
||||
assert.False(t, p1.Equal(p2), "expected %q to not equal %q", p1, p2)
|
||||
|
||||
p3 := config.NewPath(config.Key("foo"), config.Index(1))
|
||||
assert.True(t, p1.Equal(p3), "expected %q to equal %q", p1, p3)
|
||||
|
||||
p4 := config.NewPath(config.Key("foo"), config.Index(1), config.Key("bar"), config.Index(2))
|
||||
assert.False(t, p1.Equal(p4), "expected %q to not equal %q", p1, p4)
|
||||
}
|
||||
|
||||
func TestPathHasPrefixEmpty(t *testing.T) {
|
||||
empty := config.EmptyPath
|
||||
nonEmpty := config.NewPath(config.Key("foo"))
|
||||
assert.True(t, empty.HasPrefix(empty))
|
||||
assert.True(t, nonEmpty.HasPrefix(empty))
|
||||
assert.False(t, empty.HasPrefix(nonEmpty))
|
||||
}
|
||||
|
||||
func TestPathHasPrefix(t *testing.T) {
|
||||
p1 := config.NewPath(config.Key("foo"), config.Index(1))
|
||||
p2 := config.NewPath(config.Key("bar"), config.Index(2))
|
||||
assert.False(t, p1.HasPrefix(p2), "expected %q to not have prefix %q", p1, p2)
|
||||
|
||||
p3 := config.NewPath(config.Key("foo"))
|
||||
assert.True(t, p1.HasPrefix(p3), "expected %q to have prefix %q", p1, p3)
|
||||
}
|
||||
|
||||
func TestPathString(t *testing.T) {
|
||||
p1 := config.NewPath(config.Key("foo"), config.Index(1))
|
||||
assert.Equal(t, "foo[1]", p1.String())
|
||||
|
||||
p2 := config.NewPath(config.Key("bar"), config.Index(2), config.Key("baz"))
|
||||
assert.Equal(t, "bar[2].baz", p2.String())
|
||||
|
||||
p3 := config.NewPath(config.Key("foo"), config.Index(1), config.Key("bar"), config.Index(2), config.Key("baz"))
|
||||
assert.Equal(t, "foo[1].bar[2].baz", p3.String())
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package config
|
||||
|
||||
import "errors"
|
||||
|
||||
// WalkValueFunc is the type of the function called by Walk to traverse the configuration tree.
|
||||
type WalkValueFunc func(p Path, v Value) (Value, error)
|
||||
|
||||
// ErrDrop may be returned by WalkValueFunc to remove a value from the subtree.
|
||||
var ErrDrop = errors.New("drop value from subtree")
|
||||
|
||||
// ErrSkip may be returned by WalkValueFunc to skip traversal of a subtree.
|
||||
var ErrSkip = errors.New("skip traversal of subtree")
|
||||
|
||||
// Walk walks the configuration tree and calls the given function on each node.
|
||||
// The callback may return ErrDrop to remove a value from the subtree.
|
||||
// The callback may return ErrSkip to skip traversal of a subtree.
|
||||
// If the callback returns another error, the walk is aborted, and the error is returned.
|
||||
func Walk(v Value, fn func(p Path, v Value) (Value, error)) (Value, error) {
|
||||
return walk(v, EmptyPath, fn)
|
||||
}
|
||||
|
||||
// Unexported counterpart to Walk.
|
||||
// It carries the path leading up to the current node,
|
||||
// such that it can be passed to the WalkValueFunc.
|
||||
func walk(v Value, p Path, fn func(p Path, v Value) (Value, error)) (Value, error) {
|
||||
v, err := fn(p, v)
|
||||
if err != nil {
|
||||
if err == ErrSkip {
|
||||
return v, nil
|
||||
}
|
||||
return NilValue, err
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case KindMap:
|
||||
m := v.MustMap()
|
||||
out := make(map[string]Value, len(m))
|
||||
for k := range m {
|
||||
nv, err := walk(m[k], p.Append(Key(k)), fn)
|
||||
if err == ErrDrop {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
out[k] = nv
|
||||
}
|
||||
v.v = out
|
||||
case KindSequence:
|
||||
s := v.MustSequence()
|
||||
out := make([]Value, 0, len(s))
|
||||
for i := range s {
|
||||
nv, err := walk(s[i], p.Append(Index(i)), fn)
|
||||
if err == ErrDrop {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return NilValue, err
|
||||
}
|
||||
out = append(out, nv)
|
||||
}
|
||||
v.v = out
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
. "github.com/databricks/cli/libs/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Return values for specific paths.
|
||||
type walkReturn struct {
|
||||
path Path
|
||||
|
||||
// Return values.
|
||||
fn func(Value) Value
|
||||
err error
|
||||
}
|
||||
|
||||
// Track the calls to the callback.
|
||||
type walkCall struct {
|
||||
path Path
|
||||
value Value
|
||||
}
|
||||
|
||||
// Track the calls to the callback.
|
||||
type walkCallTracker struct {
|
||||
returns []walkReturn
|
||||
calls []walkCall
|
||||
}
|
||||
|
||||
func (w *walkCallTracker) on(path string, fn func(Value) Value, err error) {
|
||||
w.returns = append(w.returns, walkReturn{MustPathFromString(path), fn, err})
|
||||
}
|
||||
|
||||
func (w *walkCallTracker) returnSkip(path string) {
|
||||
w.on(path, func(v Value) Value { return v }, ErrSkip)
|
||||
}
|
||||
|
||||
func (w *walkCallTracker) returnDrop(path string) {
|
||||
w.on(path, func(v Value) Value { return NilValue }, ErrDrop)
|
||||
}
|
||||
|
||||
func (w *walkCallTracker) track(p Path, v Value) (Value, error) {
|
||||
w.calls = append(w.calls, walkCall{p, v})
|
||||
|
||||
// Look for matching return.
|
||||
for _, r := range w.returns {
|
||||
if p.Equal(r.path) {
|
||||
return r.fn(v), r.err
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func TestWalkEmpty(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
value := V(nil)
|
||||
out, err := Walk(value, tracker.track)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, value, out)
|
||||
|
||||
// The callback should have been called once.
|
||||
assert.Len(t, tracker.calls, 1)
|
||||
|
||||
// The call should have been made with the empty path.
|
||||
assert.Equal(t, EmptyPath, tracker.calls[0].path)
|
||||
|
||||
// The value should be the same as the input.
|
||||
assert.Equal(t, value, tracker.calls[0].value)
|
||||
}
|
||||
|
||||
func TestWalkMapSkip(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
// Skip traversal of the root value.
|
||||
tracker.returnSkip(".")
|
||||
|
||||
value := V(map[string]Value{
|
||||
"key": V("value"),
|
||||
})
|
||||
out, err := Walk(value, tracker.track)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
V(map[string]Value{
|
||||
"key": V("value"),
|
||||
}),
|
||||
out,
|
||||
)
|
||||
|
||||
// The callback should have been called once.
|
||||
assert.Len(t, tracker.calls, 1)
|
||||
|
||||
// The call should have been made with the empty path.
|
||||
assert.Equal(t, EmptyPath, tracker.calls[0].path)
|
||||
|
||||
// The value should be the same as the input.
|
||||
assert.Equal(t, value, tracker.calls[0].value)
|
||||
}
|
||||
|
||||
func TestWalkMapDrop(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
// Drop the value at key "foo".
|
||||
tracker.returnDrop(".foo")
|
||||
|
||||
value := V(map[string]Value{
|
||||
"foo": V("bar"),
|
||||
"bar": V("baz"),
|
||||
})
|
||||
out, err := Walk(value, tracker.track)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
V(map[string]Value{
|
||||
"bar": V("baz"),
|
||||
}),
|
||||
out,
|
||||
)
|
||||
|
||||
// The callback should have been called for the root and every key in the map.
|
||||
assert.Len(t, tracker.calls, 3)
|
||||
|
||||
// Calls 2 and 3 have been made for the keys in the map.
|
||||
assert.ElementsMatch(t,
|
||||
[]Path{
|
||||
tracker.calls[1].path,
|
||||
tracker.calls[2].path,
|
||||
}, []Path{
|
||||
MustPathFromString(".foo"),
|
||||
MustPathFromString(".bar"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalkMapError(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
// Return an error from the callback for key "foo".
|
||||
cerr := errors.New("error!")
|
||||
tracker.on(".foo", func(v Value) Value { return v }, cerr)
|
||||
|
||||
value := V(map[string]Value{
|
||||
"foo": V("bar"),
|
||||
})
|
||||
out, err := Walk(value, tracker.track)
|
||||
assert.Equal(t, cerr, err)
|
||||
assert.Equal(t, NilValue, out)
|
||||
|
||||
// The callback should have been called twice.
|
||||
assert.Len(t, tracker.calls, 2)
|
||||
|
||||
// The second call was for the value at key "foo".
|
||||
assert.Equal(t, MustPathFromString(".foo"), tracker.calls[1].path)
|
||||
}
|
||||
|
||||
func TestWalkSequenceSkip(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
// Skip traversal of the root value.
|
||||
tracker.returnSkip(".")
|
||||
|
||||
value := V([]Value{
|
||||
V("foo"),
|
||||
V("bar"),
|
||||
})
|
||||
out, err := Walk(value, tracker.track)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
V([]Value{
|
||||
V("foo"),
|
||||
V("bar"),
|
||||
}),
|
||||
out,
|
||||
)
|
||||
|
||||
// The callback should have been called once.
|
||||
assert.Len(t, tracker.calls, 1)
|
||||
|
||||
// The call should have been made with the empty path.
|
||||
assert.Equal(t, EmptyPath, tracker.calls[0].path)
|
||||
|
||||
// The value should be the same as the input.
|
||||
assert.Equal(t, value, tracker.calls[0].value)
|
||||
}
|
||||
|
||||
func TestWalkSequenceDrop(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
// Drop the value at index 1.
|
||||
tracker.returnDrop(".[1]")
|
||||
|
||||
value := V([]Value{
|
||||
V("foo"),
|
||||
V("bar"),
|
||||
V("baz"),
|
||||
})
|
||||
out, err := Walk(value, tracker.track)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
V([]Value{
|
||||
V("foo"),
|
||||
V("baz"),
|
||||
}),
|
||||
out,
|
||||
)
|
||||
|
||||
// The callback should have been called for the root and every value in the sequence.
|
||||
assert.Len(t, tracker.calls, 4)
|
||||
|
||||
// The second call was for the value at index 0.
|
||||
assert.Equal(t, MustPathFromString(".[0]"), tracker.calls[1].path)
|
||||
assert.Equal(t, V("foo"), tracker.calls[1].value)
|
||||
|
||||
// The third call was for the value at index 1.
|
||||
assert.Equal(t, MustPathFromString(".[1]"), tracker.calls[2].path)
|
||||
assert.Equal(t, V("bar"), tracker.calls[2].value)
|
||||
|
||||
// The fourth call was for the value at index 2.
|
||||
assert.Equal(t, MustPathFromString(".[2]"), tracker.calls[3].path)
|
||||
assert.Equal(t, V("baz"), tracker.calls[3].value)
|
||||
}
|
||||
|
||||
func TestWalkSequenceError(t *testing.T) {
|
||||
var tracker walkCallTracker
|
||||
|
||||
// Return an error from the callback for index 1.
|
||||
cerr := errors.New("error!")
|
||||
tracker.on(".[1]", func(v Value) Value { return v }, cerr)
|
||||
|
||||
value := V([]Value{
|
||||
V("foo"),
|
||||
V("bar"),
|
||||
})
|
||||
out, err := Walk(value, tracker.track)
|
||||
assert.Equal(t, cerr, err)
|
||||
assert.Equal(t, NilValue, out)
|
||||
|
||||
// The callback should have been called three times.
|
||||
assert.Len(t, tracker.calls, 3)
|
||||
|
||||
// The second call was for the value at index 0.
|
||||
assert.Equal(t, MustPathFromString(".[0]"), tracker.calls[1].path)
|
||||
assert.Equal(t, V("foo"), tracker.calls[1].value)
|
||||
|
||||
// The third call was for the value at index 1.
|
||||
assert.Equal(t, MustPathFromString(".[1]"), tracker.calls[2].path)
|
||||
assert.Equal(t, V("bar"), tracker.calls[2].value)
|
||||
}
|
Loading…
Reference in New Issue