From efe2182e21be93b9fb57a091b768e9abcf1307a8 Mon Sep 17 00:00:00 2001 From: William Poussier Date: Tue, 25 Aug 2020 11:50:25 +0200 Subject: [PATCH] feat: add fromJson and mustFromJson to values func map (#164) * feat: add fromJson and mustFromJson to values func map Signed-off-by: William Poussier * fix: tests Signed-off-by: William Poussier * fix: return a reflect value Signed-off-by: William Poussier * doc: add better example of fromJSON template func Signed-off-by: William Poussier --- README.md | 16 +++++++++------- engine/engine_test.go | 17 +++++++++++++++++ engine/templates_tests/jsonParsing.yaml | 22 ++++++++++++++++++++++ engine/values/values.go | 17 +++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 engine/templates_tests/jsonParsing.yaml diff --git a/README.md b/README.md index c9f62bc5..b0548ed8 100644 --- a/README.md +++ b/README.md @@ -182,13 +182,15 @@ A user can be allowed to resolve a task in three ways: The following templating functions are available: -| Name | Description | Reference | -| --------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -| **`Golang`** | Builtin functions from Golang text template | [Doc](https://golang.org/pkg/text/template/#hdr-Actions) | -| **`Sprig`** | Extended set of functions from the Sprig project | [Doc](https://masterminds.github.io/sprig/) | -| **`field`** | Equivalent to the dot notation, for entries with forbidden characters | ``{{field `config` `foo.bar`}}`` | -| **`eval`** | Evaluates the value of a template variable | ``{{eval `var1`}}`` | -| **`evalCache`** | Evaluates the value of a template variable, and cache for future usage (to avoid further computation) | ``{{evalCache `var1`}}`` | +| Name | Description | Reference | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| **`Golang`** | Builtin functions from Golang text template | [Doc](https://golang.org/pkg/text/template/#hdr-Actions) | +| **`Sprig`** | Extended set of functions from the Sprig project | [Doc](https://masterminds.github.io/sprig/) | +| **`field`** | Equivalent to the dot notation, for entries with forbidden characters | ``{{field `config` `foo.bar`}}`` | +| **`eval`** | Evaluates the value of a template variable | ``{{eval `var1`}}`` | +| **`evalCache`** | Evaluates the value of a template variable, and cache for future usage (to avoid further computation) | ``{{evalCache `var1`}}`` | +| **`fromJson`** | Decodes a JSON document into a structure. If the input cannot be decoded as JSON, the function will return an empty string | ``{{fromJson `{"a":"b"}`}}`` | +| **`mustFromJson`** | Similar to **`fromJson`**, but will return an error in case the JSON is invalid. A common usecase consists of returning a JSON stringified data structure from a JavaScript expression (object, array), and use one of its members in the template. Example: ``{{(eval `myExpression` \| fromJson).myArr}}`` or ``{{(eval `myExpression` \| fromJson).myObj}}`` | ``{{mustFromJson `{"a":"b"}`}}`` | ### Basic properties diff --git a/engine/engine_test.go b/engine/engine_test.go index 8e3bf1f0..132a113c 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -706,6 +706,23 @@ func TestJSONNumberTemplating(t *testing.T) { assert.Equal(t, "/id/1619464078", child[values.OutputKey].(string)) } +func TestJSONParsing(t *testing.T) { + res, err := createResolution("jsonParsing.yaml", nil, nil) + assert.NotNil(t, res) + assert.Nil(t, err) + + res, err = runResolution(res) + assert.NotNil(t, res) + assert.Nil(t, err) + assert.Equal(t, resolution.StateDone, res.State) + + output := res.Steps["stepOne"].Output.(map[string]interface{}) + assert.Equal(t, "utask", output["a"]) + assert.Equal(t, "666", output["b"]) + assert.Equal(t, "map[k:v]", output["c"]) + assert.Equal(t, "[1 2 3]", output["d"]) +} + func TestRetryLoop(t *testing.T) { res, err := createResolution("retryloop.yaml", nil, nil) assert.NotNil(t, res) diff --git a/engine/templates_tests/jsonParsing.yaml b/engine/templates_tests/jsonParsing.yaml new file mode 100644 index 00000000..0e6c77ad --- /dev/null +++ b/engine/templates_tests/jsonParsing.yaml @@ -0,0 +1,22 @@ +name: jsonParsing +description: Ensure that JSON can be parsed and used as value in template +title_format: "[test] correct json parsing" +auto_runnable: true + +variables: + - name: rawJSON + expression: > + var o = {"a":"utask","b":666,"c":{"k":"v"},"d":["1","2","3"]}; + JSON.stringify(o); + +steps: + stepOne: + description: first step + action: + type: echo + configuration: + output: + a: '{{(eval `rawJSON` | fromJson).a}}' + b: '{{(eval `rawJSON` | mustFromJson).b}}' + c: '{{(eval `rawJSON` | mustFromJson).c}}' + d: '{{(eval `rawJSON` | fromJson).d}}' diff --git a/engine/values/values.go b/engine/values/values.go index 06a1d43a..8e911433 100644 --- a/engine/values/values.go +++ b/engine/values/values.go @@ -2,6 +2,7 @@ package values import ( "bytes" + "encoding/json" "fmt" "reflect" "text/template" @@ -67,6 +68,9 @@ func NewValues() *Values { v.funcMap["field"] = v.fieldTmpl v.funcMap["eval"] = v.varEval v.funcMap["evalCache"] = v.varEvalCache + v.funcMap["fromJson"] = v.fromJSON + v.funcMap["mustFromJson"] = v.mustFromJSON + return v } @@ -374,6 +378,19 @@ func (v *Values) varEval(varName string) (interface{}, error) { return res.String(), nil } +// fromJSON decodes JSON into a structured value, ignoring errors. +func (v *Values) fromJSON(s string) (reflect.Value, error) { + output, _ := v.mustFromJSON(s) + return output, nil +} + +// mustFromJSON decodes JSON into a structured value, returning errors. +func (v *Values) mustFromJSON(s string) (reflect.Value, error) { + var output interface{} + err := json.Unmarshal([]byte(s), &output) + return reflect.ValueOf(output), err +} + var errTimedOut = errors.New("Timed out variable evaluation") func evalUnsafe(exp []byte, delay time.Duration) (v otto.Value, err error) {