Skip to content

Commit

Permalink
helper/schema: Decode map values per Elem for ValidateFunc
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Mar 13, 2018
1 parent 9cef467 commit c54c90d
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 12 deletions.
31 changes: 19 additions & 12 deletions helper/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -1374,14 +1374,19 @@ func (m schemaMap) validateMap(
return nil, []error{fmt.Errorf("%s: should be a map", k)}
}

var validatableMap map[string]interface{}

// If it is not a slice, validate directly
if rawV.Kind() != reflect.Slice {
mapIface := rawV.Interface()
if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 {

var errs []error
validatableMap, errs = decodeMapValues(k, mapIface.(map[string]interface{}), schema)
if len(errs) > 0 {
return nil, errs
}
if schema.ValidateFunc != nil {
return schema.ValidateFunc(mapIface, k)
return schema.ValidateFunc(validatableMap, k)
}
return nil, nil
}
Expand All @@ -1399,26 +1404,24 @@ func (m schemaMap) validateMap(
"%s: should be a map", k)}
}
mapIface := v.Interface()
if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 {

var errs []error
validatableMap, errs = decodeMapValues(k, mapIface.(map[string]interface{}), schema)
if len(errs) > 0 {
return nil, errs
}
}

if schema.ValidateFunc != nil {
validatableMap := make(map[string]interface{})
for _, raw := range raws {
for k, v := range raw.(map[string]interface{}) {
validatableMap[k] = v
}
}

return schema.ValidateFunc(validatableMap, k)
}

return nil, nil
}

func validateMapValues(k string, m map[string]interface{}, schema *Schema) ([]string, []error) {
func decodeMapValues(k string, m map[string]interface{}, schema *Schema) (map[string]interface{}, []error) {
decodedMap := make(map[string]interface{}, len(m))

for key, raw := range m {
valueType, err := getValueType(k, schema)
if err != nil {
Expand All @@ -1431,26 +1434,30 @@ func validateMapValues(k string, m map[string]interface{}, schema *Schema) ([]st
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
decodedMap[key] = n
case TypeInt:
var n int
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
decodedMap[key] = n
case TypeFloat:
var n float64
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
decodedMap[key] = n
case TypeString:
var n string
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
decodedMap[key] = n
default:
panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type))
}
}
return nil, nil
return decodedMap, nil
}

func getValueType(k string, schema *Schema) (ValueType, error) {
Expand Down
82 changes: 82 additions & 0 deletions helper/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4966,6 +4966,88 @@ func TestSchemaMap_Validate(t *testing.T) {
Err: false,
},

"ValidateFunc on TypeMap receives properly casted value type per Elem": {
Schema: map[string]*Schema{
"validate_me": &Schema{
Type: TypeMap,
Required: true,
Elem: TypeString,
ValidateFunc: func(value interface{}, k string) (ws []string, es []error) {
m := value.(map[string]interface{})
for k, v := range m {
_, isString := v.(string)
if !isString {
es = append(es, fmt.Errorf("Expected string for %q, given: %#v", k, v))
}
}
return
},
},
},
Config: map[string]interface{}{
"validate_me": map[string]interface{}{
"one": "one",
"bool": true,
"integer": 12,
},
},

Err: false,
},

"ValidateFunc on TypeMap receives properly casted value type per Elem #2 (no Elem, defaults to string)": {
Schema: map[string]*Schema{
"validate_me": &Schema{
Type: TypeMap,
Required: true,
ValidateFunc: func(value interface{}, k string) (ws []string, es []error) {
m := value.(map[string]interface{})
for k, v := range m {
_, isString := v.(string)
if !isString {
es = append(es, fmt.Errorf("Expected string for %q, given: %#v", k, v))
}
}
return
},
},
},
Config: map[string]interface{}{
"validate_me": map[string]interface{}{
"one": "one",
"bool": true,
"integer": 12,
},
},

Err: false,
},

"ValidateFunc on TypeMap receives properly casted value type per Elem #3": {
Schema: map[string]*Schema{
"string_to_bool": &Schema{
Type: TypeMap,
Required: true,
Elem: TypeBool,
ValidateFunc: func(value interface{}, k string) (ws []string, es []error) {
m := value.(map[string]interface{})
v := m["b"].(bool)
if !v {
es = append(es, fmt.Errorf("Expected %q to be %t", "b", true))
}
return
},
},
},
Config: map[string]interface{}{
"string_to_bool": map[string]interface{}{
"b": "foo",
},
},

Err: true,
},

"special timeouts field": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Expand Down

0 comments on commit c54c90d

Please sign in to comment.