helper/schema: handle TypeMap elem consistently with other collection types
For historical reasons, the handling of element types for maps is inconsistent with other collection types. Here we begin a multi-step process to make it consistent, starting by supporting both the "consistent" form of using a schema.Schema and an existing erroneous form of using a schema.Type directly. In subsequent commits we will phase out the erroneous form and require the schema.Schema approach, the same as we do for TypeList and TypeSet.
This commit is contained in:
parent
04899393b0
commit
035d56409f
|
@ -126,6 +126,8 @@ func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
|
||||||
switch v := current.Elem.(type) {
|
switch v := current.Elem.(type) {
|
||||||
case ValueType:
|
case ValueType:
|
||||||
current = &Schema{Type: v}
|
current = &Schema{Type: v}
|
||||||
|
case *Schema:
|
||||||
|
current, _ = current.Elem.(*Schema)
|
||||||
default:
|
default:
|
||||||
// maps default to string values. This is all we can have
|
// maps default to string values. This is all we can have
|
||||||
// if this is nested in another list or map.
|
// if this is nested in another list or map.
|
||||||
|
@ -249,11 +251,10 @@ func readObjectField(
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert map values to the proper primitive type based on schema.Elem
|
// convert map values to the proper primitive type based on schema.Elem
|
||||||
func mapValuesToPrimitive(m map[string]interface{}, schema *Schema) error {
|
func mapValuesToPrimitive(k string, m map[string]interface{}, schema *Schema) error {
|
||||||
|
elemType, err := getValueType(k, schema)
|
||||||
elemType := TypeString
|
if err != nil {
|
||||||
if et, ok := schema.Elem.(ValueType); ok {
|
return err
|
||||||
elemType = et
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch elemType {
|
switch elemType {
|
||||||
|
|
|
@ -206,7 +206,7 @@ func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult,
|
||||||
panic(fmt.Sprintf("unknown type: %#v", mraw))
|
panic(fmt.Sprintf("unknown type: %#v", mraw))
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mapValuesToPrimitive(result, schema)
|
err := mapValuesToPrimitive(k, result, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FieldReadResult{}, nil
|
return FieldReadResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,10 @@ func TestConfigFieldReader(t *testing.T) {
|
||||||
"one": "1",
|
"one": "1",
|
||||||
"two": "2",
|
"two": "2",
|
||||||
},
|
},
|
||||||
|
"mapIntNestedSchema": map[string]interface{}{
|
||||||
|
"one": "1",
|
||||||
|
"two": "2",
|
||||||
|
},
|
||||||
"mapFloat": map[string]interface{}{
|
"mapFloat": map[string]interface{}{
|
||||||
"oneDotTwo": "1.2",
|
"oneDotTwo": "1.2",
|
||||||
},
|
},
|
||||||
|
|
|
@ -122,7 +122,8 @@ func (r *DiffFieldReader) readMap(
|
||||||
result[k] = v.New
|
result[k] = v.New
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mapValuesToPrimitive(result, schema)
|
key := address[len(address)-1]
|
||||||
|
err = mapValuesToPrimitive(key, result, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FieldReadResult{}, nil
|
return FieldReadResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -433,6 +433,19 @@ func TestDiffFieldReader(t *testing.T) {
|
||||||
New: "2",
|
New: "2",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"mapIntNestedSchema.%": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
"mapIntNestedSchema.one": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "1",
|
||||||
|
},
|
||||||
|
"mapIntNestedSchema.two": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
|
||||||
"mapFloat.%": &terraform.ResourceAttrDiff{
|
"mapFloat.%": &terraform.ResourceAttrDiff{
|
||||||
Old: "",
|
Old: "",
|
||||||
New: "1",
|
New: "1",
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, err
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
err := mapValuesToPrimitive(result, schema)
|
err := mapValuesToPrimitive(k, result, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FieldReadResult{}, nil
|
return FieldReadResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@ func TestMapFieldReader(t *testing.T) {
|
||||||
"mapInt.one": "1",
|
"mapInt.one": "1",
|
||||||
"mapInt.two": "2",
|
"mapInt.two": "2",
|
||||||
|
|
||||||
|
"mapIntNestedSchema.%": "2",
|
||||||
|
"mapIntNestedSchema.one": "1",
|
||||||
|
"mapIntNestedSchema.two": "2",
|
||||||
|
|
||||||
"mapFloat.%": "1",
|
"mapFloat.%": "1",
|
||||||
"mapFloat.oneDotTwo": "1.2",
|
"mapFloat.oneDotTwo": "1.2",
|
||||||
|
|
||||||
|
|
|
@ -215,6 +215,13 @@ func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) {
|
||||||
Type: TypeMap,
|
Type: TypeMap,
|
||||||
Elem: TypeInt,
|
Elem: TypeInt,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// This is used to verify that the type of a Map can be specified using the
|
||||||
|
// same syntax as for lists (as a nested *Schema passed to Elem)
|
||||||
|
"mapIntNestedSchema": &Schema{
|
||||||
|
Type: TypeMap,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
"mapFloat": &Schema{
|
"mapFloat": &Schema{
|
||||||
Type: TypeMap,
|
Type: TypeMap,
|
||||||
Elem: TypeFloat,
|
Elem: TypeFloat,
|
||||||
|
@ -360,6 +367,19 @@ func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) {
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"mapIntNestedSchema": {
|
||||||
|
[]string{"mapIntNestedSchema"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: map[string]interface{}{
|
||||||
|
"one": 1,
|
||||||
|
"two": 2,
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
"mapFloat": {
|
"mapFloat": {
|
||||||
[]string{"mapFloat"},
|
[]string{"mapFloat"},
|
||||||
FieldReadResult{
|
FieldReadResult{
|
||||||
|
|
|
@ -1461,13 +1461,10 @@ func getValueType(k string, schema *Schema) (ValueType, error) {
|
||||||
return vt, nil
|
return vt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a Schema is provided to a Map, we use the Type of that schema
|
||||||
|
// as the type for each element in the Map.
|
||||||
if s, ok := schema.Elem.(*Schema); ok {
|
if s, ok := schema.Elem.(*Schema); ok {
|
||||||
if s.Elem == nil {
|
return s.Type, nil
|
||||||
return TypeString, nil
|
|
||||||
}
|
|
||||||
if vt, ok := s.Elem.(ValueType); ok {
|
|
||||||
return vt, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := schema.Elem.(*Resource); ok {
|
if _, ok := schema.Elem.(*Resource); ok {
|
||||||
|
|
|
@ -4528,6 +4528,42 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"Map with type specified as value type": {
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"user_data": &Schema{
|
||||||
|
Type: TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
Elem: TypeBool,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"user_data": map[string]interface{}{
|
||||||
|
"foo": "not_a_bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"Map with type specified as nested Schema": {
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"user_data": &Schema{
|
||||||
|
Type: TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &Schema{Type: TypeBool},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"user_data": map[string]interface{}{
|
||||||
|
"foo": "not_a_bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
"Bad map: just a slice": {
|
"Bad map: just a slice": {
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
|
|
Loading…
Reference in New Issue