diff --git a/compose/automation/expr_types.gen.go b/compose/automation/expr_types.gen.go index 996b34c42..7c793e6e8 100644 --- a/compose/automation/expr_types.gen.go +++ b/compose/automation/expr_types.gen.go @@ -388,13 +388,6 @@ func (t *ComposeRecord) Assign(val interface{}) error { } } -// Select is field accessor for *types.Record -// -// Similar to SelectGVal but returns typed values -func (t ComposeRecord) Select(k string) (TypedValue, error) { - return composeRecordTypedValueSelector(t.value, k) -} - func (t ComposeRecord) Has(k string) bool { switch k { case "ID", "recordID": @@ -576,41 +569,3 @@ func (t *ComposeRecordValueErrorSet) Assign(val interface{}) error { return nil } } - -// ComposeRecordValues is an expression type, wrapper for types.RecordValueSet type -type ComposeRecordValues struct{ value types.RecordValueSet } - -// NewComposeRecordValues creates new instance of ComposeRecordValues expression type -func NewComposeRecordValues(val interface{}) (*ComposeRecordValues, error) { - if c, err := CastToComposeRecordValues(val); err != nil { - return nil, fmt.Errorf("unable to create ComposeRecordValues: %w", err) - } else { - return &ComposeRecordValues{value: c}, nil - } -} - -// Return underlying value on ComposeRecordValues -func (t ComposeRecordValues) Get() interface{} { return t.value } - -// Return underlying value on ComposeRecordValues -func (t ComposeRecordValues) GetValue() types.RecordValueSet { return t.value } - -// Return type name -func (ComposeRecordValues) Type() string { return "ComposeRecordValues" } - -// Convert value to types.RecordValueSet -func (ComposeRecordValues) Cast(val interface{}) (TypedValue, error) { - return NewComposeRecordValues(val) -} - -// Assign new value to ComposeRecordValues -// -// value is first passed through CastToComposeRecordValues -func (t *ComposeRecordValues) Assign(val interface{}) error { - if c, err := CastToComposeRecordValues(val); err != nil { - return err - } else { - t.value = c - return nil - } -} diff --git a/compose/automation/expr_types.go b/compose/automation/expr_types.go index fd56cf068..0bb0bbfd2 100644 --- a/compose/automation/expr_types.go +++ b/compose/automation/expr_types.go @@ -97,7 +97,7 @@ var _ expr.DeepFieldAssigner = &ComposeRecord{} func (t *ComposeRecord) AssignFieldValue(kk []string, val expr.TypedValue) error { switch kk[0] { case "values": - return assignToComposeRecordValues(&t.value.Values, kk[1:], val) + return assignToComposeRecordValues(t.value, kk[1:], val) // @todo deep setting labels default: return assignToComposeRecord(t.value, kk[0], val) @@ -123,6 +123,23 @@ func (t ComposeRecord) SelectGVal(ctx context.Context, k string) (interface{}, e return composeRecordGValSelector(t.value, k) } +// Select is field accessor for *types.ComposeRecord +// +// Similar to SelectGVal but returns typed values +func (t ComposeRecord) Select(k string) (expr.TypedValue, error) { + if k == "values" { + if t.value.Values == nil { + t.value.Values = types.RecordValueSet{} + } + + return &ComposeRecordValues{value: t.value}, nil + } + + return composeRecordTypedValueSelector(t.value, k) +} + +type ComposeRecordValues struct{ value *types.Record } + func CastToComposeRecordValues(val interface{}) (out types.RecordValueSet, err error) { out = types.RecordValueSet{} switch val := val.(type) { @@ -174,7 +191,7 @@ func CastToComposeRecordValues(val interface{}) (out types.RecordValueSet, err e } func (t *ComposeRecordValues) AssignFieldValue(pp []string, val expr.TypedValue) error { - return assignToComposeRecordValues(&t.value, pp, val) + return assignToComposeRecordValues(t.value, pp, val) } // SelectGVal implements gval.Selector requirements @@ -182,34 +199,60 @@ func (t *ComposeRecordValues) AssignFieldValue(pp []string, val expr.TypedValue) // It allows gval lib to access Record's underlying value (*types.RecordValues) // and it's fields // -func (t ComposeRecordValues) SelectGVal(ctx context.Context, k string) (interface{}, error) { +func (t *ComposeRecordValues) SelectGVal(ctx context.Context, k string) (interface{}, error) { return composeRecordValuesGValSelector(t.value, k) } // Select is field accessor for *types.Record // // Similar to SelectGVal but returns typed values -func (t ComposeRecordValues) Select(k string) (expr.TypedValue, error) { +func (t *ComposeRecordValues) Select(k string) (expr.TypedValue, error) { return composeRecordValuesTypedValueSelector(t.value, k) } func (t ComposeRecordValues) Has(k string) bool { - return t.value.Get(k, 0) != nil + return t.value.Values.Get(k, 0) != nil } // recordGValSelector is field accessor for *types.RecordValueSet -func composeRecordValuesGValSelector(res types.RecordValueSet, k string) (interface{}, error) { - vv := res.FilterByName(k) +func composeRecordValuesGValSelector(res *types.Record, k string) (interface{}, error) { + var ( + vv = res.Values.FilterByName(k) + + multiValueField bool + field *types.ModuleField + ) + + if mod := res.GetModule(); mod != nil { + fld := mod.Fields.FindByName(k) + + if fld == nil { + return nil, fmt.Errorf("field '%s' does not exist on module %s", k, mod.Name) + } + + multiValueField = fld.Multi + } + + switch { + case len(vv) == 0: + if field != nil && field.IsBoolean() { + return false, nil + } - switch len(vv) { - case 0: return nil, nil - case 1: - return vv[0].Value, nil + + case len(vv) == 1 && !multiValueField: + return recordValueCast(field, vv[0]) + default: - out := make([]string, 0, len(vv)) + out := make([]interface{}, 0, len(vv)) return out, vv.Walk(func(v *types.RecordValue) error { - out = append(out, v.Value) + i, err := recordValueCast(field, v) + if err != nil { + return err + } + + out = append(out, i) return nil }) } @@ -218,34 +261,49 @@ func composeRecordValuesGValSelector(res types.RecordValueSet, k string) (interf // recordValuesTypedValueSelector is field accessor for *types.RecordValueSet // // @todo return appropriate types (atm all values are returned as String) -func composeRecordValuesTypedValueSelector(res types.RecordValueSet, k string) (expr.TypedValue, error) { - vv := res.FilterByName(k) +func composeRecordValuesTypedValueSelector(res *types.Record, k string) (expr.TypedValue, error) { + var ( + vv = res.Values.FilterByName(k) + + multiValueField bool + field *types.ModuleField + ) + + if mod := res.GetModule(); mod != nil { + field = mod.Fields.FindByName(k) + + if field == nil { + return nil, fmt.Errorf("field '%s' does not exist on module %s", k, mod.Name) + } + + multiValueField = field.Multi + } switch { case len(vv) == 0: - return nil, nil - case len(vv) == 1: - return expr.NewString(vv[0].Value) - default: - mval := make([]expr.TypedValue, 0, len(vv)) - _ = vv.Walk(func(v *types.RecordValue) error { - mval = append(mval, expr.Must(expr.NewString(v.Value))) - return nil - }) + if field != nil && field.IsBoolean() { + return &expr.Boolean{}, nil + } - return expr.NewArray(mval) + return nil, nil + case len(vv) == 1 && !multiValueField: + return recordValueToExprTypedValue(field, vv[0]) + default: + return recordValueSetoToExprArray(field, vv...) } } -// assignToRecordValuesSet is field value setter for *types.Record -func assignToComposeRecordValues(res *types.RecordValueSet, pp []string, val interface{}) (err error) { +// assignToRecordValuesSet is field value setter for *types.RecordValueSet +// +// We'll be using types.Record for the base (and not types.RecordValueSet) +func assignToComposeRecordValues(res *types.Record, pp []string, val interface{}) (err error) { if len(pp) < 1 { switch val := expr.UntypedValue(val).(type) { case types.RecordValueSet: - *res = val + res.Values = val return case *types.Record: - *res = val.Values + *res = *val return } @@ -273,7 +331,7 @@ func assignToComposeRecordValues(res *types.RecordValueSet, pp []string, val int return err } - *res = res.Set(rv) + res.Values = res.Values.Set(rv) } return nil @@ -317,11 +375,51 @@ func assignToComposeRecordValues(res *types.RecordValueSet, pp []string, val int } } - *res = res.Set(rv) + res.Values = res.Values.Set(rv) return nil } +// NewComposeRecordValues creates new instance of ComposeRecordValues expression type +func NewComposeRecordValues(val interface{}) (*ComposeRecordValues, error) { + // Try to cast to ComposeRecord first + if rec, err := CastToComposeRecord(val); err == nil { + return &ComposeRecordValues{value: rec}, nil + } + + if c, err := CastToComposeRecordValues(val); err != nil { + return nil, fmt.Errorf("unable to create ComposeRecordValues: %w", err) + } else { + return &ComposeRecordValues{value: &types.Record{Values: c}}, nil + } +} + +// Return underlying value on ComposeRecordValues +func (t ComposeRecordValues) Get() interface{} { return t.value } + +// Return underlying value on ComposeRecordValues +func (t ComposeRecordValues) GetValue() types.RecordValueSet { return t.value.Values } + +// Return type name +func (ComposeRecordValues) Type() string { return "ComposeRecordValues" } + +// Convert value to types.RecordValueSet +func (ComposeRecordValues) Cast(val interface{}) (expr.TypedValue, error) { + return NewComposeRecordValues(val) +} + +// Assign new value to ComposeRecordValues +// +// value is first passed through CastToComposeRecordValues +func (t *ComposeRecordValues) Assign(val interface{}) error { + if c, err := CastToComposeRecordValues(val); err != nil { + return err + } else { + t.value.Values = c + return nil + } +} + func CastToComposeRecordValueErrorSet(val interface{}) (out *types.RecordValueErrorSet, err error) { switch val := expr.UntypedValue(val).(type) { case *types.RecordValueErrorSet: @@ -330,3 +428,80 @@ func CastToComposeRecordValueErrorSet(val interface{}) (out *types.RecordValueEr return nil, fmt.Errorf("unable to cast type %T to %T", val, out) } } + +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.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) + + } +} + +func recordValueSetoToExprArray(field *types.ModuleField, vv ...*types.RecordValue) (arr *expr.Array, err error) { + var ( + tv expr.TypedValue + ) + + arr = &expr.Array{} + + for _, v := range vv { + tv, err = recordValueToExprTypedValue(field, v) + if err != nil { + return + } + + arr.Push(tv) + } + + return +} diff --git a/compose/automation/expr_types.yaml b/compose/automation/expr_types.yaml index 3c1da8ced..c286fbb29 100644 --- a/compose/automation/expr_types.yaml +++ b/compose/automation/expr_types.yaml @@ -40,11 +40,13 @@ types: - { name: 'updatedBy', exprType: 'ID', goType: 'uint64', mode: ro } - { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro } - { name: 'deletedBy', exprType: 'ID', goType: 'uint64', mode: ro } - customGValSelector: true + customSelector: true + customGValSelector: true customFieldAssigner: true ComposeRecordValues: - as: 'types.RecordValueSet' + customType: true + as: 'types.RecordValueSet' ComposeRecordValueErrorSet: as: '*types.RecordValueErrorSet' diff --git a/compose/automation/expr_types_test.go b/compose/automation/expr_types_test.go index 47dc22a32..3b2abb739 100644 --- a/compose/automation/expr_types_test.go +++ b/compose/automation/expr_types_test.go @@ -13,13 +13,13 @@ func TestSetRecordValuesWithPath(t *testing.T) { var ( r = require.New(t) - rvs = &ComposeRecordValues{types.RecordValueSet{}} + rvs = &ComposeRecordValues{&types.Record{}} ) r.NoError(expr.Assign(rvs, "field1", expr.Must(expr.NewString("a")))) r.NoError(expr.Assign(rvs, "field1.1", expr.Must(expr.NewString("a")))) - r.True(rvs.value.Has("field1", 0)) - r.True(rvs.value.Has("field1", 1)) + r.True(rvs.value.Values.Has("field1", 0)) + r.True(rvs.value.Values.Has("field1", 1)) }) t.Run("cast string map", func(t *testing.T) { @@ -56,14 +56,17 @@ func TestRecordFieldValuesAccess(t *testing.T) { &types.ModuleField{Name: "m1", Multi: true}, &types.ModuleField{Name: "m2", Multi: true}, &types.ModuleField{Name: "s2", Multi: false}, + &types.ModuleField{Name: "b0", Multi: false, Kind: "Bool"}, + &types.ModuleField{Name: "b1", Multi: false, Kind: "Bool"}, }} raw = &types.Record{Values: types.RecordValueSet{ &types.RecordValue{Name: "s1", Value: "sVal1"}, - &types.RecordValue{Name: "m1", Value: "mVal1.0"}, + &types.RecordValue{Name: "m1", Value: "mVal1.0", Place: 0}, &types.RecordValue{Name: "m1", Value: "mVal1.1", Place: 1}, &types.RecordValue{Name: "m1", Value: "mVal1.2", Place: 2}, - &types.RecordValue{Name: "m2", Value: "mVal2.0"}, + &types.RecordValue{Name: "m2", Value: "mVal2.0", Place: 0}, + &types.RecordValue{Name: "b1", Value: "1", Place: 0}, }} tval = &ComposeRecord{value: raw} @@ -74,61 +77,70 @@ func TestRecordFieldValuesAccess(t *testing.T) { raw.SetModule(mod) t.Run("via typed value", func(t *testing.T) { - var ( - req = require.New(t) - ) + tcc := []struct { + expects interface{} + path string + }{ + {"sVal1", "rec.values.s1"}, + {"mVal1.0", "rec.values.m1.0"}, + {"mVal1.1", "rec.values.m1.1"}, + {"mVal2.0", "rec.values.m2.0"}, + // expecting valid value (false) even when boolean fields are not set + {false, "rec.values.b0"}, + {true, "rec.values.b1"}, + } - v, err = expr.Select(scope, "rec.values.s1") - req.NoError(err) - req.NotEmpty(v) - req.Equal("sVal1", v.Get()) + for _, tc := range tcc { + t.Run(tc.path, func(t *testing.T) { + var ( + req = require.New(t) + ) - v, err = expr.Select(scope, "rec.values.m1.0") - req.NoError(err) - req.NotEmpty(v) - req.Equal("mVal1.0", v.Get()) - - v, err = expr.Select(scope, "rec.values.m1.1") - req.NoError(err) - req.NotEmpty(v) - req.Equal("mVal1.1", v.Get()) - - // @todo when RecordValueSet supports back-ref to record, - // we can employ better field access: - // - no error on missing values when field exists - // - proper handling of multi-value field values - // - proper value-types that corelate to field types - //v, err = expr.Select(scope, "rec.values.m2.0") - //req.NoError(err) - //req.NotEmpty(v) - //req.Equal("mVal2.0", v.Get()) + v, err = expr.Select(scope, tc.path) + req.NoError(err) + req.Equal(tc.expects, v.Get()) + }) + } }) t.Run("via gval selector", func(t *testing.T) { - var ( - req = require.New(t) - parser = expr.NewParser() - ) + tcc := []struct { + test bool + expr string + }{ + {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`}, + {true, `rec.values.s2 != "foo"`}, + {false, `rec.values.s2 == "foo"`}, + {true, `rec.values.m1[0] == "mVal1.0"`}, + {true, `rec.values.m1[1] == "mVal1.1"`}, + {true, `rec.values.m2[0] == "mVal2.0"`}, + {true, `!rec.values.b0`}, + {false, `rec.values.b0`}, + {true, `rec.values.b1`}, + {false, `!rec.values.b1`}, + } - eval, err := parser.Parse(`rec.values.s1 == "sVal1"`) - req.NoError(err) - req.True(eval.Test(context.Background(), scope)) + for _, tc := range tcc { + var ( + req = require.New(t) + parser = expr.NewParser() + ) - eval, err = parser.Parse(`rec.values.s1 != "foo"`) - req.NoError(err) - req.True(eval.Test(context.Background(), scope)) + t.Run(tc.expr, func(t *testing.T) { + eval, err := parser.Parse(tc.expr) + req.NoError(err) - eval, err = parser.Parse(`rec.values.m1[0] == "mVal1.0"`) - req.NoError(err) - req.True(eval.Test(context.Background(), scope)) - - eval, err = parser.Parse(`rec.values.m1[1] == "mVal1.1"`) - req.NoError(err) - req.True(eval.Test(context.Background(), scope)) - - eval, err = parser.Parse(`rec.values.m2[0] == "mVal2.0"`) - req.NoError(err) - req.True(eval.Test(context.Background(), scope)) + test, err := eval.Test(context.Background(), scope) + req.NoError(err) + req.Equal(tc.test, test) + }) + } }) } @@ -136,63 +148,63 @@ func TestAssignToComposeRecordValues(t *testing.T) { t.Run("assign simple", func(t *testing.T) { var ( req = require.New(t) - target = types.RecordValueSet{} + target = &types.Record{Values: types.RecordValueSet{}} ) - req.NoError(assignToComposeRecordValues(&target, []string{"a"}, "b")) - req.Len(target, 1) - req.True(target.Has("a", 0)) - req.NoError(assignToComposeRecordValues(&target, []string{"a", "1"}, "b")) - req.Len(target, 2) - req.True(target.Has("a", 0)) - req.True(target.Has("a", 1)) + req.NoError(assignToComposeRecordValues(target, []string{"a"}, "b")) + req.Len(target.Values, 1) + req.True(target.Values.Has("a", 0)) + req.NoError(assignToComposeRecordValues(target, []string{"a", "1"}, "b")) + req.Len(target.Values, 2) + req.True(target.Values.Has("a", 0)) + req.True(target.Values.Has("a", 1)) }) t.Run("assign rvs", func(t *testing.T) { var ( req = require.New(t) - target = types.RecordValueSet{} + target = &types.Record{Values: types.RecordValueSet{}} ) - req.NoError(assignToComposeRecordValues(&target, nil, types.RecordValueSet{{}})) - req.Len(target, 1) + req.NoError(assignToComposeRecordValues(target, nil, types.RecordValueSet{{}})) + req.Len(target.Values, 1) }) t.Run("assign record", func(t *testing.T) { var ( req = require.New(t) - target = types.RecordValueSet{} + target = &types.Record{Values: types.RecordValueSet{}} ) - req.NoError(assignToComposeRecordValues(&target, nil, &types.Record{Values: types.RecordValueSet{{}}})) - req.Len(target, 1) + req.NoError(assignToComposeRecordValues(target, nil, &types.Record{Values: types.RecordValueSet{{}}})) + req.Len(target.Values, 1) }) t.Run("overwrite rvs", func(t *testing.T) { var ( req = require.New(t) - target = types.RecordValueSet{{Name: "a"}} + target = &types.Record{Values: types.RecordValueSet{{Name: "a"}}} ) - req.NoError(assignToComposeRecordValues(&target, nil, types.RecordValueSet{{Name: "b"}})) - req.Len(target, 1) - req.False(target.Has("a", 0)) - req.True(target.Has("b", 0)) + req.NoError(assignToComposeRecordValues(target, nil, types.RecordValueSet{{Name: "b"}})) + req.Len(target.Values, 1) + req.False(target.Values.Has("a", 0)) + req.True(target.Values.Has("b", 0)) }) t.Run("assign multiple values", func(t *testing.T) { var ( req = require.New(t) - target = types.RecordValueSet{} + target = &types.Record{Values: types.RecordValueSet{}} ) - req.Error(assignToComposeRecordValues(&target, []string{"a", "2"}, expr.Must(expr.NewAny([]interface{}{"1", "2"})))) - req.Len(target, 0) + req.Error(assignToComposeRecordValues(target, []string{"a", "2"}, expr.Must(expr.NewAny([]interface{}{"1", "2"})))) + req.Len(target.Values, 0) - req.NoError(assignToComposeRecordValues(&target, []string{"a"}, expr.Must(expr.NewAny([]interface{}{"1", "2"})))) - req.Len(target, 2) + req.NoError(assignToComposeRecordValues(target, []string{"a"}, expr.Must(expr.NewAny([]interface{}{"1", "2"})))) + req.Len(target.Values, 2) - req.NoError(assignToComposeRecordValues(&target, []string{"a"}, expr.Must(expr.NewAny([]string{"1", "2"})))) - req.Len(target, 2) + req.NoError(assignToComposeRecordValues(target, []string{"a"}, expr.Must(expr.NewAny([]string{"1", "2"})))) + req.Len(target.Values, 2) }) } diff --git a/pkg/codegen/assets/expr_types.gen.go.tpl b/pkg/codegen/assets/expr_types.gen.go.tpl index bf9b93bdb..3e07ce49a 100644 --- a/pkg/codegen/assets/expr_types.gen.go.tpl +++ b/pkg/codegen/assets/expr_types.gen.go.tpl @@ -23,6 +23,7 @@ var _ = context.Background var _ = fmt.Errorf {{ range $exprType, $def := .Types }} +{{ if not $def.CustomType }} // {{ $exprType }} is an expression type, wrapper for {{ $def.As }} type type {{ $exprType }} struct{ value {{ $def.As }} } @@ -81,12 +82,14 @@ func (t {{ $exprType }}) SelectGVal(ctx context.Context, k string) (interface{}, } {{ end }} +{{ if not $def.CustomSelector }} // Select is field accessor for {{ $def.As }} // // Similar to SelectGVal but returns typed values func (t {{ $exprType }}) Select(k string) (TypedValue, error) { return {{ unexport $exprType "TypedValueSelector" }}(t.value, k) } +{{ end }} func (t {{ $exprType }}) Has(k string) bool { switch k { @@ -150,7 +153,7 @@ func {{ $def.AssignerFn }}(res {{ $def.As }}, k string, val interface{}) (error) return fmt.Errorf("unknown field '%s'", k) } {{ end }} -{{ end }} - -{{ end }} +{{ end }} {{/* if $def.Struct */}} +{{ end }} {{/* if not $def.CustomType */}} +{{ end }} {{/* types loop */}} diff --git a/pkg/codegen/expr_types.go b/pkg/codegen/expr_types.go index 97c188c18..893afaa16 100644 --- a/pkg/codegen/expr_types.go +++ b/pkg/codegen/expr_types.go @@ -28,7 +28,9 @@ type ( AssignerFn string `yaml:"assignerFn"` BuiltInCastFn bool BuiltInAssignerFn bool + CustomType bool `yaml:"customType"` CustomGValSelector bool `yaml:"customGValSelector"` + CustomSelector bool `yaml:"customSelector"` CustomFieldAssigner bool `yaml:"customFieldAssigner"` Struct []*exprTypeStructDef diff --git a/pkg/expr/expr_types.go b/pkg/expr/expr_types.go index b7610741a..2683bd1e6 100644 --- a/pkg/expr/expr_types.go +++ b/pkg/expr/expr_types.go @@ -213,6 +213,11 @@ func (t Array) Has(k string) bool { } } +// Push appends value to array +func (t *Array) Push(v TypedValue) { + t.value = append(t.value, v) +} + // Select is field accessor for *types.Array // // Similar to SelectGVal but returns typed values diff --git a/pkg/expr/parser.go b/pkg/expr/parser.go index 0cf599436..9f7d33ddc 100644 --- a/pkg/expr/parser.go +++ b/pkg/expr/parser.go @@ -3,7 +3,6 @@ package expr import ( "context" "fmt" - "github.com/PaesslerAG/gval" ) @@ -71,7 +70,12 @@ func (e *gvalEval) Eval(ctx context.Context, scope *Vars) (interface{}, error) { } func (e *gvalEval) Test(ctx context.Context, scope *Vars) (bool, error) { - return e.evaluable.EvalBool(ctx, scope.Dict()) + r, err := e.evaluable(ctx, scope.Dict()) + if err != nil { + return false, err + } + + return !isEmpty(r), nil } func Parser(ll ...gval.Language) gval.Language {