Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: support lists and maps in jsonencode #6749

Merged
merged 1 commit into from
May 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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