3
0

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:
Vivek Patel
2022-02-16 19:33:31 +05:30
parent 1981608b48
commit ef904e0cd3
7 changed files with 367 additions and 12 deletions

View File

@@ -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
}

View File

@@ -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())
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
})
}

View File

@@ -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