Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline logic to set a value in dyn.SetByPath #1261

Merged
merged 3 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions libs/dyn/visit.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ type visitOptions struct {
// If this function returns an error, the original visit function call
// returns this error and the value is left unmodified.
fn func(Path, Value) (Value, error)

// If set, tolerate the absence of the last component in the path.
// This option is needed to set a key in a map that is not yet present.
allowMissingKeyInMap bool
}

func visit(v Value, prefix, suffix Path, opts visitOptions) (Value, error) {
Expand Down Expand Up @@ -76,7 +72,7 @@ func visit(v Value, prefix, suffix Path, opts visitOptions) (Value, error) {

// Lookup current value in the map.
ev, ok := m[component.key]
if !ok && !opts.allowMissingKeyInMap {
if !ok {
return InvalidValue, noSuchKeyError{prefix}
}

Expand Down
64 changes: 59 additions & 5 deletions libs/dyn/visit_set.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package dyn

import (
"fmt"
"maps"
"slices"
)

// Set assigns a new value at the specified path in the specified value.
// It is identical to [SetByPath], except that it takes a string path instead of a [Path].
func Set(v Value, path string, nv Value) (Value, error) {
Expand All @@ -14,11 +20,59 @@ func Set(v Value, path string, nv Value) (Value, error) {
// If successful, it returns the new value with all intermediate values copied and updated.
// If the path doesn't exist, it returns InvalidValue and an error.
func SetByPath(v Value, p Path, nv Value) (Value, error) {
return visit(v, EmptyPath, p, visitOptions{
fn: func(_ Path, _ Value) (Value, error) {
// Return the incoming value to set it.
return nv, nil
lp := len(p)
if lp == 0 {
return nv, nil
}

parent := p[:lp-1]
component := p[lp-1]

return visit(v, EmptyPath, parent, visitOptions{
fn: func(prefix Path, v Value) (Value, error) {
path := prefix.Append(component)

switch {
case component.isKey():
// Expect a map to be set if this is a key.
m, ok := v.AsMap()
if !ok {
return InvalidValue, fmt.Errorf("expected a map to index %q, found %s", path, v.Kind())
}

// Return an updated map value.
m = maps.Clone(m)
m[component.key] = nv
return Value{
v: m,
k: KindMap,
l: v.l,
}, nil

case component.isIndex():
// Expect a sequence to be set if this is an index.
s, ok := v.AsSequence()
if !ok {
return InvalidValue, fmt.Errorf("expected a sequence to index %q, found %s", path, v.Kind())
}

// Lookup current value in the sequence.
if component.index < 0 || component.index >= len(s) {
return InvalidValue, indexOutOfBoundsError{prefix}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the idiomatic way to append to a sequence using dyn?

Copy link
Contributor Author

@pietern pietern Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an existing sequence, this would be to call dyn.Map on its path and the modify the sequence yourself.

}

// Return an updated sequence value.
s = slices.Clone(s)
s[component.index] = nv
return Value{
v: s,
k: KindSequence,
l: v.l,
}, nil

default:
panic("invalid component")
}
},
allowMissingKeyInMap: true,
})
}
Loading