From 0e34b8f9137be3549a16dc1f3ed60baa529456de Mon Sep 17 00:00:00 2001 From: Cameron Stitt Date: Tue, 19 Dec 2017 09:29:37 +1000 Subject: [PATCH] Add json encoding interpolation --- docs/interpolation/index.rst | 2 ++ examples/input.json | 15 ++++---- examples/vars.hcl | 10 ++++++ interpolation/encoding.go | 64 ++++++++++++++++++++++++++++++++++ interpolation/encoding_test.go | 37 ++++++++++++++++++++ interpolation/funcs.go | 31 ++++++++-------- 6 files changed, 138 insertions(+), 21 deletions(-) diff --git a/docs/interpolation/index.rst b/docs/interpolation/index.rst index 87ef3fb..da2177d 100644 --- a/docs/interpolation/index.rst +++ b/docs/interpolation/index.rst @@ -7,4 +7,6 @@ Interpolation explanation. :maxdepth: 1 :caption: Contents: + common + maps strings diff --git a/examples/input.json b/examples/input.json index ff8419f..21a3329 100644 --- a/examples/input.json +++ b/examples/input.json @@ -1,7 +1,10 @@ { - "i": ${i}, - "j": "${join(", ", h)}", - "other": "${upper("foobar")}", - "foo": "${join(" | ", list("foo", "bar"))}", - "len": ${length(h)} -} \ No newline at end of file + "i": ${i}, + "j": "${join(", ", h)}", + "other": "${upper("foobar")}", + "foo": "${join(" | ", list("foo", "bar"))}", + "len": ${length(h)}, + "list": ${jsonencode(foo)}, + "map": ${jsonencode(bar)}, + "string": ${jsonencode(bar["this"])} +} diff --git a/examples/vars.hcl b/examples/vars.hcl index c0490d3..7b19b3d 100644 --- a/examples/vars.hcl +++ b/examples/vars.hcl @@ -4,4 +4,14 @@ variable "i" { variable "j" { value = 100 +} + +variable "foo" { + value = ["this", "that"] +} + +variable "bar" { + value = { + "this" = "that" + } } \ No newline at end of file diff --git a/interpolation/encoding.go b/interpolation/encoding.go index 1f3fcaf..6d1e8cf 100644 --- a/interpolation/encoding.go +++ b/interpolation/encoding.go @@ -2,6 +2,10 @@ package interpolation import ( "encoding/base64" + "encoding/json" + "fmt" + + "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" ) @@ -31,3 +35,63 @@ func interpolationFuncBase64Decode() ast.Function { }, } } + +// interpolationFuncJSONEncode will encode an arbitrary into its JSON representation +func interpolationFuncJSONEncode() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeAny}, + ReturnType: ast.TypeString, + Callback: func(inputs []interface{}) (interface{}, error) { + input := inputs[0] + + var encodeVal interface{} + + switch in := input.(type) { + case []ast.Variable: + //convert to slice + inStrings := make([]string, len(in)) + + for i, v := range in { + if v.Type != ast.TypeString { + variable, _ := hil.InterfaceToVariable(in) + encodeVal, _ = hil.VariableToInterface(variable) + + jEnc, err := json.Marshal(encodeVal) + if err != nil { + return "", fmt.Errorf("failed to encode JSON data '%s'", encodeVal) + } + return string(jEnc), nil + } + inStrings[i] = v.Value.(string) + } + encodeVal = inStrings + case map[string]ast.Variable: + //convert to map + mapStrings := make(map[string]string) + for k, v := range in { + if v.Type != ast.TypeString { + variable, _ := hil.InterfaceToVariable(in) + encodeVal, _ = hil.VariableToInterface(variable) + + jEnc, err := json.Marshal(encodeVal) + if err != nil { + return "", fmt.Errorf("failed to encode JSON data '%s'", encodeVal) + } + return string(jEnc), nil + } + mapStrings[k] = v.Value.(string) + } + encodeVal = mapStrings + case string: + encodeVal = in + default: + return nil, fmt.Errorf("unknown type for JSON encoding: %T", input) + } + b, err := json.Marshal(encodeVal) + if err != nil { + return nil, err + } + return string(b), nil + }, + } +} diff --git a/interpolation/encoding_test.go b/interpolation/encoding_test.go index a33c21c..5f9b9e8 100644 --- a/interpolation/encoding_test.go +++ b/interpolation/encoding_test.go @@ -37,3 +37,40 @@ func TestInterpolationFuncBase64Decode(t *testing.T) { }) } } + +func TestInterpolationFuncJSONEncode(t *testing.T) { + testCases := []functionTestCase{ + { + description: "String encoding", + text: `${jsonencode("foo")}`, + expectation: `"foo"`, + }, + { + description: "List encoding", + text: `${jsonencode(list("foo", "bar"))}`, + expectation: `["foo","bar"]`, + }, + { + description: "Map encoding", + text: `${jsonencode(map("foo", "bar"))}`, + expectation: `{"foo":"bar"}`, + }, + { + description: "Nested encoding", + text: `${jsonencode(map("foo", list("this", "that")))}`, + expectation: `{"foo":["this","that"]}`, + }, + } + + jsonencodeFunc := testInterpolationFunc(keyFuncs{ + "jsonencode": interpolationFuncJSONEncode, + "list": interpolationFuncList, + "map": interpolationFuncMap, + }) + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + jsonencodeFunc(t, tc) + }) + } +} diff --git a/interpolation/funcs.go b/interpolation/funcs.go index de97910..573823d 100644 --- a/interpolation/funcs.go +++ b/interpolation/funcs.go @@ -10,21 +10,22 @@ import ( // CoreFunctions are the custom functions for interpolation var CoreFunctions = map[string]ast.Function{ - "lower": interpolationFuncLower(), - "upper": interpolationFuncUpper(), - "env": interpolationFuncEnv(), - "join": interpolationFuncJoin(), - "has": interpolationFuncHas(), - "map": interpolationFuncMap(), - "keys": interpolationFuncKeys(), - "list": interpolationFuncList(), - "concat": interpolationFuncConcat(), - "replace": interpolationFuncReplace(), - "max": interpolationFuncMax(), - "min": interpolationFuncMin(), - "contains": interpolationFuncContains(), - "split": interpolationFuncSplit(), - "length": interpolationFuncLength(), + "lower": interpolationFuncLower(), + "upper": interpolationFuncUpper(), + "env": interpolationFuncEnv(), + "join": interpolationFuncJoin(), + "has": interpolationFuncHas(), + "map": interpolationFuncMap(), + "keys": interpolationFuncKeys(), + "list": interpolationFuncList(), + "concat": interpolationFuncConcat(), + "replace": interpolationFuncReplace(), + "max": interpolationFuncMax(), + "min": interpolationFuncMin(), + "contains": interpolationFuncContains(), + "split": interpolationFuncSplit(), + "length": interpolationFuncLength(), + "jsonencode": interpolationFuncJSONEncode(), } // interpolationFuncEnv will extract a variable out of the env