Skip to content

Commit

Permalink
feat: add fromJson and mustFromJson to values func map (#164)
Browse files Browse the repository at this point in the history
* feat: add fromJson and mustFromJson to values func map

Signed-off-by: William Poussier <william.poussier@gmail.com>

* fix: tests

Signed-off-by: William Poussier <william.poussier@gmail.com>

* fix: return a reflect value

Signed-off-by: William Poussier <william.poussier@gmail.com>

* doc: add better example of fromJSON template func

Signed-off-by: William Poussier <william.poussier@gmail.com>
  • Loading branch information
wI2L authored Aug 25, 2020
1 parent c67c916 commit efe2182
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 7 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions engine/templates_tests/jsonParsing.yaml
Original file line number Diff line number Diff line change
@@ -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}}'
17 changes: 17 additions & 0 deletions engine/values/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package values

import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"text/template"
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit efe2182

Please sign in to comment.