config: support lists and maps in jsonencode

For now we only support lists and maps whose values are strings, not
deeply nested data.
This commit is contained in:
David Glasser 2016-05-18 10:45:22 -07:00
parent eedc523281
commit 594ea105d8
3 changed files with 93 additions and 7 deletions

View File

@ -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
},

View File

@ -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{
{
@ -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,
},
},
})
}

View File

@ -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.