Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

schema: Enable map value validation #12638

Merged
merged 1 commit into from
Mar 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 70 additions & 5 deletions helper/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -1262,8 +1262,15 @@ func (m schemaMap) validateMap(
return nil, []error{fmt.Errorf("%s: should be a map", k)}
}

// If it is not a slice, it is valid
// 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 {
return nil, errs
}
if schema.ValidateFunc != nil {
return schema.ValidateFunc(mapIface, k)
}
return nil, nil
}

Expand All @@ -1279,6 +1286,10 @@ func (m schemaMap) validateMap(
return nil, []error{fmt.Errorf(
"%s: should be a map", k)}
}
mapIface := v.Interface()
if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 {
return nil, errs
}
}

if schema.ValidateFunc != nil {
Expand All @@ -1295,6 +1306,60 @@ func (m schemaMap) validateMap(
return nil, nil
}

func validateMapValues(k string, m map[string]interface{}, schema *Schema) ([]string, []error) {
for key, raw := range m {
valueType, err := getValueType(k, schema)
if err != nil {
return nil, []error{err}
}

switch valueType {
case TypeBool:
var n bool
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
case TypeInt:
var n int
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
case TypeFloat:
var n float64
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
case TypeString:
var n string
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
}
default:
panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type))
}
}
return nil, nil
}

func getValueType(k string, schema *Schema) (ValueType, error) {
if schema.Elem == nil {
return TypeString, nil
}
if vt, ok := schema.Elem.(ValueType); ok {
return vt, nil
}

if s, ok := schema.Elem.(*Schema); ok {
if s.Elem == nil {
return TypeString, nil
}
if vt, ok := s.Elem.(ValueType); ok {
return vt, nil
}
}
return 0, fmt.Errorf("%s: unexpected map value type: %#v", k, schema.Elem)
}

func (m schemaMap) validateObject(
k string,
schema map[string]*Schema,
Expand Down Expand Up @@ -1372,28 +1437,28 @@ func (m schemaMap) validatePrimitive(
// Verify that we can parse this as the correct type
var n bool
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{err}
return nil, []error{fmt.Errorf("%s: %s", k, err)}
}
decoded = n
case TypeInt:
// Verify that we can parse this as an int
var n int
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{err}
return nil, []error{fmt.Errorf("%s: %s", k, err)}
}
decoded = n
case TypeFloat:
// Verify that we can parse this as an int
var n float64
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{err}
return nil, []error{fmt.Errorf("%s: %s", k, err)}
}
decoded = n
case TypeString:
// Verify that we can parse this as a string
var n string
if err := mapstructure.WeakDecode(raw, &n); err != nil {
return nil, []error{err}
return nil, []error{fmt.Errorf("%s: %s", k, err)}
}
decoded = n
default:
Expand Down
187 changes: 157 additions & 30 deletions helper/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4790,49 +4790,176 @@ func TestSchemaMap_Validate(t *testing.T) {

Err: false,
},

"invalid bool field": {
Schema: map[string]*Schema{
"bool_field": {
Type: TypeBool,
Optional: true,
},
},
Config: map[string]interface{}{
"bool_field": "abcdef",
},
Err: true,
},
"invalid integer field": {
Schema: map[string]*Schema{
"integer_field": {
Type: TypeInt,
Optional: true,
},
},
Config: map[string]interface{}{
"integer_field": "abcdef",
},
Err: true,
},
"invalid float field": {
Schema: map[string]*Schema{
"float_field": {
Type: TypeFloat,
Optional: true,
},
},
Config: map[string]interface{}{
"float_field": "abcdef",
},
Err: true,
},

// Invalid map values
"invalid bool map value": {
Schema: map[string]*Schema{
"boolMap": &Schema{
Type: TypeMap,
Elem: TypeBool,
Optional: true,
},
},
Config: map[string]interface{}{
"boolMap": map[string]interface{}{
"boolField": "notbool",
},
},
Err: true,
},
"invalid int map value": {
Schema: map[string]*Schema{
"intMap": &Schema{
Type: TypeMap,
Elem: TypeInt,
Optional: true,
},
},
Config: map[string]interface{}{
"intMap": map[string]interface{}{
"intField": "notInt",
},
},
Err: true,
},
"invalid float map value": {
Schema: map[string]*Schema{
"floatMap": &Schema{
Type: TypeMap,
Elem: TypeFloat,
Optional: true,
},
},
Config: map[string]interface{}{
"floatMap": map[string]interface{}{
"floatField": "notFloat",
},
},
Err: true,
},

"map with positive validate function": {
Schema: map[string]*Schema{
"floatInt": &Schema{
Type: TypeMap,
Elem: TypeInt,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
return
},
},
},
Config: map[string]interface{}{
"floatInt": map[string]interface{}{
"rightAnswer": "42",
"tooMuch": "43",
},
},
Err: false,
},
"map with negative validate function": {
Schema: map[string]*Schema{
"floatInt": &Schema{
Type: TypeMap,
Elem: TypeInt,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
es = append(es, fmt.Errorf("this is not fine"))
return
},
},
},
Config: map[string]interface{}{
"floatInt": map[string]interface{}{
"rightAnswer": "42",
"tooMuch": "43",
},
},
Err: true,
},
}

for tn, tc := range cases {
c, err := config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
}
if tc.Vars != nil {
vars := make(map[string]ast.Variable)
for k, v := range tc.Vars {
vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
}

if err := c.Interpolate(vars); err != nil {
t.Run(tn, func(t *testing.T) {
c, err := config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
if tc.Vars != nil {
vars := make(map[string]ast.Variable)
for k, v := range tc.Vars {
vars[k] = ast.Variable{Value: v, Type: ast.TypeString}
}

ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
if len(es) > 0 != tc.Err {
if len(es) == 0 {
t.Errorf("%q: no errors", tn)
if err := c.Interpolate(vars); err != nil {
t.Fatalf("err: %s", err)
}
}

for _, e := range es {
t.Errorf("%q: err: %s", tn, e)
}
ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
if len(es) > 0 != tc.Err {
if len(es) == 0 {
t.Errorf("%q: no errors", tn)
}

t.FailNow()
}
for _, e := range es {
t.Errorf("%q: err: %s", tn, e)
}

if !reflect.DeepEqual(ws, tc.Warnings) {
t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
}
t.FailNow()
}

if tc.Errors != nil {
sort.Sort(errorSort(es))
sort.Sort(errorSort(tc.Errors))
if !reflect.DeepEqual(ws, tc.Warnings) {
t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
}

if !reflect.DeepEqual(es, tc.Errors) {
t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
if tc.Errors != nil {
sort.Sort(errorSort(es))
sort.Sort(errorSort(tc.Errors))

if !reflect.DeepEqual(es, tc.Errors) {
t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
}
}
}
})

}
}

Expand Down