diff --git a/template.go b/template.go index 0a7ce122d..8fac7e7c4 100644 --- a/template.go +++ b/template.go @@ -12,6 +12,8 @@ import ( _ "github.com/flanksource/gomplate/v3/js" "github.com/flanksource/mapstructure" "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" "github.com/pkg/errors" "github.com/robertkrimen/otto" "github.com/robertkrimen/otto/registry" @@ -25,10 +27,11 @@ func init() { } type Template struct { - Template string `yaml:"template,omitempty" json:"template,omitempty"` // Go template - JSONPath string `yaml:"jsonPath,omitempty" json:"jsonPath,omitempty"` - Expression string `yaml:"expr,omitempty" json:"expr,omitempty"` // A cel-go expression - Javascript string `yaml:"javascript,omitempty" json:"javascript,omitempty"` + Template string `yaml:"template,omitempty" json:"template,omitempty"` // Go template + JSONPath string `yaml:"jsonPath,omitempty" json:"jsonPath,omitempty"` + Expression string `yaml:"expr,omitempty" json:"expr,omitempty"` // A cel-go expression + Javascript string `yaml:"javascript,omitempty" json:"javascript,omitempty"` + Functions map[string]func() any `yaml:"-" json:"-"` } func (t Template) IsEmpty() bool { @@ -36,7 +39,23 @@ func (t Template) IsEmpty() bool { } func RunExpression(environment map[string]any, template Template) (any, error) { - env, err := cel.NewEnv(GetCelEnv(environment)...) + funcs := GetCelEnv(environment) + + for name, fn := range template.Functions { + _name := name + _fn := fn + funcs = append(funcs, cel.Function(_name, cel.Overload( + _name, + nil, + cel.AnyType, + cel.FunctionBinding(func(values ...ref.Val) ref.Val { + out := _fn() + return types.DefaultTypeAdapter.NativeToValue(out) + }), + ))) + } + + env, err := cel.NewEnv(funcs...) if err != nil { return "", err } @@ -57,7 +76,7 @@ func RunExpression(environment map[string]any, template Template) (any, error) { out, _, err := prg.Eval(data) if err != nil { - return nil, errors.Wrapf(err, "error evaluating expression %s: %s, %v", template.Expression, err, data) + return nil, errors.Wrapf(err, "error evaluating expression %s: %s, %+v", template.Expression, err, data) } return out.Value(), nil @@ -88,7 +107,21 @@ func RunTemplate(environment map[string]any, template Template) (string, error) // gotemplate if template.Template != "" { tpl := gotemplate.New("") - tpl, err := tpl.Funcs(funcMap).Parse(template.Template) + funcs := make(map[string]any) + if len(template.Functions) > 0 { + for k, v := range funcMap { + funcs[k] = v + } + for k, v := range template.Functions { + funcs[k] = v + } + } else { + funcs = funcMap + } + for k, v := range template.Functions { + funcs[k] = v + } + tpl, err := tpl.Funcs(funcs).Parse(template.Template) if err != nil { return "", err } diff --git a/tests/cel_test.go b/tests/cel_test.go index 46c63748b..53b841306 100644 --- a/tests/cel_test.go +++ b/tests/cel_test.go @@ -29,13 +29,6 @@ type Test struct { out string } -func TestCel(t *testing.T) { - - tests := []Test{} - - runTests(t, tests) -} - func runTests(t *testing.T, tests []Test) { for _, tc := range tests { t.Run(tc.expression, func(t *testing.T) { @@ -48,6 +41,27 @@ func runTests(t *testing.T, tests []Test) { } } +func TestFunctions(t *testing.T) { + funcs := map[string]func() any{ + "fn": func() any { + return map[string]any{ + "a": "b", + "c": 1, + } + }, + } + + out, err := gomplate.RunTemplate(map[string]interface{}{ + "hello": "hi", + }, gomplate.Template{ + Expression: "hello + ' ' + fn().a", + Functions: funcs, + }) + + assert.ErrorIs(t, nil, err) + assert.Equal(t, "hi b", out) +} + func TestRegex(t *testing.T) { runTests(t, []Test{ {nil, `' asdsa A123 asdsd'.find(r"A\d{3}")`, "A123"}, diff --git a/tests/gomplate_test.go b/tests/gomplate_test.go index dbfe58faa..a4e5990f0 100644 --- a/tests/gomplate_test.go +++ b/tests/gomplate_test.go @@ -11,6 +11,30 @@ import ( "github.com/stretchr/testify/assert" ) +func TestGomplateFunctions(t *testing.T) { + funcs := map[string]func() any{ + "fn": func() any { + return map[string]any{ + "a": "b", + "c": 1, + } + }, + "fn1": func() any { + return "c" + }, + } + + out, err := gomplate.RunTemplate(map[string]interface{}{ + "hello": "hi", + }, gomplate.Template{ + Template: "{{.hello}} {{fn1}}", + Functions: funcs, + }) + + assert.ErrorIs(t, nil, err) + assert.Equal(t, "hi c", out) +} + func TestGomplate(t *testing.T) { tests := []struct { env map[string]interface{}