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
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"slices"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MapFunc is a function that maps a value to another value.
|
2024-03-07 13:56:50 +00:00
|
|
|
|
type MapFunc func(Path, Value) (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
|
|
|
|
|
|
|
|
|
// Foreach returns a [MapFunc] that applies the specified [MapFunc] to each
|
|
|
|
|
// value in a map or sequence and returns the new map or sequence.
|
2024-07-01 13:00:31 +00:00
|
|
|
|
// If the input is nil, it returns nil.
|
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 Foreach(fn MapFunc) MapFunc {
|
2024-03-07 13:56:50 +00:00
|
|
|
|
return func(p Path, v Value) (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
|
|
|
|
switch v.Kind() {
|
2024-07-01 13:00:31 +00:00
|
|
|
|
case KindNil:
|
|
|
|
|
return v, nil
|
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
|
|
|
|
case KindMap:
|
2024-03-25 11:01:09 +00:00
|
|
|
|
m := v.MustMap().Clone()
|
|
|
|
|
for _, pair := range m.Pairs() {
|
|
|
|
|
pk := pair.Key
|
|
|
|
|
pv := pair.Value
|
|
|
|
|
nv, err := fn(append(p, Key(pk.MustString())), pv)
|
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
|
|
|
|
if err != nil {
|
|
|
|
|
return InvalidValue, err
|
|
|
|
|
}
|
2024-03-25 11:01:09 +00:00
|
|
|
|
m.Set(pk, nv)
|
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 NewValue(m, v.Location()), nil
|
|
|
|
|
case KindSequence:
|
|
|
|
|
s := slices.Clone(v.MustSequence())
|
|
|
|
|
for i, value := range s {
|
|
|
|
|
var err error
|
2024-03-19 09:49:26 +00:00
|
|
|
|
s[i], err = fn(append(p, Index(i)), value)
|
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
|
|
|
|
if err != nil {
|
|
|
|
|
return InvalidValue, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NewValue(s, v.Location()), nil
|
|
|
|
|
default:
|
|
|
|
|
return InvalidValue, fmt.Errorf("expected a map or sequence, found %s", v.Kind())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 14:33:01 +00:00
|
|
|
|
// Map applies a function to the value at the given path in the given value.
|
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
|
|
|
|
// It is identical to [MapByPath], except that it takes a string path instead of a [Path].
|
|
|
|
|
func Map(v Value, path string, fn MapFunc) (Value, error) {
|
|
|
|
|
p, err := NewPathFromString(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return InvalidValue, err
|
|
|
|
|
}
|
|
|
|
|
return MapByPath(v, p, fn)
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 14:33:01 +00:00
|
|
|
|
// MapByPath applies a function to the value at the given path in the given value.
|
|
|
|
|
// It is identical to [MapByPattern], except that it takes a [Path] instead of a [Pattern].
|
|
|
|
|
// This means it only matches a single value, not a pattern of values.
|
|
|
|
|
func MapByPath(v Value, p Path, fn MapFunc) (Value, error) {
|
|
|
|
|
return MapByPattern(v, NewPatternFromPath(p), fn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MapByPattern applies a function to the values whose paths match the given pattern in the given value.
|
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
|
|
|
|
// If successful, it returns the new value with all intermediate values copied and updated.
|
|
|
|
|
//
|
2024-03-08 14:33:01 +00:00
|
|
|
|
// If the pattern contains a key that doesn't exist, or an index that is out of bounds,
|
|
|
|
|
// it returns the original value and no 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
|
|
|
|
//
|
2024-03-08 14:33:01 +00:00
|
|
|
|
// If the pattern is invalid for the given value, it returns InvalidValue and an error.
|
|
|
|
|
func MapByPattern(v Value, p Pattern, fn MapFunc) (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
|
|
|
|
nv, err := visit(v, EmptyPath, p, visitOptions{
|
|
|
|
|
fn: fn,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Check for success.
|
|
|
|
|
if err == nil {
|
|
|
|
|
return nv, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 13:00:31 +00:00
|
|
|
|
// Return original value if:
|
|
|
|
|
// - any map or sequence is a nil, or
|
|
|
|
|
// - a key or index is missing
|
|
|
|
|
if IsCannotTraverseNilError(err) || IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) {
|
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 v, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nv, err
|
|
|
|
|
}
|