Skip to content

Commit

Permalink
config: support lists and maps in jsonencode
Browse files Browse the repository at this point in the history
For now we only support lists and maps whose values are strings, not
deeply nested data.
  • Loading branch information
glasser committed May 18, 2016
1 parent eedc523 commit 594ea10
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 7 deletions.
48 changes: 43 additions & 5 deletions config/interpolate_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,16 +409,54 @@ func interpolationFuncJoin() ast.Function {
}

// interpolationFuncJSONEncode implements the "jsonencode" function that encodes
// a string as its JSON representation.
// a string, list, or map as its JSON representation. For now, values in the
// list or map may only be strings.
func interpolationFuncJSONEncode() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ArgTypes: []ast.Type{ast.TypeAny},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
s := args[0].(string)
jEnc, err := json.Marshal(s)
var toEncode interface{}

switch typedArg := args[0].(type) {
case string:
toEncode = typedArg

case []ast.Variable:
// We preallocate the list here. Note that it's important that in
// the length 0 case, we have an empty list rather than nil, as
// they encode differently.
// XXX It would be nice to support arbitrarily nested data here. Is
// there an inverse of hil.InterfaceToVariable?
strings := make([]string, len(typedArg))

for i, v := range typedArg {
if v.Type != ast.TypeString {
return "", fmt.Errorf("list elements must be strings")
}
strings[i] = v.Value.(string)
}
toEncode = strings

case map[string]ast.Variable:
// XXX It would be nice to support arbitrarily nested data here. Is
// there an inverse of hil.InterfaceToVariable?
stringMap := make(map[string]string)
for k, v := range typedArg {
if v.Type != ast.TypeString {
return "", fmt.Errorf("map values must be strings")
}
stringMap[k] = v.Value.(string)
}
toEncode = stringMap

default:
return "", fmt.Errorf("unknown type for JSON encoding: %T", args[0])
}

jEnc, err := json.Marshal(toEncode)
if err != nil {
return "", fmt.Errorf("failed to encode JSON data '%s'", s)
return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode)
}
return string(jEnc), nil
},
Expand Down
46 changes: 46 additions & 0 deletions config/interpolate_funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,22 @@ func TestInterpolateFuncJSONEncode(t *testing.T) {
Value: " foo \\ \n \t \" bar ",
Type: ast.TypeString,
},
"list": interfaceToVariableSwallowError([]string{"foo", "bar\tbaz"}),
// XXX can't use InterfaceToVariable as it converts empty slice into empty
// map.
"emptylist": ast.Variable{
Value: []ast.Variable{},
Type: ast.TypeList,
},
"map": interfaceToVariableSwallowError(map[string]string{
"foo": "bar",
"ba \n z": "q\\x",
}),
"emptymap": interfaceToVariableSwallowError(map[string]string{}),

// Not yet supported (but it would be nice)
"nestedlist": interfaceToVariableSwallowError([][]string{{"foo"}}),
"nestedmap": interfaceToVariableSwallowError(map[string][]string{"foo": {"bar"}}),
},
Cases: []testFunctionCase{
{
Expand Down Expand Up @@ -446,6 +462,36 @@ func TestInterpolateFuncJSONEncode(t *testing.T) {
nil,
true,
},
{
`${jsonencode(list)}`,
`["foo","bar\tbaz"]`,
false,
},
{
`${jsonencode(emptylist)}`,
`[]`,
false,
},
{
`${jsonencode(map)}`,
`{"ba \n z":"q\\x","foo":"bar"}`,
false,
},
{
`${jsonencode(emptymap)}`,
`{}`,
false,
},
{
`${jsonencode(nestedlist)}`,
nil,
true,
},
{
`${jsonencode(nestedmap)}`,
nil,
true,
},
},
})
}
Expand Down
6 changes: 4 additions & 2 deletions website/source/docs/configuration/interpolation.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,10 @@ The supported built-in functions are:
only possible with splat variables from resources with a count
greater than one. Example: `join(",", aws_instance.foo.*.id)`

* `jsonencode(string)` - Returns a JSON-encoded representation of the given
string (including double quotes).
* `jsonencode(item)` - Returns a JSON-encoded representation of the given
item, which may be a string, list of strings, or map from string to string.
Note that if the item is a string, the return value includes the double
quotes.

* `length(list)` - Returns a number of members in a given list
or a number of characters in a given string.
Expand Down

0 comments on commit 594ea10

Please sign in to comment.