3
0

Adds expr functions for KV, KVV, Vars manipulation

- Adds/Refactors methods for Set, Merge, Filter, Delete
- Renames expr/Vars.Merge to MustMerge, updates its usage
- Appends kvFunctions to parser in pkg/expr
- Update/Fixes tests and example testable
This commit is contained in:
Vivek Patel
2021-05-12 06:32:30 +05:30
committed by Denis Arh
parent 28e1774cd2
commit 044d02bb76
14 changed files with 896 additions and 28 deletions

View File

@@ -538,7 +538,7 @@ func (svc *workflow) Exec(ctx context.Context, workflowID uint64, p types.Workfl
}()
// Start with workflow scope
scope := wf.Scope.Merge()
scope := wf.Scope.MustMerge()
ssp := types.SessionStartParams{
WorkflowID: wf.ID,
@@ -559,7 +559,7 @@ func (svc *workflow) Exec(ctx context.Context, workflowID uint64, p types.Workfl
wap.setTrigger(t)
// Add trigger's input to scope
scope = scope.Merge(t.Input)
scope = scope.MustMerge(t.Input)
_ = scope.AssignFieldValue("eventType", expr.Must(expr.NewString(t.EventType)))
_ = scope.AssignFieldValue("resourceType", expr.Must(expr.NewString(t.ResourceType)))
@@ -572,7 +572,7 @@ func (svc *workflow) Exec(ctx context.Context, workflowID uint64, p types.Workfl
}
// Finally, assign input values
ssp.Input = scope.Merge(p.Input)
ssp.Input = scope.MustMerge(p.Input)
if wf.RunAs > 0 {
if runAs, err = DefaultUser.FindByID(ctx, wf.RunAs); err != nil {
@@ -617,7 +617,7 @@ func makeWorkflowHandler(ac workflowExecController, s *session, t *types.Trigger
var (
// create session scope from predefined workflow scope and trigger input
scope = wf.Scope.Merge(t.Input)
scope = wf.Scope.MustMerge(t.Input)
evScope *expr.Vars
wait WaitFn
)
@@ -627,7 +627,7 @@ func makeWorkflowHandler(ac workflowExecController, s *session, t *types.Trigger
return
}
scope = scope.Merge(evScope)
scope = scope.MustMerge(evScope)
}
_ = scope.AssignFieldValue("eventType", expr.Must(expr.NewString(ev.EventType())))

View File

@@ -450,7 +450,7 @@ func (svc workflowConverter) convPromptStep(s *types.WorkflowStep) (wfexec.Step,
return wfexec.Prompt(ownerId, s.Ref, payload), nil
}
results, err := res.Eval(ctx, r.Scope.Merge(r.Input))
results, err := res.Eval(ctx, r.Scope.MustMerge(r.Input))
if err != nil {
return nil, err
}

View File

@@ -87,7 +87,7 @@ func (set ExprSet) Validate(ctx context.Context, in *expr.Vars) (TestSet, error)
err error
// Copy/create scope
scope = (&expr.Vars{}).Merge(in)
scope = (&expr.Vars{}).MustMerge(in)
)
for _, e := range set {
@@ -108,7 +108,7 @@ func (set ExprSet) Eval(ctx context.Context, in *expr.Vars) (*expr.Vars, error)
err error
// Copy input to scope
scope = (&expr.Vars{}).Merge(in)
scope = (&expr.Vars{}).MustMerge(in)
// Prepare output scope
out, _ = expr.NewVars(nil)
@@ -223,7 +223,7 @@ func ExpressionsStep(ee ...*Expr) *expressionsStep {
}
func (s expressionsStep) Exec(ctx context.Context, r *wfexec.ExecRequest) (wfexec.ExecResponse, error) {
result, err := s.Set.Eval(ctx, r.Scope.Merge(r.Input))
result, err := s.Set.Eval(ctx, r.Scope.MustMerge(r.Input))
if err != nil {
return nil, err
}

View File

@@ -98,7 +98,7 @@ func (f functionStep) Exec(ctx context.Context, r *wfexec.ExecRequest) (wfexec.E
if len(f.arguments) > 0 {
// Arguments defined, get values from scope and use them when calling
// function/handler
args, err = f.arguments.Eval(ctx, r.Scope.Merge(r.Input))
args, err = f.arguments.Eval(ctx, r.Scope.MustMerge(r.Input))
if err != nil {
return nil, err
}
@@ -171,7 +171,7 @@ func (f *iteratorStep) Exec(ctx context.Context, r *wfexec.ExecRequest) (wfexec.
if len(f.arguments) > 0 {
// Arguments defined, get values from scope and use them when calling
// iterator/handler
args, err = f.arguments.Eval(ctx, r.Scope.Merge(r.Input))
args, err = f.arguments.Eval(ctx, r.Scope.MustMerge(r.Input))
if err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/PaesslerAG/gval"
"io"
"io/ioutil"
"net/http"
@@ -23,16 +24,71 @@ type (
Type
ResolveTypes(func(string) Type) error
}
merger interface {
Merge(...Iterator) (TypedValue, error)
}
filterer interface {
Filter(...string) (TypedValue, error)
}
deleter interface {
Delete(...string) (TypedValue, error)
}
)
func KvFunctions() []gval.Language {
return []gval.Language{
gval.Function("set", set),
gval.Function("merge", merge),
gval.Function("filter", filter),
gval.Function("omit", omit),
}
}
func EmptyKV() *KV {
return &KV{value: make(map[string]string)}
}
func EmptyKVV() *KVV {
return &KVV{value: make(map[string][]string)}
}
func EmptyVars() *Vars {
return &Vars{value: map[string]TypedValue{}}
return &Vars{value: make(map[string]TypedValue)}
}
func ResolveTypes(rt resolvableType, resolver func(typ string) Type) error {
return rt.ResolveTypes(resolver)
}
func set(m merger, key string, val TypedValue) (out TypedValue, err error) {
out, err = m.Merge()
if err != nil {
return
}
err = Assign(out, key, val)
if err != nil {
return
}
return
}
func merge(m merger, nn ...Iterator) (TypedValue, error) {
return m.Merge(nn...)
}
func filter(f filterer, ss ...string) (TypedValue, error) {
return f.Filter(ss...)
}
func omit(d deleter, ss ...string) (TypedValue, error) {
return d.Delete(ss...)
}
// Typify detects input type and wraps it with expression type
func Typify(in interface{}) (tv TypedValue, err error) {
var is bool
@@ -301,7 +357,7 @@ func CastToString(val interface{}) (out string, err error) {
}
}
func CastStringSlice(val interface{}) (out []string, err error) {
func CastToStringSlice(val interface{}) (out []string, err error) {
return cast.ToStringSliceE(UntypedValue(val))
}
@@ -404,6 +460,86 @@ func CastToKV(val interface{}) (out map[string]string, err error) {
}
}
func (t *KV) Each(fn func(k string, v TypedValue) error) (err error) {
if t == nil || t.value == nil {
return
}
for k, v := range t.value {
var val TypedValue
val, err = Typify(v)
if err != nil {
return err
}
if err = fn(k, val); err != nil {
return
}
}
return
}
// Merge combines the given KVs into KV
// NOTE: It will return CLONE of the original KV, if its called without any parameters
func (t *KV) Merge(nn ...Iterator) (out TypedValue, err error) {
kv := EmptyKV()
nn = append([]Iterator{t}, nn...)
for _, i := range nn {
err = i.Each(func(k string, v TypedValue) error {
str, err := cast.ToStringE(v.Get())
kv.value[k] = str
return err
})
if err != nil {
return
}
}
return kv, nil
}
// Filter take keys returns KV with only those key value pair
func (t *KV) Filter(keys ...string) (out TypedValue, err error) {
if t.value == nil {
return
}
kv := EmptyKV()
for _, k := range keys {
_, has := t.value[k]
if has {
kv.value[k] = t.value[k]
}
}
return kv, nil
}
// Delete take keys returns KV without those key value pair
func (t *KV) Delete(keys ...string) (out TypedValue, err error) {
if t.value == nil {
return
}
// get cloned KV
out, err = t.Merge()
if err != nil {
return
}
kv := out.(*KV)
// Delete key from out.value if exist
for _, k := range keys {
delete(kv.value, k)
}
return kv, nil
}
func (t *KVV) AssignFieldValue(key string, val TypedValue) error {
return assignToKVV(t, key, val)
}
@@ -449,3 +585,92 @@ func CastToReader(val interface{}) (out io.Reader, err error) {
return nil, fmt.Errorf("unable to cast %T to io.Reader", val)
}
}
func (t *KVV) Each(fn func(k string, v TypedValue) error) (err error) {
if t == nil || t.value == nil {
return
}
for k, v := range t.value {
var val TypedValue
val, err = Typify(v)
if err != nil {
return err
}
if err = fn(k, val); err != nil {
return
}
}
return
}
// Merge combines the given KVVs into KVV
// NOTE: It will return CLONE of the original KVV, if its called without any parameters
func (t *KVV) Merge(nn ...Iterator) (out TypedValue, err error) {
kvv := EmptyKVV()
nn = append([]Iterator{t}, nn...)
for _, i := range nn {
err = i.Each(func(k string, v TypedValue) error {
ss, err := cast.ToStringSliceE(v.Get())
if err != nil {
return err
}
_, has := kvv.value[k]
if has {
kvv.value[k] = append(kvv.value[k], ss...)
} else {
kvv.value[k] = ss
}
return nil
})
if err != nil {
return
}
}
return kvv, nil
}
// Filter take keys returns KVV with only those key value pair
func (t *KVV) Filter(keys ...string) (out TypedValue, err error) {
if t.value == nil {
return
}
kvv := EmptyKVV()
for _, k := range keys {
_, has := t.value[k]
if has {
kvv.value[k] = t.value[k]
}
}
return kvv, nil
}
// Delete take keys returns KVV without those key value pair
func (t *KVV) Delete(keys ...string) (out TypedValue, err error) {
if t.value == nil {
return
}
// get cloned KVV
out, err = t.Merge()
if err != nil {
return
}
kvv := out.(*KVV)
// Delete key from t.value if exist
for _, k := range keys {
delete(kvv.value, k)
}
return kvv, nil
}

View File

@@ -2,12 +2,180 @@ package expr
import (
"context"
"fmt"
"github.com/stretchr/testify/require"
"net/http"
"net/url"
"testing"
)
func Example_set_kv() {
var (
kv = &KV{}
p = map[string]interface{}{
"kv": kv,
"key": "k1",
"value": Must(NewString("v11")),
}
)
eval(`set(kv, key, value)`, p)
fmt.Printf("\nOriginal KV should be %v", kv)
// output:
// &{value:map[k1:v11]}
// Original KV should be &{map[]}
}
func Example_merge_kv() {
var (
kv = &KV{}
p = map[string]interface{}{
"kv": kv,
"foo": &KV{value: map[string]string{
"k1": "v1",
}},
"bar": &KV{value: map[string]string{
"k2": "v2",
}},
}
)
eval(`merge(kv, foo, bar)`, p)
fmt.Printf("\nOriginal KV should be %v", kv)
// output:
// &{value:map[k1:v1 k2:v2]}
// Original KV should be &{map[]}
}
func Example_filter_kv() {
var (
kv = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
}}
p = map[string]interface{}{
"kv": kv,
"key1": "k1",
"key2": "k3",
}
)
eval(`filter(kv, key1, key2)`, p)
fmt.Printf("\nOriginal KV should be %v", kv)
// output:
// &{value:map[k1:v1]}
// Original KV should be &{map[k1:v1 k2:v2]}
}
func Example_omit_kv() {
var (
kv = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
"k3": "v3",
}}
p = map[string]interface{}{
"kv": kv,
"key1": "k1",
"key2": "k3",
}
)
eval(`omit(kv, key1, key2)`, p)
fmt.Printf("\nOriginal KV should be %v", kv)
// output:
// &{value:map[k2:v2]}
// Original KV should be &{map[k1:v1 k2:v2 k3:v3]}
}
func Example_set_kvv() {
var (
kvv = &KVV{}
p = map[string]interface{}{
"kvv": kvv,
"key": "foo",
"value": Must(NewString("bar")),
}
)
eval(`set(kvv, key, value)`, p)
fmt.Printf("\nOriginal KVV should be %v", kvv)
// output:
// &{value:map[foo:[bar]]}
// Original KVV should be &{map[]}
}
func Example_merge_kvv() {
var (
kvv = &KVV{}
p = map[string]interface{}{
"kvv": kvv,
"foo": &KVV{value: map[string][]string{
"k1": {"v1"},
}},
"bar": &KVV{value: map[string][]string{
"k1": {"v11"},
"k2": {"v2"},
}},
}
)
eval(`merge(kvv, foo, bar)`, p)
fmt.Printf("\nOriginal KVV should be %v", kvv)
// output:
// &{value:map[k1:[v1 v11] k2:[v2]]}
// Original KVV should be &{map[]}
}
func Example_filter_kvv() {
var (
kvv = &KVV{value: map[string][]string{
"k1": {"v1"},
"k2": {"v2"},
}}
p = map[string]interface{}{
"kv": kvv,
"key1": "k1",
"key2": "k3",
}
)
eval(`filter(kv, key1, key2)`, p)
fmt.Printf("\nOriginal KVV should be %v", kvv)
// output:
// &{value:map[k1:[v1]]}
// Original KVV should be &{map[k1:[v1] k2:[v2]]}
}
func Example_omit_kvv() {
var (
kvv = &KVV{value: map[string][]string{
"k1": {"v1"},
"k2": {"v2"},
"k3": {"v3"},
}}
p = map[string]interface{}{
"kvv": kvv,
"key1": "k1",
"key2": "k3",
}
)
eval(`omit(kvv, key1, key2)`, p)
fmt.Printf("\nOriginal KVV should be %v", kvv)
// output:
// &{value:map[k2:[v2]]}
// Original KVV should be &{map[k1:[v1] k2:[v2] k3:[v3]]}
}
func TestTypedValueOperations(t *testing.T) {
scope, _ := NewVars(map[string]interface{}{
"xUint": uint(1),
@@ -55,7 +223,7 @@ func TestTypedValueOperations(t *testing.T) {
}
func TestKV_Set(t *testing.T) {
func TestKV_Assign(t *testing.T) {
var (
req = require.New(t)
@@ -71,7 +239,108 @@ func TestKV_Set(t *testing.T) {
}
func TestKVV_Set(t *testing.T) {
func TestKV_Set(t *testing.T) {
var (
req = require.New(t)
kv = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
}}
)
out, err := set(kv, "k1", Must(NewString("v11")))
req.NoError(err)
req.Equal("v11", out.(*KV).GetValue()["k1"])
// Making sure empty KV updates without error
kv = &KV{}
out, err = set(kv, "k1", Must(NewString("v11")))
req.NoError(err)
req.Equal("v11", out.(*KV).GetValue()["k1"])
}
func TestKV_Merge(t *testing.T) {
var (
req = require.New(t)
kv = &KV{}
foo = &KV{value: map[string]string{
"k1": "v1",
}}
bar = &KV{value: map[string]string{
"k2": "v2",
}}
expected = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
}}
)
out, err := merge(kv, foo, bar)
req.NoError(err)
req.Equal(expected, out)
}
func TestKV_Clone(t *testing.T) {
var (
req = require.New(t)
kv = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
}}
expected = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
}}
)
out, err := kv.Merge()
req.NoError(err)
req.Equal(expected, out)
}
func TestKV_Filter(t *testing.T) {
var (
req = require.New(t)
kv = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
}}
expected = &KV{value: map[string]string{
"k1": "v1",
}}
)
out, err := filter(kv, "k1", "k3")
req.NoError(err)
req.Equal(expected, out)
req.NotEqual(kv, out)
}
func TestKV_Omit(t *testing.T) {
var (
req = require.New(t)
kv = &KV{value: map[string]string{
"k1": "v1",
"k2": "v2",
"k3": "v3",
}}
expected = &KV{value: map[string]string{
"k2": "v2",
}}
)
out, err := omit(kv, "k1", "k3")
req.NoError(err)
req.Equal(expected, out)
req.NotEqual(kv, out)
}
func TestKVV_Assign(t *testing.T) {
var (
req = require.New(t)
kvv KVV
@@ -94,6 +363,105 @@ func TestKVV_Set(t *testing.T) {
req.Equal([]string{"bar"}, kvv.value["foo"])
}
func TestKVV_Set(t *testing.T) {
var (
req = require.New(t)
kvv = &KVV{value: map[string][]string{
"k1": {"v1"},
"k2": {"v2"},
}}
)
out, err := set(kvv, "k1", Must(NewString("v11")))
req.NoError(err)
req.Equal([]string{"v11"}, out.(*KVV).GetValue()["k1"])
// Making sure empty KV updates without error
kvv = &KVV{}
out, err = set(kvv, "foo", Must(NewString("bar")))
req.NoError(err)
req.Equal([]string{"bar"}, out.(*KVV).GetValue()["foo"])
}
func TestKVV_Merge(t *testing.T) {
var (
req = require.New(t)
kvv *KVV
foo = KVV{value: map[string][]string{
"k1": {"v1"},
}}
bar = KVV{value: map[string][]string{
"k1": {"v11"},
"k2": {"v2"},
}}
expected = &KVV{value: map[string][]string{
"k1": {"v1", "v11"},
"k2": {"v2"},
}}
)
out, err := merge(kvv, &foo, &bar)
req.NoError(err)
req.Equal(expected, out)
}
func TestKVV_Clone(t *testing.T) {
var (
req = require.New(t)
kvv = KVV{value: map[string][]string{
"k1": {"v1", "v11"},
"k2": {"v2"},
}}
expected = &KVV{value: map[string][]string{
"k1": {"v1", "v11"},
"k2": {"v2"},
}}
)
out, err := kvv.Merge()
req.NoError(err)
req.Equal(expected, out)
}
func TestKVV_Filter(t *testing.T) {
var (
req = require.New(t)
kvv = &KVV{value: map[string][]string{
"k1": {"v1"},
"k2": {"v2"},
}}
expected = &KVV{value: map[string][]string{
"k1": {"v1"},
}}
)
out, err := filter(kvv, "k1", "k3")
req.NoError(err)
req.Equal(expected, out)
}
func TestKVV_Omit(t *testing.T) {
var (
req = require.New(t)
kvv = &KVV{value: map[string][]string{
"k1": {"v1"},
"k2": {"v2"},
"k3": {"v3"},
}}
expected = &KVV{value: map[string][]string{
"k2": {"v2"},
}}
)
out, err := omit(kvv, "k1", "k3")
req.NoError(err)
req.Equal(expected, out)
}
func TestCastEmptyString(t *testing.T) {
var (
req = require.New(t)

18
pkg/expr/func_json.go Normal file
View File

@@ -0,0 +1,18 @@
package expr
import (
"encoding/json"
"github.com/PaesslerAG/gval"
)
func JsonFunctions() []gval.Language {
return []gval.Language{
// TODO: Json decoding, parsing, stringify, JQ, JSONPath
gval.Function("toJSON", toJSON),
}
}
func toJSON(f interface{}) string {
b, _ := json.Marshal(f)
return string(b)
}

View File

@@ -0,0 +1,19 @@
package expr
func Example_toJSON() {
var (
p = map[string]interface{}{
"vars": Must(NewVars(
&Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
}},
)),
}
)
eval(`toJSON(vars)`, p)
// output:
// {"k1":{"@value":"v1","@type":"String"},"k2":{"@value":"v2","@type":"String"}}
}

View File

@@ -83,7 +83,7 @@ func join(arr interface{}, sep string) (out string, err error) {
} else if i, is := arr.([]interface{}); is {
// If slice of interfaces, we can try to cast them
var aux []string
aux, err = CastStringSlice(i)
aux, err = CastToStringSlice(i)
if err != nil {
return
}

View File

@@ -87,9 +87,11 @@ func AllFunctions() []gval.Language {
//ff = append(ff, GenericFunctions()...)
ff = append(ff, StringFunctions()...)
ff = append(ff, JsonFunctions()...)
ff = append(ff, NumericFunctions()...)
ff = append(ff, TimeFunctions()...)
ff = append(ff, ArrayFunctions()...)
ff = append(ff, KvFunctions()...)
return ff
}

View File

@@ -58,8 +58,25 @@ func (t Vars) ResolveTypes(res func(typ string) Type) (err error) {
return nil
}
// Assign takes base variables and assigns all new variables
func (t *Vars) Merge(nn ...Iterator) *Vars {
// Merge combines the given Vars(es) into Vars
// NOTE: It will return CLONE of the original Vars, if its called without any parameters
func (t *Vars) Merge(nn ...Iterator) (out TypedValue, err error) {
vars := EmptyVars()
nn = append([]Iterator{t}, nn...)
for _, i := range nn {
_ = i.Each(func(k string, v TypedValue) error {
vars.value[k] = v
return nil
})
}
return vars, nil
}
// MustMerge returns Vars after merging the given Vars(es) into it
func (t *Vars) MustMerge(nn ...Iterator) *Vars {
var (
out = &Vars{value: make(map[string]TypedValue)}
)
@@ -76,7 +93,7 @@ func (t *Vars) Merge(nn ...Iterator) *Vars {
return out
}
// Assign takes base variables and assigns all new variables
// Copy takes base variables and copies all to dst
func (t *Vars) Copy(dst *Vars, kk ...string) {
if t == nil {
return
@@ -91,12 +108,12 @@ func (t *Vars) Copy(dst *Vars, kk ...string) {
}
}
// Returns true key is present
// Has returns true key is present
func (t *Vars) Has(key string) bool {
return t.HasAll(key)
}
// Returns true if all keys are present
// HasAll returns true if all keys are present
func (t *Vars) HasAll(key string, kk ...string) bool {
if t == nil {
return false
@@ -111,7 +128,7 @@ func (t *Vars) HasAll(key string, kk ...string) bool {
return true
}
// Returns true if all keys are present
// HasAny returns true if all keys are present
func (t *Vars) HasAny(key string, kk ...string) bool {
if t == nil {
return false
@@ -230,7 +247,7 @@ func (t Vars) SelectGVal(_ context.Context, k string) (interface{}, error) {
}
}
// UnmarshalJSON
// UnmarshalJSON unmarshal JSON value into Vars
func (t *Vars) UnmarshalJSON(in []byte) (err error) {
var (
aux = make(map[string]*typedValueWrap)
@@ -271,6 +288,7 @@ func (t *Vars) Each(fn func(k string, v TypedValue) error) (err error) {
return
}
// Set set/update the specific key value in KV
func (t *Vars) Set(k string, v interface{}) (err error) {
if t.value == nil {
t.value = make(map[string]TypedValue)
@@ -280,7 +298,7 @@ func (t *Vars) Set(k string, v interface{}) (err error) {
return
}
// UnmarshalJSON parses sort expression when passed inside JSON
// MarshalJSON returns JSON encoding of expression
func (t Vars) MarshalJSON() ([]byte, error) {
aux := make(map[string]*typedValueWrap)
for k, v := range t.value {
@@ -409,3 +427,42 @@ func CastToVars(val interface{}) (out map[string]TypedValue, err error) {
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
// Filter take keys returns KV with only those key value pair
func (t *Vars) Filter(keys ...string) (out TypedValue, err error) {
if t.value == nil {
return
}
vars := EmptyVars()
for _, k := range keys {
_, has := t.value[k]
if has {
vars.value[k] = t.value[k]
}
}
return vars, nil
}
// Delete take keys returns KV without those key value pair
func (t *Vars) Delete(keys ...string) (out TypedValue, err error) {
if t.value == nil {
return
}
// get cloned Vars
out, err = t.Merge()
if err != nil {
return
}
vars := out.(*Vars)
// Delete key from t.value if exist
for _, k := range keys {
delete(vars.value, k)
}
return vars, nil
}

View File

@@ -2,12 +2,85 @@ package expr
import (
"encoding/json"
"fmt"
"github.com/stretchr/testify/require"
"testing"
)
// extract typed-value
func Example_set_vars() {
var (
p = map[string]interface{}{
"vars": &Vars{},
"key": "foo",
"value": &String{value: "foo"},
}
)
eval(`toJSON(set(vars, key, value))`, p)
// output:
// {"foo":{"@value":"foo","@type":"String"}}
}
func Example_merge_vars() {
var (
p = map[string]interface{}{
"vars": &Vars{},
"foo": &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
}},
"bar": &Vars{value: map[string]TypedValue{
"k2": &String{value: "v2"},
}},
}
)
eval(`toJSON(merge(foo, bar))`, p)
// output:
// {"k1":{"@value":"v1","@type":"String"},"k2":{"@value":"v2","@type":"String"}}
}
func Example_filter_vars() {
var (
p = map[string]interface{}{
"vars": &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k5": &String{value: "v5"},
}},
"key1": "k1",
"key2": "k3",
"key3": "k5",
}
)
eval(`toJSON(filter(vars, key1, key2, key3))`, p)
// output:
// {"k1":{"@value":"v1","@type":"String"},"k5":{"@value":"v5","@type":"String"}}
}
func Example_omit_vars() {
var (
p = map[string]interface{}{
"vars": &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
"k3": &String{value: "v3"},
}},
"key1": "k1",
"key2": "k3",
}
)
eval(`toJSON(omit(vars, key1, key2))`, p)
// output:
// {"k2":{"@value":"v2","@type":"String"}}
}
func TestVars_Decode(t *testing.T) {
t.Run("mix", func(t *testing.T) {
var (
@@ -107,6 +180,112 @@ func TestVars_Assign(t *testing.T) {
req.NoError(Assign(vars, "foo", &String{value: "foo"}))
req.NoError(Assign(vars, "vars", &Vars{}))
req.NoError(Assign(vars, "vars.foo", &String{value: "foo"}))
fmt.Println("Vars: ", vars)
}
func TestVars_Set(t *testing.T) {
var (
req = require.New(t)
vars = &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
}}
)
out, err := set(vars, "k1", &String{value: "v11"})
fmt.Println("Out: ", out)
req.NoError(err)
req.Equal(&String{value: "v11"}, out.(*Vars).GetValue()["k1"])
// Making sure empty vars updates without error
vars = &Vars{}
out, err = set(vars, "k2", &Array{value: []TypedValue{&String{value: "foo"}, &String{value: "bar"}}})
req.NoError(err)
req.Equal(&Array{value: []TypedValue{&String{value: "foo"}, &String{value: "bar"}}}, out.(*Vars).GetValue()["k2"])
}
func TestVars_MergeVars(t *testing.T) {
var (
req = require.New(t)
vars Vars
foo = Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
}}
bar = Vars{value: map[string]TypedValue{
"k2": &String{value: "v2"},
}}
expected = &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
}}
)
fmt.Println("vars: ", vars)
out := vars.MustMerge(&foo, &bar)
fmt.Println("vars: ", out)
req.Equal(expected, out)
}
func TestVars_Merge(t *testing.T) {
var (
req = require.New(t)
vars = &Vars{}
foo = &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
}}
bar = &Vars{value: map[string]TypedValue{
"k2": &String{value: "v2"},
}}
expected = &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
}}
)
out, err := merge(vars, foo, bar)
req.NoError(err)
req.Equal(expected, out)
}
func TestVars_Filter(t *testing.T) {
var (
req = require.New(t)
vars = &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
}}
expected = &Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
}}
)
out, err := filter(vars, "k1", "k3")
req.NoError(err)
req.Equal(expected, out)
}
func TestVars_Omit(t *testing.T) {
var (
req = require.New(t)
vars = Vars{value: map[string]TypedValue{
"k1": &String{value: "v1"},
"k2": &String{value: "v2"},
"k3": &String{value: "v3"},
}}
expected = &Vars{value: map[string]TypedValue{
"k2": &String{value: "v2"},
}}
)
out, err := omit(&vars, "k1", "k3")
req.NoError(err)
req.Equal(expected, out)
}
func TestVars_UnmarshalJSON(t *testing.T) {

View File

@@ -66,7 +66,7 @@ func (gw *joinGateway) Exec(_ context.Context, r *ExecRequest) (ExecResponse, er
var merged *expr.Vars
for _, p := range gw.paths {
if gw.scopes[p] != nil {
merged = merged.Merge(gw.scopes[p])
merged = merged.MustMerge(gw.scopes[p])
}
}

View File

@@ -418,7 +418,7 @@ func (s *Session) worker(ctx context.Context) {
defer s.mux.Unlock()
// making sure result != nil
s.result = (&expr.Vars{}).Merge(st.scope)
s.result = (&expr.Vars{}).MustMerge(st.scope)
// Call event handler with completed status
s.eventHandler(SessionCompleted, st, s)
@@ -549,7 +549,7 @@ func (s *Session) exec(ctx context.Context, st *State) (err error) {
var (
result ExecResponse
scope = (&expr.Vars{}).Merge(st.scope)
scope = (&expr.Vars{}).MustMerge(st.scope)
currLoop = st.loopCurr()
@@ -635,7 +635,7 @@ func (s *Session) exec(ctx context.Context, st *State) (err error) {
// most common (successful) result
// session will continue with configured child steps
st.results = result
scope = scope.Merge(st.results)
scope = scope.MustMerge(st.results)
case *errHandler:
st.action = "error handler initialized"