160 lines
4.3 KiB
Go
160 lines
4.3 KiB
Go
package hil
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// UnknownValue is a sentinel value that can be used to denote
|
|
// that a value of a variable (or map element, list element, etc.)
|
|
// is unknown. This will always have the type ast.TypeUnknown.
|
|
const UnknownValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
|
|
|
|
var hilMapstructureDecodeHookSlice []interface{}
|
|
var hilMapstructureDecodeHookStringSlice []string
|
|
var hilMapstructureDecodeHookMap map[string]interface{}
|
|
|
|
// hilMapstructureWeakDecode behaves in the same way as mapstructure.WeakDecode
|
|
// but has a DecodeHook which defeats the backward compatibility mode of mapstructure
|
|
// which WeakDecodes []interface{}{} into an empty map[string]interface{}. This
|
|
// allows us to use WeakDecode (desirable), but not fail on empty lists.
|
|
func hilMapstructureWeakDecode(m interface{}, rawVal interface{}) error {
|
|
config := &mapstructure.DecoderConfig{
|
|
DecodeHook: func(source reflect.Type, target reflect.Type, val interface{}) (interface{}, error) {
|
|
sliceType := reflect.TypeOf(hilMapstructureDecodeHookSlice)
|
|
stringSliceType := reflect.TypeOf(hilMapstructureDecodeHookStringSlice)
|
|
mapType := reflect.TypeOf(hilMapstructureDecodeHookMap)
|
|
|
|
if (source == sliceType || source == stringSliceType) && target == mapType {
|
|
return nil, fmt.Errorf("Cannot convert %s into a %s", source, target)
|
|
}
|
|
|
|
return val, nil
|
|
},
|
|
WeaklyTypedInput: true,
|
|
Result: rawVal,
|
|
}
|
|
|
|
decoder, err := mapstructure.NewDecoder(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return decoder.Decode(m)
|
|
}
|
|
|
|
func InterfaceToVariable(input interface{}) (ast.Variable, error) {
|
|
if inputVariable, ok := input.(ast.Variable); ok {
|
|
return inputVariable, nil
|
|
}
|
|
|
|
var stringVal string
|
|
if err := hilMapstructureWeakDecode(input, &stringVal); err == nil {
|
|
// Special case the unknown value to turn into "unknown"
|
|
if stringVal == UnknownValue {
|
|
return ast.Variable{Value: UnknownValue, Type: ast.TypeUnknown}, nil
|
|
}
|
|
|
|
// Otherwise return the string value
|
|
return ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: stringVal,
|
|
}, nil
|
|
}
|
|
|
|
var mapVal map[string]interface{}
|
|
if err := hilMapstructureWeakDecode(input, &mapVal); err == nil {
|
|
elements := make(map[string]ast.Variable)
|
|
for i, element := range mapVal {
|
|
varElement, err := InterfaceToVariable(element)
|
|
if err != nil {
|
|
return ast.Variable{}, err
|
|
}
|
|
elements[i] = varElement
|
|
}
|
|
|
|
return ast.Variable{
|
|
Type: ast.TypeMap,
|
|
Value: elements,
|
|
}, nil
|
|
}
|
|
|
|
var sliceVal []interface{}
|
|
if err := hilMapstructureWeakDecode(input, &sliceVal); err == nil {
|
|
elements := make([]ast.Variable, len(sliceVal))
|
|
for i, element := range sliceVal {
|
|
varElement, err := InterfaceToVariable(element)
|
|
if err != nil {
|
|
return ast.Variable{}, err
|
|
}
|
|
elements[i] = varElement
|
|
}
|
|
|
|
return ast.Variable{
|
|
Type: ast.TypeList,
|
|
Value: elements,
|
|
}, nil
|
|
}
|
|
|
|
return ast.Variable{}, fmt.Errorf("value for conversion must be a string, interface{} or map[string]interface: got %T", input)
|
|
}
|
|
|
|
func VariableToInterface(input ast.Variable) (interface{}, error) {
|
|
if input.Type == ast.TypeString {
|
|
if inputStr, ok := input.Value.(string); ok {
|
|
return inputStr, nil
|
|
} else {
|
|
return nil, fmt.Errorf("ast.Variable with type string has value which is not a string")
|
|
}
|
|
}
|
|
|
|
if input.Type == ast.TypeList {
|
|
inputList, ok := input.Value.([]ast.Variable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("ast.Variable with type list has value which is not a []ast.Variable")
|
|
}
|
|
|
|
result := make([]interface{}, 0)
|
|
if len(inputList) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
for _, element := range inputList {
|
|
if convertedElement, err := VariableToInterface(element); err == nil {
|
|
result = append(result, convertedElement)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
if input.Type == ast.TypeMap {
|
|
inputMap, ok := input.Value.(map[string]ast.Variable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("ast.Variable with type map has value which is not a map[string]ast.Variable")
|
|
}
|
|
|
|
result := make(map[string]interface{}, 0)
|
|
if len(inputMap) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
for key, value := range inputMap {
|
|
if convertedValue, err := VariableToInterface(value); err == nil {
|
|
result[key] = convertedValue
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown input type: %s", input.Type)
|
|
}
|