diff --git a/automation/automation/iterators.go b/automation/automation/iterators.go index 398d35435..f7e3eb876 100644 --- a/automation/automation/iterators.go +++ b/automation/automation/iterators.go @@ -71,7 +71,7 @@ func (i *collectionIterator) Next(context.Context, *Vars) (out *Vars, err error) case TypedValue: item = c default: - if item, err = Cast(c); err != nil { + if item, err = Typify(c); err != nil { return } } diff --git a/automation/rest/workflow.go b/automation/rest/workflow.go index d8b584e0b..8ea29fdac 100644 --- a/automation/rest/workflow.go +++ b/automation/rest/workflow.go @@ -164,7 +164,6 @@ func (ctrl Workflow) Exec(ctx context.Context, r *request.WorkflowExec) (interfa if err != nil { return fmt.Errorf("failed to resolve ComposeRecord type: %w", err) } - c.GetValue().SetModule(mod) } diff --git a/automation/types/expr.go b/automation/types/expr.go index 42ee68cfe..613e232ab 100644 --- a/automation/types/expr.go +++ b/automation/types/expr.go @@ -168,7 +168,7 @@ func (set ExprSet) Eval(ctx context.Context, in *expr.Vars) (*expr.Vars, error) return nil, fmt.Errorf("cannot cast value %T to %s: %w", value, e.typ.Type(), err) } } else { - typedValue, err = expr.Cast(value) + typedValue, err = expr.Typify(value) if err != nil { return nil, fmt.Errorf("cannot cast value %T to %s: %w", value, e.typ.Type(), err) } diff --git a/automation/types/expr_test.go b/automation/types/expr_test.go index 41f703aae..92d1ae7c2 100644 --- a/automation/types/expr_test.go +++ b/automation/types/expr_test.go @@ -32,7 +32,7 @@ func TestExprSet_Eval(t *testing.T) { name: "vars with path", set: ExprSet{&Expr{Target: "l1.l2", Expr: `"bar"`}}, input: RVars{"l1": RVars{}.Vars()}, - output: RVars{"l1": RVars{"l2": Must(Cast("bar"))}.Vars()}, + output: RVars{"l1": RVars{"l2": Must(Typify("bar"))}.Vars()}, }, { name: "copy vars with same types", @@ -118,7 +118,7 @@ func TestExprSet_Eval(t *testing.T) { }, output: RVars{ "arr": Must(NewArray([]TypedValue{ - Must(Cast("foo")), + Must(Typify("foo")), })), }, }, diff --git a/compose/automation/expr_types.go b/compose/automation/expr_types.go index 0bb0bbfd2..1fd352b54 100644 --- a/compose/automation/expr_types.go +++ b/compose/automation/expr_types.go @@ -111,7 +111,7 @@ var _ gval.Selector = &ComposeRecord{} // It allows gval lib to access Record's underlying value (*types.Record) // and it's fields // -func (t ComposeRecord) SelectGVal(ctx context.Context, k string) (interface{}, error) { +func (t ComposeRecord) SelectGVal(_ context.Context, k string) (interface{}, error) { if k == "values" { if t.value.Values == nil { t.value.Values = types.RecordValueSet{} @@ -242,12 +242,12 @@ func composeRecordValuesGValSelector(res *types.Record, k string) (interface{}, return nil, nil case len(vv) == 1 && !multiValueField: - return recordValueCast(field, vv[0]) + return vv[0].Cast(field) default: out := make([]interface{}, 0, len(vv)) return out, vv.Walk(func(v *types.RecordValue) error { - i, err := recordValueCast(field, v) + i, err := v.Cast(field) if err != nil { return err } @@ -429,61 +429,20 @@ func CastToComposeRecordValueErrorSet(val interface{}) (out *types.RecordValueEr } } -func recordValueCast(field *types.ModuleField, rv *types.RecordValue) (interface{}, error) { - if field == nil { - // safe fallback to string - return rv.Value, nil - } - - switch { - case field.IsRef(): - return rv.Ref, nil - - case field.IsDateTime(): - return cast.ToTimeE(rv.Value) - - case field.IsBoolean(): - return cast.ToBoolE(rv.Value) - - case field.IsNumeric(): - if field.Options.Precision() == 0 { - return cast.ToInt64E(rv.Value) - } - - return cast.ToFloat64E(rv.Value) - - default: - return rv.Value, nil - - } -} - func recordValueToExprTypedValue(field *types.ModuleField, rv *types.RecordValue) (expr.TypedValue, error) { - if field == nil { - // safe fallback to string - return expr.NewString(rv.Value) - } - switch { + case field == nil: + return expr.NewString(rv.Value) + case field.IsRef(): return expr.NewID(rv.Ref) - case field.IsDateTime(): - return expr.NewDateTime(rv.Value) - - case field.IsBoolean(): - return expr.NewBoolean(rv.Value) - - case field.IsNumeric(): - if field.Options.Precision() == 0 { - return expr.NewInteger(rv.Value) - } - - return expr.NewFloat(rv.Value) - default: - return expr.NewString(rv.Value) - + if v, err := rv.Cast(field); err != nil { + return nil, err + } else { + return expr.Typify(v) + } } } diff --git a/compose/automation/expr_types_test.go b/compose/automation/expr_types_test.go index 3b2abb739..6f55105ab 100644 --- a/compose/automation/expr_types_test.go +++ b/compose/automation/expr_types_test.go @@ -52,10 +52,10 @@ func TestRecordFieldValuesAccess(t *testing.T) { v expr.TypedValue mod = &types.Module{Fields: types.ModuleFieldSet{ - &types.ModuleField{Name: "s1", Multi: false}, - &types.ModuleField{Name: "m1", Multi: true}, - &types.ModuleField{Name: "m2", Multi: true}, - &types.ModuleField{Name: "s2", Multi: false}, + &types.ModuleField{Name: "s1", Multi: false, Kind: "String"}, + &types.ModuleField{Name: "m1", Multi: true, Kind: "String"}, + &types.ModuleField{Name: "m2", Multi: true, Kind: "String"}, + &types.ModuleField{Name: "s2", Multi: false, Kind: "String"}, &types.ModuleField{Name: "b0", Multi: false, Kind: "Bool"}, &types.ModuleField{Name: "b1", Multi: false, Kind: "Bool"}, }} @@ -108,18 +108,24 @@ func TestRecordFieldValuesAccess(t *testing.T) { test bool expr string }{ + // interaction with set values {true, `rec.values.s1 == "sVal1"`}, {false, `rec.values.s1 == "sVal2"`}, {true, `rec.values.s1`}, - {false, `rec.values.s2`}, {true, `rec.values.s1 != "foo"`}, - {true, `rec.values.s2 != "foo"`}, - {true, `!rec.values.s2`}, + + // interaction with unset (= nil) values {true, `rec.values.s2 != "foo"`}, {false, `rec.values.s2 == "foo"`}, + {true, `!rec.values.s2`}, + {false, `rec.values.s2`}, + + // multival {true, `rec.values.m1[0] == "mVal1.0"`}, {true, `rec.values.m1[1] == "mVal1.1"`}, {true, `rec.values.m2[0] == "mVal2.0"`}, + + // booleans {true, `!rec.values.b0`}, {false, `rec.values.b0`}, {true, `rec.values.b1`}, diff --git a/compose/types/record_value.go b/compose/types/record_value.go index e64b7324d..e15a447ea 100644 --- a/compose/types/record_value.go +++ b/compose/types/record_value.go @@ -5,10 +5,8 @@ import ( "encoding/json" "fmt" "github.com/cortezaproject/corteza-server/pkg/filter" - "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/pkg/errors" - "strconv" - "strings" + "github.com/spf13/cast" "time" ) @@ -53,6 +51,34 @@ func (v RecordValue) Clone() *RecordValue { } } +func (v RecordValue) Cast(f *ModuleField) (interface{}, error) { + if f == nil { + // safe fallback to string + return v.Value, nil + } + + switch { + case f.IsRef(): + return v.Ref, nil + + case f.IsDateTime(): + return cast.ToTimeE(v.Value) + + case f.IsBoolean(): + return cast.ToBoolE(v.Value) + + case f.IsNumeric(): + if f.Options.Precision() == 0 { + return cast.ToInt64E(v.Value) + } + + return cast.ToFloat64E(v.Value) + + default: + return v.Value, nil + } +} + func (set RecordValueSet) Clone() (vv RecordValueSet) { vv = make(RecordValueSet, len(vv)) for i := range set { @@ -337,23 +363,6 @@ func (set RecordValueSet) String() (o string) { func (set RecordValueSet) Dict(fields ModuleFieldSet) map[string]interface{} { var ( rval = make(map[string]interface{}) - - format = func(f *ModuleField, v string) interface{} { - switch strings.ToLower(f.Kind) { - case "bool": - return payload.ParseBool(v) - case "number": - if f.Options.Precision() > 0 { - num, _ := strconv.ParseFloat(v, 64) - return num - } - - num, _ := strconv.ParseInt(v, 10, 64) - return num - } - - return v - } ) if len(fields) == 0 { @@ -361,17 +370,20 @@ func (set RecordValueSet) Dict(fields ModuleFieldSet) map[string]interface{} { } _ = fields.Walk(func(f *ModuleField) error { + // make sure all fields are set at least to nil + rval[f.Name] = nil + if f.Multi { var ( rv = set.FilterByName(f.Name) vv = make([]interface{}, len(rv)) ) for i, val := range rv { - vv[i] = format(f, val.Value) + vv[i], _ = val.Cast(f) } rval[f.Name] = vv } else if v := set.Get(f.Name, 0); v != nil { - rval[f.Name] = format(f, v.Value) + rval[f.Name], _ = v.Cast(f) } return nil diff --git a/pkg/expr/expr_types.go b/pkg/expr/expr_types.go index 2683bd1e6..165a66c9e 100644 --- a/pkg/expr/expr_types.go +++ b/pkg/expr/expr_types.go @@ -28,8 +28,8 @@ func ResolveTypes(rt resolvableType, resolver func(typ string) Type) error { return rt.ResolveTypes(resolver) } -// cast intput into some well-known types -func Cast(in interface{}) (tv TypedValue, err error) { +// Typify detects input type and wraps it with expression type +func Typify(in interface{}) (tv TypedValue, err error) { var is bool if tv, is = in.(TypedValue); is { return @@ -135,7 +135,7 @@ func CastToArray(val interface{}) (out []TypedValue, err error) { out = make([]TypedValue, ref.Len()) for i := 0; i < ref.Len(); i++ { item := ref.Index(i).Interface() - out[i], err = Cast(item) + out[i], err = Typify(item) if err != nil { return } diff --git a/pkg/expr/func_arr.go b/pkg/expr/func_arr.go index 7d50d316d..6cac2c26e 100644 --- a/pkg/expr/func_arr.go +++ b/pkg/expr/func_arr.go @@ -36,7 +36,7 @@ func push(arr interface{}, nn ...interface{}) (out interface{}, err error) { stv = append(stv, tv) } else { // wrap unknown types... - stv = append(stv, Must(Cast(n))) + stv = append(stv, Must(Typify(n))) } } diff --git a/pkg/expr/parser.go b/pkg/expr/parser.go index 9f7d33ddc..11e3353a4 100644 --- a/pkg/expr/parser.go +++ b/pkg/expr/parser.go @@ -66,11 +66,13 @@ func (p *gvalParser) ParseEvaluators(ee ...Evaluator) error { } func (e *gvalEval) Eval(ctx context.Context, scope *Vars) (interface{}, error) { - return e.evaluable(ctx, scope.Dict()) + return e.evaluable(ctx, scope) + //return e.evaluable(ctx, scope.Dict()) } func (e *gvalEval) Test(ctx context.Context, scope *Vars) (bool, error) { - r, err := e.evaluable(ctx, scope.Dict()) + r, err := e.evaluable(ctx, scope) + //r, err := e.evaluable(ctx, scope.Dict()) if err != nil { return false, err } diff --git a/pkg/expr/vars.go b/pkg/expr/vars.go index e0fd86d48..8a6e313f3 100644 --- a/pkg/expr/vars.go +++ b/pkg/expr/vars.go @@ -1,6 +1,7 @@ package expr import ( + "context" "database/sql/driver" "encoding/json" "fmt" @@ -225,6 +226,10 @@ func (t *Vars) Value() (driver.Value, error) { return json.Marshal(t) } +func (t Vars) SelectGVal(_ context.Context, k string) (interface{}, error) { + return t.Select(k) +} + // UnmarshalJSON func (t *Vars) UnmarshalJSON(in []byte) (err error) { if len(in) == 0 {