Skip to content

Commit

Permalink
Support for bind to map values
Browse files Browse the repository at this point in the history
ref: #569

```yaml
bind:
  vars:
    "foo[i]": current.res
```
  • Loading branch information
k1LoW committed Nov 28, 2023
1 parent fabb24d commit d3ffcd8
Show file tree
Hide file tree
Showing 2 changed files with 568 additions and 14 deletions.
216 changes: 213 additions & 3 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package runn
import (
"context"
"fmt"
"sort"
"strings"

"github.com/antonmedv/expr/ast"
"github.com/antonmedv/expr/parser"
"github.com/samber/lo"
)

Expand All @@ -26,18 +30,224 @@ func (rnr *bindRunner) Run(ctx context.Context, s *step, first bool) error {
store[storeRootPrevious] = o.store.previous()
store[storeRootKeyCurrent] = o.store.latest()
}
for k, v := range cond {
keys := lo.Keys(cond)
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
for _, k := range keys {
if lo.Contains(reservedStoreRootKeys, k) {
return fmt.Errorf("%q is reserved", k)
}
vv, err := EvalAny(v, store)
v := cond[k]
kv, err := evalBindKeyValue(o.store.bindVars, k, v, store)
if err != nil {
return err
}
o.store.bindVars[k] = vv
o.store.bindVars = kv
}
if first {
o.record(nil)
}
return nil
}

func evalBindKeyValue(bindVars map[string]any, k string, v any, store map[string]any) (map[string]any, error) {
vv, err := EvalAny(v, store)
if err != nil {
return nil, err
}
if !strings.Contains(k, "[") || !strings.HasSuffix(k, "]") {
// Override the value of bindVars
bindVars[k] = vv
return bindVars, nil
}
// Merge the value of bindVars
// foo[bar]
// foo['bar']
// foo[5]
// foo[bar][baz]
tr, err := parser.Parse(k)
if err != nil {
return nil, err
}
kv, err := nodeToMap(tr.Node, vv, store)
if err != nil {
return nil, err
}
return mergeVars(bindVars, kv), nil
}

func nodeToMap(n ast.Node, v any, store map[string]any) (map[string]any, error) {
m := map[string]any{}
switch nn := n.(type) {
case *ast.MemberNode:
switch nnn := nn.Node.(type) {
case *ast.IdentifierNode:
k := nnn.Value
switch p := nn.Property.(type) {
case *ast.IdentifierNode:
kk, err := EvalAny(p.Value, store)
if err != nil {
return nil, err
}
if kk == nil {
return nil, fmt.Errorf("invalid value: %v", p.Value)
}
m[k] = map[any]any{
kk: v,
}
case *ast.StringNode:
m[k] = map[any]any{
p.Value: v,
}
case *ast.IntegerNode:
m[k] = map[any]any{
p.Value: v,
}
case *ast.MemberNode:
kk, err := EvalAny(p.String(), store)
if err != nil {
return nil, err
}
if kk == nil {
return nil, fmt.Errorf("invalid value: %v", p.String())
}
m[k] = map[any]any{
kk: v,
}
default:
return nil, fmt.Errorf("invalid node type of %v: %T", nn.Property, nn.Property)
}
case *ast.MemberNode:
vv := map[any]any{}
switch p := nn.Property.(type) {
case *ast.IdentifierNode:
kk, err := EvalAny(p.Value, store)
if err != nil {
return nil, err
}
if kk == nil {
return nil, fmt.Errorf("invalid value: %v", p.Value)
}
vv = map[any]any{
kk: v,
}
case *ast.StringNode:
vv = map[any]any{
p.Value: v,
}
case *ast.IntegerNode:
vv = map[any]any{
p.Value: v,
}
case *ast.MemberNode:
kk, err := EvalAny(p.String(), store)
if err != nil {
return nil, err
}
if kk == nil {
return nil, fmt.Errorf("invalid value: %v", p.String())
}
vv = map[any]any{
kk: v,
}
default:
return nil, fmt.Errorf("invalid node type of %v: %T", nn.Property, nn.Property)
}
vvv, err := nodeToMap(nnn, vv, store)
if err != nil {
return nil, err
}
m = vvv
}
default:
return nil, fmt.Errorf("invalid node type of %v: %T", n, n)
}
return m, nil
}

func mergeVars(store map[string]any, vars map[string]any) map[string]any {
for k, v := range vars {
sv, ok := store[k]
if !ok {
store[k] = v
continue
}
switch svv := sv.(type) {
case map[string]any:
switch vv := v.(type) {
case map[string]any:
store[k] = mergeVars(svv, vv)
case map[any]any:
// convert svv map[string]any to map[any]any
svv2 := make(map[any]any)
for k, v := range svv {
svv2[k] = v
}
store[k] = mergeMapAny(svv2, vv)
default:
store[k] = vv
}
case map[any]any:
switch vv := v.(type) {
case map[string]any:
// convert vv map[string]any to map[any]any
vv2 := make(map[any]any)
for k, v := range vv {
vv2[k] = v
}
store[k] = mergeMapAny(svv, vv2)
case map[any]any:
store[k] = mergeMapAny(svv, vv)
default:
store[k] = vv
}
case []any:
switch vv := v.(type) {
case []any:
store[k] = append(svv, vv...)
default:
store[k] = vv
}
default:
store[k] = v
}
}
return store
}

func mergeMapAny(store map[any]any, vars map[any]any) map[any]any {
for k, v := range vars {
sv, ok := store[k]
if !ok {
store[k] = v
continue
}
switch svv := sv.(type) {
case map[string]any:
switch vv := v.(type) {
case map[string]any:
store[k] = mergeVars(svv, vv)
default:
store[k] = vv
}
case map[any]any:
switch vv := v.(type) {
case map[any]any:
store[k] = mergeMapAny(svv, vv)
default:
store[k] = vv
}
case []any:
switch vv := v.(type) {
case []any:
store[k] = append(svv, vv...)
default:
store[k] = vv
}
default:
store[k] = v
}
}
return store
}
Loading

0 comments on commit d3ffcd8

Please sign in to comment.