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) }