7 "github.com/hashicorp/hil/ast"
8 "github.com/mitchellh/mapstructure"
11 // UnknownValue is a sentinel value that can be used to denote
12 // that a value of a variable (or map element, list element, etc.)
13 // is unknown. This will always have the type ast.TypeUnknown.
14 const UnknownValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
16 var hilMapstructureDecodeHookSlice []interface{}
17 var hilMapstructureDecodeHookStringSlice []string
18 var hilMapstructureDecodeHookMap map[string]interface{}
20 // hilMapstructureWeakDecode behaves in the same way as mapstructure.WeakDecode
21 // but has a DecodeHook which defeats the backward compatibility mode of mapstructure
22 // which WeakDecodes []interface{}{} into an empty map[string]interface{}. This
23 // allows us to use WeakDecode (desirable), but not fail on empty lists.
24 func hilMapstructureWeakDecode(m interface{}, rawVal interface{}) error {
25 config := &mapstructure.DecoderConfig{
26 DecodeHook: func(source reflect.Type, target reflect.Type, val interface{}) (interface{}, error) {
27 sliceType := reflect.TypeOf(hilMapstructureDecodeHookSlice)
28 stringSliceType := reflect.TypeOf(hilMapstructureDecodeHookStringSlice)
29 mapType := reflect.TypeOf(hilMapstructureDecodeHookMap)
31 if (source == sliceType || source == stringSliceType) && target == mapType {
32 return nil, fmt.Errorf("Cannot convert %s into a %s", source, target)
37 WeaklyTypedInput: true,
41 decoder, err := mapstructure.NewDecoder(config)
46 return decoder.Decode(m)
49 func InterfaceToVariable(input interface{}) (ast.Variable, error) {
50 if inputVariable, ok := input.(ast.Variable); ok {
51 return inputVariable, nil
55 if err := hilMapstructureWeakDecode(input, &stringVal); err == nil {
56 // Special case the unknown value to turn into "unknown"
57 if stringVal == UnknownValue {
58 return ast.Variable{Value: UnknownValue, Type: ast.TypeUnknown}, nil
61 // Otherwise return the string value
68 var mapVal map[string]interface{}
69 if err := hilMapstructureWeakDecode(input, &mapVal); err == nil {
70 elements := make(map[string]ast.Variable)
71 for i, element := range mapVal {
72 varElement, err := InterfaceToVariable(element)
74 return ast.Variable{}, err
76 elements[i] = varElement
85 var sliceVal []interface{}
86 if err := hilMapstructureWeakDecode(input, &sliceVal); err == nil {
87 elements := make([]ast.Variable, len(sliceVal))
88 for i, element := range sliceVal {
89 varElement, err := InterfaceToVariable(element)
91 return ast.Variable{}, err
93 elements[i] = varElement
102 return ast.Variable{}, fmt.Errorf("value for conversion must be a string, interface{} or map[string]interface: got %T", input)
105 func VariableToInterface(input ast.Variable) (interface{}, error) {
106 if input.Type == ast.TypeString {
107 if inputStr, ok := input.Value.(string); ok {
110 return nil, fmt.Errorf("ast.Variable with type string has value which is not a string")
114 if input.Type == ast.TypeList {
115 inputList, ok := input.Value.([]ast.Variable)
117 return nil, fmt.Errorf("ast.Variable with type list has value which is not a []ast.Variable")
120 result := make([]interface{}, 0)
121 if len(inputList) == 0 {
125 for _, element := range inputList {
126 if convertedElement, err := VariableToInterface(element); err == nil {
127 result = append(result, convertedElement)
136 if input.Type == ast.TypeMap {
137 inputMap, ok := input.Value.(map[string]ast.Variable)
139 return nil, fmt.Errorf("ast.Variable with type map has value which is not a map[string]ast.Variable")
142 result := make(map[string]interface{}, 0)
143 if len(inputMap) == 0 {
147 for key, value := range inputMap {
148 if convertedValue, err := VariableToInterface(value); err == nil {
149 result[key] = convertedValue
158 return nil, fmt.Errorf("unknown input type: %s", input.Type)