mirror of https://github.com/databricks/cli.git
Pass copy of `dyn.Path` to callback function (#1747)
## Changes Some call sites hold on to the `dyn.Path` provided to them by the callback. It must therefore never be mutated after the callback returns, or these mutations leak out into unknown scope. This change means it is no longer possible for this failure mode to happen. ## Tests Unit test.
This commit is contained in:
parent
f71d9e7649
commit
ceefa80d72
|
@ -33,12 +33,7 @@ func createGlobError(v dyn.Value, p dyn.Path, message string) diag.Diagnostic {
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: fmt.Sprintf("%s: %s", source, message),
|
Summary: fmt.Sprintf("%s: %s", source, message),
|
||||||
Locations: []dyn.Location{v.Location()},
|
Locations: []dyn.Location{v.Location()},
|
||||||
|
Paths: []dyn.Path{p},
|
||||||
Paths: []dyn.Path{
|
|
||||||
// Hack to clone the path. This path copy is mutable.
|
|
||||||
// To be addressed in a later PR.
|
|
||||||
p.Append(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package validate
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
|
@ -66,10 +65,7 @@ func (m *uniqueResourceKeys) Apply(ctx context.Context, b *bundle.Bundle) diag.D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dyn.Path under the hood is a slice. The code that walks the configuration
|
m.paths = append(m.paths, p)
|
||||||
// tree uses the same underlying slice to track the path as it walks
|
|
||||||
// the tree. So, we need to clone it here.
|
|
||||||
m.paths = append(m.paths, slices.Clone(p))
|
|
||||||
m.locations = append(m.locations, v.Locations()...)
|
m.locations = append(m.locations, v.Locations()...)
|
||||||
|
|
||||||
resourceMetadata[k] = m
|
resourceMetadata[k] = m
|
||||||
|
|
|
@ -16,12 +16,10 @@ type expand struct {
|
||||||
|
|
||||||
func matchError(p dyn.Path, l []dyn.Location, message string) diag.Diagnostic {
|
func matchError(p dyn.Path, l []dyn.Location, message string) diag.Diagnostic {
|
||||||
return diag.Diagnostic{
|
return diag.Diagnostic{
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: message,
|
Summary: message,
|
||||||
Paths: []dyn.Path{
|
|
||||||
p.Append(),
|
|
||||||
},
|
|
||||||
Locations: l,
|
Locations: l,
|
||||||
|
Paths: []dyn.Path{p},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ func collectLocalLibraries(b *bundle.Bundle) (map[string][]configLocation, error
|
||||||
|
|
||||||
source = filepath.Join(b.RootPath, source)
|
source = filepath.Join(b.RootPath, source)
|
||||||
libs[source] = append(libs[source], configLocation{
|
libs[source] = append(libs[source], configLocation{
|
||||||
configPath: p.Append(), // Hack to get the copy of path
|
configPath: p,
|
||||||
location: v.Location(),
|
location: v.Location(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ type visitOptions struct {
|
||||||
|
|
||||||
func visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
|
func visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
|
||||||
if len(suffix) == 0 {
|
if len(suffix) == 0 {
|
||||||
return opts.fn(prefix, v)
|
return opts.fn(slices.Clone(prefix), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize prefix if it is empty.
|
// Initialize prefix if it is empty.
|
||||||
|
|
|
@ -21,7 +21,7 @@ func Foreach(fn MapFunc) MapFunc {
|
||||||
for _, pair := range m.Pairs() {
|
for _, pair := range m.Pairs() {
|
||||||
pk := pair.Key
|
pk := pair.Key
|
||||||
pv := pair.Value
|
pv := pair.Value
|
||||||
nv, err := fn(append(p, Key(pk.MustString())), pv)
|
nv, err := fn(p.Append(Key(pk.MustString())), pv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return InvalidValue, err
|
return InvalidValue, err
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func Foreach(fn MapFunc) MapFunc {
|
||||||
s := slices.Clone(v.MustSequence())
|
s := slices.Clone(v.MustSequence())
|
||||||
for i, value := range s {
|
for i, value := range s {
|
||||||
var err error
|
var err error
|
||||||
s[i], err = fn(append(p, Index(i)), value)
|
s[i], err = fn(p.Append(Index(i)), value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return InvalidValue, err
|
return InvalidValue, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dyn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/libs/dyn"
|
||||||
|
assert "github.com/databricks/cli/libs/dyn/dynassert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVisitCallbackPathCopy(t *testing.T) {
|
||||||
|
vin := dyn.V(map[string]dyn.Value{
|
||||||
|
"foo": dyn.V(42),
|
||||||
|
"bar": dyn.V(43),
|
||||||
|
})
|
||||||
|
|
||||||
|
var paths []dyn.Path
|
||||||
|
|
||||||
|
// The callback should receive a copy of the path.
|
||||||
|
// If the same underlying value is used, all collected paths will be the same.
|
||||||
|
// This test uses `MapByPattern` to collect all paths in the map.
|
||||||
|
// Visit itself doesn't have public functions and we exclusively use black-box testing for this package.
|
||||||
|
_, _ = dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey()), func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
|
||||||
|
paths = append(paths, p)
|
||||||
|
return v, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify that the paths retained their original values.
|
||||||
|
var strings []string
|
||||||
|
for _, p := range paths {
|
||||||
|
strings = append(strings, p.String())
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, strings, []string{
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue