From 9d272d5bdaacda53e90e7619d2a15bbf97a91dcb Mon Sep 17 00:00:00 2001 From: Vivek Patel Date: Fri, 30 Jul 2021 19:09:05 +0530 Subject: [PATCH] Fixes string expressions int param casting issue in workflow --- compose/automation/expr_types_test.go | 70 +++++++++++++++++++++++++++ pkg/expr/func_str.go | 29 +++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/compose/automation/expr_types_test.go b/compose/automation/expr_types_test.go index 0c7f62e4b..53893699d 100644 --- a/compose/automation/expr_types_test.go +++ b/compose/automation/expr_types_test.go @@ -476,3 +476,73 @@ func TestIsNil(t *testing.T) { }) } } + +func TestGvalStringFunctionParamsIntCasting(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + + scope = &expr.Vars{} + ) + + t.Run("substring", func(t *testing.T) { + eval, err := expr.NewParser().Parse(`substring("foobar", 1, -1)`) + req.NoError(err) + + res, err := eval.Eval(ctx, scope) + req.NoError(err) + + req.Equal("oobar", res.(string)) + }) + + t.Run("replace", func(t *testing.T) { + eval, err := expr.NewParser().Parse(`replace(" foo foo foo", "foo", "bar", 1)`) + req.NoError(err) + + res, err := eval.Eval(ctx, scope) + req.NoError(err) + + req.Equal(" bar foo foo", res.(string)) + }) + + t.Run("split", func(t *testing.T) { + eval, err := expr.NewParser().Parse(`split("foo-bar", "-")`) + req.NoError(err) + + res, err := eval.Eval(ctx, scope) + req.NoError(err) + + req.Equal([]string{"foo", "bar"}, res.([]string)) + }) + + t.Run("has", func(t *testing.T) { + eval, err := expr.NewParser().Parse(`has([1,2], 2)`) + req.NoError(err) + + res, err := eval.Eval(ctx, scope) + req.NoError(err) + + req.Equal(true, res) + }) + + t.Run("repeat", func(t *testing.T) { + eval, err := expr.NewParser().Parse(`repeat("! ", 3)`) + req.NoError(err) + + res, err := eval.Eval(ctx, scope) + req.NoError(err) + + req.Equal("! ! ! ", res.(string)) + }) + + t.Run("shorten", func(t *testing.T) { + eval, err := expr.NewParser().Parse(`shorten("This is a whole sentence", "word", 4)`) + req.NoError(err) + + res, err := eval.Eval(ctx, scope) + req.NoError(err) + + req.Equal("This is a whole …", res.(string)) + }) + +} diff --git a/pkg/expr/func_str.go b/pkg/expr/func_str.go index 050f6ffff..76bede31f 100644 --- a/pkg/expr/func_str.go +++ b/pkg/expr/func_str.go @@ -3,6 +3,8 @@ package expr import ( "errors" "fmt" + "github.com/spf13/cast" + "reflect" "regexp" "strings" @@ -22,22 +24,41 @@ func StringFunctions() []gval.Language { gval.Function("format", fmt.Sprintf), gval.Function("title", title), gval.Function("untitle", untitle), - gval.Function("repeat", strings.Repeat), - gval.Function("replace", strings.Replace), + gvalFunc("repeat", strings.Repeat), + gvalFunc("replace", strings.Replace), gval.Function("isUrl", valid.IsURL), gval.Function("isEmail", valid.IsEmail), gval.Function("split", strings.Split), gval.Function("join", join), gval.Function("hasSubstring", hasSubstring), - gval.Function("substring", substring), + gvalFunc("substring", substring), gval.Function("hasPrefix", strings.HasPrefix), gval.Function("hasSuffix", strings.HasSuffix), - gval.Function("shorten", shorten), + gvalFunc("shorten", shorten), gval.Function("camelize", camelize), gval.Function("snakify", snakify), } } +// gvalFunc cast any nth number of float64 param to int +func gvalFunc(name string, fn interface{}) gval.Language { + return gval.Function(name, func(params ...interface{}) (out interface{}) { + in := make([]reflect.Value, len(params)) + for i, param := range params { + if reflect.TypeOf(param).Kind() == reflect.Float64 { + param = cast.ToInt(param) + } + in[i] = reflect.ValueOf(param) + } + fun := reflect.ValueOf(fn) + res := fun.Call(in) + if len(res) > 0 { + out = cast.ToString(reflect.ValueOf(res[0]).Interface()) + } + return + }) +} + func shortest(f string, aa ...string) string { for _, s := range aa { if len(f) > len(s) {