3
0

More improvements on expr vars & rec values

This commit is contained in:
Denis Arh 2021-03-30 15:52:53 +02:00
parent 597c18fd16
commit 333b2f344f
11 changed files with 75 additions and 92 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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