]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
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 inputVariable, ok := input.(ast.Variable); ok { | |
51 | return inputVariable, nil | |
52 | } | |
53 | ||
54 | var stringVal string | |
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 | |
59 | } | |
60 | ||
61 | // Otherwise return the string value | |
62 | return ast.Variable{ | |
63 | Type: ast.TypeString, | |
64 | Value: stringVal, | |
65 | }, nil | |
66 | } | |
67 | ||
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) | |
73 | if err != nil { | |
74 | return ast.Variable{}, err | |
75 | } | |
76 | elements[i] = varElement | |
77 | } | |
78 | ||
79 | return ast.Variable{ | |
80 | Type: ast.TypeMap, | |
81 | Value: elements, | |
82 | }, nil | |
83 | } | |
84 | ||
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) | |
90 | if err != nil { | |
91 | return ast.Variable{}, err | |
92 | } | |
93 | elements[i] = varElement | |
94 | } | |
95 | ||
96 | return ast.Variable{ | |
97 | Type: ast.TypeList, | |
98 | Value: elements, | |
99 | }, nil | |
100 | } | |
101 | ||
102 | return ast.Variable{}, fmt.Errorf("value for conversion must be a string, interface{} or map[string]interface: got %T", input) | |
103 | } | |
104 | ||
105 | func VariableToInterface(input ast.Variable) (interface{}, error) { | |
106 | if input.Type == ast.TypeString { | |
107 | if inputStr, ok := input.Value.(string); ok { | |
108 | return inputStr, nil | |
109 | } else { | |
110 | return nil, fmt.Errorf("ast.Variable with type string has value which is not a string") | |
111 | } | |
112 | } | |
113 | ||
114 | if input.Type == ast.TypeList { | |
115 | inputList, ok := input.Value.([]ast.Variable) | |
116 | if !ok { | |
117 | return nil, fmt.Errorf("ast.Variable with type list has value which is not a []ast.Variable") | |
118 | } | |
119 | ||
120 | result := make([]interface{}, 0) | |
121 | if len(inputList) == 0 { | |
122 | return result, nil | |
123 | } | |
124 | ||
125 | for _, element := range inputList { | |
126 | if convertedElement, err := VariableToInterface(element); err == nil { | |
127 | result = append(result, convertedElement) | |
128 | } else { | |
129 | return nil, err | |
130 | } | |
131 | } | |
132 | ||
133 | return result, nil | |
134 | } | |
135 | ||
136 | if input.Type == ast.TypeMap { | |
137 | inputMap, ok := input.Value.(map[string]ast.Variable) | |
138 | if !ok { | |
139 | return nil, fmt.Errorf("ast.Variable with type map has value which is not a map[string]ast.Variable") | |
140 | } | |
141 | ||
142 | result := make(map[string]interface{}, 0) | |
143 | if len(inputMap) == 0 { | |
144 | return result, nil | |
145 | } | |
146 | ||
147 | for key, value := range inputMap { | |
148 | if convertedValue, err := VariableToInterface(value); err == nil { | |
149 | result[key] = convertedValue | |
150 | } else { | |
151 | return nil, err | |
152 | } | |
153 | } | |
154 | ||
155 | return result, nil | |
156 | } | |
157 | ||
158 | return nil, fmt.Errorf("unknown input type: %s", input.Type) | |
159 | } |