Extend set/omit expr function
It updates 1st parameter of set expr function from merger type to interface, so It will accept constant values, also extend set/omit usage for ComposeRecordValues.
This commit is contained in:
@@ -518,3 +518,105 @@ func CastToAttachment(val interface{}) (out *types.Attachment, err error) {
|
||||
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
|
||||
}
|
||||
}
|
||||
|
||||
func EmptyComposeRecordValues() *ComposeRecordValues {
|
||||
return &ComposeRecordValues{value: &types.Record{Values: types.RecordValueSet{}}}
|
||||
}
|
||||
|
||||
func (t *ComposeRecordValues) Each(fn func(k string, v expr.TypedValue) error) (err error) {
|
||||
if t.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, vv := range t.GetValue() {
|
||||
key := vv.Name
|
||||
if len(key) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var val expr.TypedValue
|
||||
val, err = expr.Typify(vv)
|
||||
if err = fn(key, val); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Merge combines the given ComposeRecordValues into ComposeRecordValues
|
||||
// NOTE: It will return CLONE of the original ComposeRecordValues, if it's called without any parameters
|
||||
func (t *ComposeRecordValues) Merge(nn ...expr.Iterator) (out expr.TypedValue, err error) {
|
||||
rv := EmptyComposeRecordValues()
|
||||
|
||||
nn = append([]expr.Iterator{t}, nn...)
|
||||
|
||||
for _, i := range nn {
|
||||
err = i.Each(func(k string, v expr.TypedValue) error {
|
||||
var (
|
||||
rVal types.RecordValue
|
||||
bb []byte
|
||||
)
|
||||
|
||||
// @todo implement casting of RecordValue from TypedValue
|
||||
bb, err = json.Marshal(v.Get())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bb, &rVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr := rv.GetValue().Set(&rVal)
|
||||
err = rv.Assign(rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (t *ComposeRecordValues) Delete(keys ...string) (out expr.TypedValue, err error) {
|
||||
if t.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// get cloned ComposeRecordValues
|
||||
out, err = t.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rv := out.(*ComposeRecordValues)
|
||||
if !rv.IsEmpty() {
|
||||
rv.value.Values = types.RecordValueSet{}
|
||||
}
|
||||
|
||||
keyMap := make(map[string]string)
|
||||
for _, k := range keys {
|
||||
keyMap[k] = k
|
||||
}
|
||||
|
||||
// Push the only with values with non-matching Name
|
||||
for _, val := range t.GetValue() {
|
||||
_, ok := keyMap[val.Name]
|
||||
if !ok {
|
||||
rr := rv.GetValue().Set(val.Clone())
|
||||
err = rv.Assign(rr)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
@@ -580,3 +580,92 @@ func TestGvalStringFunctionParamsIntCasting(t *testing.T) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestRecordValues_Merge(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
rv = &ComposeRecordValues{}
|
||||
foo = &ComposeRecordValues{
|
||||
value: &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k1",
|
||||
Value: "testValue1",
|
||||
},
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
bar = &ComposeRecordValues{
|
||||
value: &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k3",
|
||||
Value: "testValue3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expected = &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k1",
|
||||
Value: "testValue1",
|
||||
},
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
{
|
||||
Name: "k3",
|
||||
Value: "testValue3",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
out, err := rv.Merge(foo, bar)
|
||||
req.NoError(err)
|
||||
req.Equal(expected, out.Get())
|
||||
}
|
||||
|
||||
func TestRecordValues_Omit(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
rv = ComposeRecordValues{
|
||||
value: &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k1",
|
||||
Value: "testValue1",
|
||||
},
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
{
|
||||
Name: "k3",
|
||||
Value: "testValue3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expected = &types.Record{
|
||||
Values: []*types.RecordValue{
|
||||
{
|
||||
Name: "k2",
|
||||
Value: "testValue2",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
out, err := rv.Delete("k1", "k3")
|
||||
req.NoError(err)
|
||||
req.Equal(expected, out.Get())
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (set RecordValueSet) FilterByRecordID(recordID uint64) (vv RecordValueSet)
|
||||
return
|
||||
}
|
||||
|
||||
// Replaces existing values, remove extra
|
||||
// Replace existing values, remove extra
|
||||
func (set RecordValueSet) Replace(name string, values ...string) (vv RecordValueSet) {
|
||||
for i := range set {
|
||||
if set[i].Name != name {
|
||||
@@ -160,7 +160,7 @@ func (set RecordValueSet) Set(v *RecordValue) RecordValueSet {
|
||||
return append(set, v)
|
||||
}
|
||||
|
||||
// Has value set?
|
||||
// Get value set?
|
||||
func (set RecordValueSet) Get(name string, place uint) *RecordValue {
|
||||
for i := range set {
|
||||
if set[i].Name != name {
|
||||
|
||||
@@ -64,7 +64,20 @@ func ResolveTypes(rt resolvableType, resolver func(typ string) Type) error {
|
||||
return rt.ResolveTypes(resolver)
|
||||
}
|
||||
|
||||
func set(m merger, key string, val interface{}) (out TypedValue, err error) {
|
||||
func set(i interface{}, key string, val interface{}) (out TypedValue, err error) {
|
||||
m, ok := i.(merger)
|
||||
if !ok {
|
||||
i, err = Typify(i)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m, ok = i.(merger)
|
||||
if !ok {
|
||||
return out, fmt.Errorf("cannot set on unexpected type: %T", i)
|
||||
}
|
||||
}
|
||||
|
||||
out, err = m.Merge()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -75,7 +75,7 @@ func (t *Vars) ResolveTypes(res func(typ string) Type) (err error) {
|
||||
}
|
||||
|
||||
// Merge combines the given Vars(es) into Vars
|
||||
// NOTE: It will return CLONE of the original Vars, if its called without any parameters
|
||||
// NOTE: It will return CLONE of the original Vars, if it's called without any parameters
|
||||
func (t *Vars) Merge(nn ...Iterator) (out TypedValue, err error) {
|
||||
return t.MustMerge(nn...), nil
|
||||
}
|
||||
@@ -336,7 +336,7 @@ func (t *Vars) Each(fn func(k string, v TypedValue) error) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set set/update the specific key value in KV
|
||||
// Set or update the specific key value in Vars
|
||||
func (t *Vars) Set(k string, v interface{}) (err error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
@@ -485,7 +485,7 @@ 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
|
||||
// Filter take keys returns Vars with only those key value pair
|
||||
func (t *Vars) Filter(keys ...string) (out TypedValue, err error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
@@ -505,7 +505,7 @@ func (t *Vars) Filter(keys ...string) (out TypedValue, err error) {
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
// Delete take keys returns KV without those key value pair
|
||||
// Delete take keys returns Vars without those key value pair
|
||||
func (t *Vars) Delete(keys ...string) (out TypedValue, err error) {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
@@ -2,6 +2,7 @@ package workflows
|
||||
|
||||
import (
|
||||
"context"
|
||||
cmpTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
@@ -18,8 +19,16 @@ func Test0016_set_expression_issue(t *testing.T) {
|
||||
loadScenario(ctx, t)
|
||||
|
||||
t.Run("set expressions", func(t *testing.T) {
|
||||
type (
|
||||
testInput struct {
|
||||
Out map[string]expr.TypedValue
|
||||
OutConstStr map[string]expr.TypedValue
|
||||
OutConstInt map[string]expr.TypedValue
|
||||
OutRv *cmpTypes.Record
|
||||
}
|
||||
)
|
||||
var (
|
||||
aux = struct{ Out map[string]expr.TypedValue }{}
|
||||
aux = testInput{}
|
||||
vars, _ = mustExecWorkflow(ctx, t, "set_expression", types.WorkflowExecParams{})
|
||||
|
||||
testString = expr.Must(expr.NewString("testing string"))
|
||||
@@ -28,14 +37,67 @@ func Test0016_set_expression_issue(t *testing.T) {
|
||||
"testString": testString,
|
||||
"testFloat": expr.Must(expr.NewFloat(50)),
|
||||
}))
|
||||
expected = map[string]expr.TypedValue{
|
||||
|
||||
expectedVars = map[string]expr.TypedValue{
|
||||
"testString": testString,
|
||||
"testInt": testInt,
|
||||
"testVar": testVar,
|
||||
}
|
||||
expected = testInput{
|
||||
Out: expectedVars,
|
||||
OutConstStr: map[string]expr.TypedValue{
|
||||
"testConstKey": expr.Must(expr.NewString("testConstValue")),
|
||||
},
|
||||
OutConstInt: map[string]expr.TypedValue{
|
||||
"testInt": testInt,
|
||||
},
|
||||
|
||||
OutRv: &cmpTypes.Record{
|
||||
Values: []*cmpTypes.RecordValue{
|
||||
{
|
||||
Name: "testRv",
|
||||
Value: "testing string",
|
||||
},
|
||||
{
|
||||
Name: "testFloat",
|
||||
Value: "50",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
req.NoError(vars.Decode(&aux))
|
||||
req.Equal(expected, aux.Out)
|
||||
req.Equal(expected, aux)
|
||||
})
|
||||
|
||||
t.Run("omit expressions", func(t *testing.T) {
|
||||
type (
|
||||
testInput struct {
|
||||
Out map[string]expr.TypedValue
|
||||
OutConstInt map[string]expr.TypedValue
|
||||
OutRv *cmpTypes.Record
|
||||
}
|
||||
)
|
||||
var (
|
||||
aux = testInput{}
|
||||
vars, _ = mustExecWorkflow(ctx, t, "omit_expression", types.WorkflowExecParams{})
|
||||
|
||||
expected = testInput{
|
||||
Out: map[string]expr.TypedValue{},
|
||||
OutConstInt: map[string]expr.TypedValue{},
|
||||
OutRv: &cmpTypes.Record{
|
||||
Values: []*cmpTypes.RecordValue{
|
||||
{
|
||||
Name: "testFloat",
|
||||
Value: "50",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
req.NoError(vars.Decode(&aux))
|
||||
req.Equal(expected, aux)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,16 +10,105 @@ workflows:
|
||||
- stepID: 1
|
||||
kind: expressions
|
||||
arguments: [
|
||||
## Initialize empty vars
|
||||
{ target: aux, type: Vars, value: null },
|
||||
|
||||
## Initialize key and value(string) for set expression
|
||||
{ target: testString, type: String, value: "testString" },
|
||||
{ target: foo, type: String, value: "testing string" },
|
||||
|
||||
# Evaluate set for Vars(1st argument) with string value(3rd argument);
|
||||
# it will set string value type for given key to Vars(out)
|
||||
{ target: out, type: Vars, expr: "set(aux, testString, foo)" },
|
||||
|
||||
## Initialize key and value(int) for set expression
|
||||
{ target: testInt, type: String, value: "testInt" },
|
||||
{ target: bar, type: Integer, expr: "40" },
|
||||
|
||||
# Evaluate set for Vars(1st argument) with int value(3rd argument);
|
||||
# it will set int value type for given key to Vars(out)
|
||||
{ target: out, type: Vars, expr: "set(out, testInt, bar)" },
|
||||
|
||||
## Initialize key and value(Vars) for set expression
|
||||
{ target: testVar, type: String, value: "testVar" },
|
||||
{ target: foobar, type: Vars, expr: "{\"testString\": \"testing string\", \"testFloat\": 50}" },
|
||||
{ target: out, type: Vars, expr: "set(aux, testString, foo)" },
|
||||
{ target: out, type: Vars, expr: "set(out, testInt, bar)" },
|
||||
|
||||
# Evaluate set for Vars(1st argument) with Vars value(3rd argument);
|
||||
# it will set Vars value type for given key to Vars(out)
|
||||
{ target: out, type: Vars, expr: "set(out, testVar, foobar)" },
|
||||
|
||||
## Evaluate set for constant: `{}`(1st argument) with string value(3rd argument);
|
||||
# it will set string value type for given key to Vars(outConstStr)
|
||||
{ target: outConstStr, type: Vars, expr: "set({}, \"testConstKey\", \"testConstValue\")" },
|
||||
|
||||
## Evaluate set for constant value(1st argument) with int value(3rd argument);
|
||||
# it will set int value type for given key to Vars(outConstInt)
|
||||
{ target: outConstInt, type: Vars, expr: "set({}, testInt, bar)" },
|
||||
|
||||
## Initialize rv(ComposeRecordValues) and key for set expression
|
||||
{ target: rv, type: ComposeRecordValues, expr: "{}" },
|
||||
{ target: testRv, type: String, value: "testRv" },
|
||||
|
||||
# Evaluate set for ComposeRecordValues(1st argument) with string value(3rd argument);
|
||||
# it will set string value for given key(Name) to ComposeRecordValues(outRv)
|
||||
{ target: outRv, type: ComposeRecordValues, expr: "set(rv, testRv, foo)" },
|
||||
|
||||
# Evaluate set for ComposeRecordValues(1st argument) with float value(3rd argument);
|
||||
# it will set float value for given key(Name) to ComposeRecordValues(outRv)
|
||||
{ target: outRv, type: ComposeRecordValues, expr: "set(outRv, \"testFloat\", 50)" },
|
||||
]
|
||||
- stepID: 2
|
||||
kind: termination
|
||||
|
||||
paths:
|
||||
- { parentID: 1, childID: 2 }
|
||||
|
||||
omit_expression:
|
||||
enabled: true
|
||||
trace: true
|
||||
triggers:
|
||||
- enabled: true
|
||||
stepID: 1
|
||||
|
||||
steps:
|
||||
- stepID: 1
|
||||
kind: expressions
|
||||
arguments: [
|
||||
# Initialize empty vars
|
||||
{ target: aux, type: Vars, value: null },
|
||||
|
||||
## Initialize key and value(string) for set/omit expressions
|
||||
{ target: testString, type: String, value: "testString" },
|
||||
{ target: foo, type: String, value: "testing string" },
|
||||
|
||||
# Set values into Vars
|
||||
{ target: out, type: Vars, expr: "set(aux, testString, foo)" },
|
||||
|
||||
# Evaluate omit for Vars(1st argument) for keys;
|
||||
# it will omit values matching keys from Vars(outConstInt)
|
||||
{ target: out, type: Vars, expr: "omit(out, testString)" },
|
||||
|
||||
## Set/Omit value into constant
|
||||
{ target: testInt, type: String, value: "testInt" },
|
||||
{ target: bar, type: Integer, expr: "40" },
|
||||
{ target: outConstInt, type: Vars, expr: "set({}, testInt, bar)" },
|
||||
|
||||
# Evaluate omit for Vars(1st argument) for keys;
|
||||
# it will omit values matching keys from Vars(outConstInt)
|
||||
{ target: outConstInt, type: Vars, expr: "omit(outConstInt, testInt)" },
|
||||
|
||||
## Initialize rv(ComposeRecordValues) and key for set/omit expressions
|
||||
{ target: rv, type: ComposeRecordValues, expr: "{}" },
|
||||
{ target: testRv, type: String, value: "testRv" },
|
||||
|
||||
# Set values into ComposeRecordValues
|
||||
{ target: outRv, type: ComposeRecordValues, expr: "set(rv, testRv, foo)" },
|
||||
{ target: outRv, type: ComposeRecordValues, expr: "set(rv, \"removeMe\", \"I will be removed\")" },
|
||||
{ target: outRv, type: ComposeRecordValues, expr: "set(outRv, \"testFloat\", 50)" },
|
||||
|
||||
# Evaluate omit for ComposeRecordValues(1st argument) for key(Name);
|
||||
# it will omit values matching Name from ComposeRecordValues(outRv)
|
||||
{ target: outRv, type: ComposeRecordValues, expr: "omit(outRv, \"testRv\", \"removeMe\")" },
|
||||
]
|
||||
- stepID: 2
|
||||
kind: termination
|
||||
|
||||
Reference in New Issue
Block a user