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:
@@ -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())))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
18
pkg/expr/func_json.go
Normal 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)
|
||||
}
|
||||
19
pkg/expr/func_json_test.go
Normal file
19
pkg/expr/func_json_test.go
Normal 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"}}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user