From 544515c2a6bd44454bbdb86829e5b63da798fd71 Mon Sep 17 00:00:00 2001 From: jamesread Date: Sun, 15 Feb 2026 00:19:36 +0000 Subject: [PATCH] chore: #829, json support in template engine --- service/internal/tpl/templates.go | 15 ++++- service/internal/tpl/templates_test.go | 81 ++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 service/internal/tpl/templates_test.go diff --git a/service/internal/tpl/templates.go b/service/internal/tpl/templates.go index 580b907..cb9978f 100644 --- a/service/internal/tpl/templates.go +++ b/service/internal/tpl/templates.go @@ -1,6 +1,7 @@ package tpl import ( + "encoding/json" "fmt" "regexp" "strings" @@ -12,8 +13,20 @@ import ( log "github.com/sirupsen/logrus" ) +func jsonFunc(v any) (string, error) { + if v == nil { + return "null", nil + } + data, err := json.Marshal(v) + if err != nil { + return "", err + } + return string(data), nil +} + var tpl = template.New("tpl"). - Option("missingkey=error") + Option("missingkey=error"). + Funcs(template.FuncMap{"Json": jsonFunc}) type olivetinInfo struct { Build *installationinfo.BuildInfo diff --git a/service/internal/tpl/templates_test.go b/service/internal/tpl/templates_test.go new file mode 100644 index 0000000..d46f902 --- /dev/null +++ b/service/internal/tpl/templates_test.go @@ -0,0 +1,81 @@ +package tpl + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/OliveTin/OliveTin/internal/entities" + "github.com/stretchr/testify/assert" +) + +func TestParseTemplateWithActionContext_Json(t *testing.T) { + tests := []struct { + name string + source string + ent *entities.Entity + args map[string]string + expectedOutput string + expectError bool + checkJsonOnly bool + }{ + { + name: "Arguments piped to Json", + source: `echo {{ .Arguments | Json }}`, + ent: nil, + args: map[string]string{"value": "true", "ot_username": "alice"}, + expectedOutput: `echo `, + expectError: false, + checkJsonOnly: true, + }, + { + name: "CurrentEntity field piped to Json", + source: `curl -d {{ .CurrentEntity.foo.bar | Json }}`, + ent: &entities.Entity{Data: map[string]any{"foo": map[string]any{"bar": "baz"}}}, + args: nil, + expectedOutput: `curl -d "baz"`, + expectError: false, + }, + { + name: "CurrentEntity nested object piped to Json", + source: `curl --json -d {{ .CurrentEntity.payload | Json }}`, + ent: &entities.Entity{Data: map[string]any{"payload": map[string]any{"on": true}}}, + args: nil, + expectedOutput: `curl --json -d {"on":true}`, + expectError: false, + }, + { + name: "Single argument value as Json", + source: `echo {{ .Arguments.value | Json }}`, + ent: nil, + args: map[string]string{"value": "hello"}, + expectedOutput: `echo "hello"`, + expectError: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output, err := ParseTemplateWithActionContext(tt.source, tt.ent, tt.args) + if tt.expectError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + if tt.checkJsonOnly { + prefix := strings.TrimSuffix(tt.expectedOutput, " ") + assert.True(t, strings.HasPrefix(output, prefix), "output %q should start with %q", output, prefix) + jsonPart := strings.TrimPrefix(output, prefix) + jsonPart = strings.TrimSpace(jsonPart) + var decoded map[string]string + err := json.Unmarshal([]byte(jsonPart), &decoded) + assert.NoError(t, err) + for k, v := range tt.args { + assert.Equal(t, v, decoded[k], "decoded JSON should contain %s=%s", k, v) + } + assert.Len(t, decoded, len(tt.args)) + } else { + assert.Equal(t, tt.expectedOutput, output) + } + }) + } +}