]>
Commit | Line | Data |
---|---|---|
1 | package hil | |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "reflect" | |
6 | ||
7 | "github.com/hashicorp/hil/ast" | |
8 | "github.com/mitchellh/mapstructure" | |
9 | ) | |
10 | ||
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" | |
15 | ||
16 | var hilMapstructureDecodeHookSlice []interface{} | |
17 | var hilMapstructureDecodeHookStringSlice []string | |
18 | var hilMapstructureDecodeHookMap map[string]interface{} | |
19 | ||
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) | |
30 | ||
31 | if (source == sliceType || source == stringSliceType) && target == mapType { | |
32 | return nil, fmt.Errorf("Cannot convert %s into a %s", source, target) | |
33 | } | |
34 | ||
35 | return val, nil | |
36 | }, | |
37 | WeaklyTypedInput: true, | |
38 | Result: rawVal, | |
39 | } | |
40 | ||
41 | decoder, err := mapstructure.NewDecoder(config) | |
42 | if err != nil { | |
43 | return err | |
44 | } | |
45 | ||
46 | return decoder.Decode(m) | |
47 | } | |
48 | ||
49 | func InterfaceToVariable(input interface{}) (ast.Variable, error) { | |
50 | if iv, ok := input.(ast.Variable); ok { | |
51 | return iv, nil | |
52 | } | |
53 | ||
54 | // This is just to maintain backward compatibility | |
55 | // after https://github.com/mitchellh/mapstructure/pull/98 | |
56 | if v, ok := input.([]ast.Variable); ok { | |
57 | return ast.Variable{ | |
58 | Type: ast.TypeList, | |
59 | Value: v, | |
60 | }, nil | |
61 | } | |
62 | if v, ok := input.(map[string]ast.Variable); ok { | |
63 | return ast.Variable{ | |
64 | Type: ast.TypeMap, | |
65 | Value: v, | |
66 | }, nil | |
67 | } | |
68 | ||
69 | var stringVal string | |
70 | if err := hilMapstructureWeakDecode(input, &stringVal); err == nil { | |
71 | // Special case the unknown value to turn into "unknown" | |
72 | if stringVal == UnknownValue { | |
73 | return ast.Variable{Value: UnknownValue, Type: ast.TypeUnknown}, nil | |
74 | } | |
75 | ||
76 | // Otherwise return the string value | |
77 | return ast.Variable{ | |
78 | Type: ast.TypeString, | |
79 | Value: stringVal, | |
80 | }, nil | |
81 | } | |
82 | ||
83 | var mapVal map[string]interface{} | |
84 | if err := hilMapstructureWeakDecode(input, &mapVal); err == nil { | |
85 | elements := make(map[string]ast.Variable) | |
86 | for i, element := range mapVal { | |
87 | varElement, err := InterfaceToVariable(element) | |
88 | if err != nil { | |
89 | return ast.Variable{}, err | |
90 | } | |
91 | elements[i] = varElement | |
92 | } | |
93 | ||
94 | return ast.Variable{ | |
95 | Type: ast.TypeMap, | |
96 | Value: elements, | |
97 | }, nil | |
98 | } | |
99 | ||
100 | var sliceVal []interface{} | |
101 | if err := hilMapstructureWeakDecode(input, &sliceVal); err == nil { | |
102 | elements := make([]ast.Variable, len(sliceVal)) | |
103 | for i, element := range sliceVal { | |
104 | varElement, err := InterfaceToVariable(element) | |
105 | if err != nil { | |
106 | return ast.Variable{}, err | |
107 | } | |
108 | elements[i] = varElement | |
109 | } | |
110 | ||
111 | return ast.Variable{ | |
112 | Type: ast.TypeList, | |
113 | Value: elements, | |
114 | }, nil | |
115 | } | |
116 | ||
117 | return ast.Variable{}, fmt.Errorf("value for conversion must be a string, interface{} or map[string]interface: got %T", input) | |
118 | } | |
119 | ||
120 | func VariableToInterface(input ast.Variable) (interface{}, error) { | |
121 | if input.Type == ast.TypeString { | |
122 | if inputStr, ok := input.Value.(string); ok { | |
123 | return inputStr, nil | |
124 | } else { | |
125 | return nil, fmt.Errorf("ast.Variable with type string has value which is not a string") | |
126 | } | |
127 | } | |
128 | ||
129 | if input.Type == ast.TypeList { | |
130 | inputList, ok := input.Value.([]ast.Variable) | |
131 | if !ok { | |
132 | return nil, fmt.Errorf("ast.Variable with type list has value which is not a []ast.Variable") | |
133 | } | |
134 | ||
135 | result := make([]interface{}, 0) | |
136 | if len(inputList) == 0 { | |
137 | return result, nil | |
138 | } | |
139 | ||
140 | for _, element := range inputList { | |
141 | if convertedElement, err := VariableToInterface(element); err == nil { | |
142 | result = append(result, convertedElement) | |
143 | } else { | |
144 | return nil, err | |
145 | } | |
146 | } | |
147 | ||
148 | return result, nil | |
149 | } | |
150 | ||
151 | if input.Type == ast.TypeMap { | |
152 | inputMap, ok := input.Value.(map[string]ast.Variable) | |
153 | if !ok { | |
154 | return nil, fmt.Errorf("ast.Variable with type map has value which is not a map[string]ast.Variable") | |
155 | } | |
156 | ||
157 | result := make(map[string]interface{}, 0) | |
158 | if len(inputMap) == 0 { | |
159 | return result, nil | |
160 | } | |
161 | ||
162 | for key, value := range inputMap { | |
163 | if convertedValue, err := VariableToInterface(value); err == nil { | |
164 | result[key] = convertedValue | |
165 | } else { | |
166 | return nil, err | |
167 | } | |
168 | } | |
169 | ||
170 | return result, nil | |
171 | } | |
172 | ||
173 | return nil, fmt.Errorf("unknown input type: %s", input.Type) | |
174 | } |