mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-02 12:17:39 -06:00
035d56409f
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.
472 lines
8.2 KiB
Go
472 lines
8.2 KiB
Go
package schema
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestAddrToSchema(t *testing.T) {
|
|
cases := map[string]struct {
|
|
Addr []string
|
|
Schema map[string]*Schema
|
|
Result []ValueType
|
|
}{
|
|
"full object": {
|
|
[]string{},
|
|
map[string]*Schema{
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{Type: TypeInt},
|
|
},
|
|
},
|
|
[]ValueType{typeObject},
|
|
},
|
|
|
|
"list": {
|
|
[]string{"list"},
|
|
map[string]*Schema{
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{Type: TypeInt},
|
|
},
|
|
},
|
|
[]ValueType{TypeList},
|
|
},
|
|
|
|
"list.#": {
|
|
[]string{"list", "#"},
|
|
map[string]*Schema{
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{Type: TypeInt},
|
|
},
|
|
},
|
|
[]ValueType{TypeList, TypeInt},
|
|
},
|
|
|
|
"list.0": {
|
|
[]string{"list", "0"},
|
|
map[string]*Schema{
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{Type: TypeInt},
|
|
},
|
|
},
|
|
[]ValueType{TypeList, TypeInt},
|
|
},
|
|
|
|
"list.0 with resource": {
|
|
[]string{"list", "0"},
|
|
map[string]*Schema{
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Resource{
|
|
Schema: map[string]*Schema{
|
|
"field": &Schema{Type: TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeList, typeObject},
|
|
},
|
|
|
|
"list.0.field": {
|
|
[]string{"list", "0", "field"},
|
|
map[string]*Schema{
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Resource{
|
|
Schema: map[string]*Schema{
|
|
"field": &Schema{Type: TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeList, typeObject, TypeString},
|
|
},
|
|
|
|
"set": {
|
|
[]string{"set"},
|
|
map[string]*Schema{
|
|
"set": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Schema{Type: TypeInt},
|
|
Set: func(a interface{}) int {
|
|
return a.(int)
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeSet},
|
|
},
|
|
|
|
"set.#": {
|
|
[]string{"set", "#"},
|
|
map[string]*Schema{
|
|
"set": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Schema{Type: TypeInt},
|
|
Set: func(a interface{}) int {
|
|
return a.(int)
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeSet, TypeInt},
|
|
},
|
|
|
|
"set.0": {
|
|
[]string{"set", "0"},
|
|
map[string]*Schema{
|
|
"set": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Schema{Type: TypeInt},
|
|
Set: func(a interface{}) int {
|
|
return a.(int)
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeSet, TypeInt},
|
|
},
|
|
|
|
"set.0 with resource": {
|
|
[]string{"set", "0"},
|
|
map[string]*Schema{
|
|
"set": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Resource{
|
|
Schema: map[string]*Schema{
|
|
"field": &Schema{Type: TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeSet, typeObject},
|
|
},
|
|
|
|
"mapElem": {
|
|
[]string{"map", "foo"},
|
|
map[string]*Schema{
|
|
"map": &Schema{Type: TypeMap},
|
|
},
|
|
[]ValueType{TypeMap, TypeString},
|
|
},
|
|
|
|
"setDeep": {
|
|
[]string{"set", "50", "index"},
|
|
map[string]*Schema{
|
|
"set": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Resource{
|
|
Schema: map[string]*Schema{
|
|
"index": &Schema{Type: TypeInt},
|
|
"value": &Schema{Type: TypeString},
|
|
},
|
|
},
|
|
Set: func(a interface{}) int {
|
|
return a.(map[string]interface{})["index"].(int)
|
|
},
|
|
},
|
|
},
|
|
[]ValueType{TypeSet, typeObject, TypeInt},
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
result := addrToSchema(tc.Addr, tc.Schema)
|
|
types := make([]ValueType, len(result))
|
|
for i, v := range result {
|
|
types[i] = v.Type
|
|
}
|
|
|
|
if !reflect.DeepEqual(types, tc.Result) {
|
|
t.Fatalf("%s: %#v", name, types)
|
|
}
|
|
}
|
|
}
|
|
|
|
// testFieldReader is a helper that should be used to verify that
|
|
// a FieldReader behaves properly in all the common cases.
|
|
func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) {
|
|
schema := map[string]*Schema{
|
|
// Primitives
|
|
"bool": &Schema{Type: TypeBool},
|
|
"float": &Schema{Type: TypeFloat},
|
|
"int": &Schema{Type: TypeInt},
|
|
"string": &Schema{Type: TypeString},
|
|
|
|
// Lists
|
|
"list": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{Type: TypeString},
|
|
},
|
|
"listInt": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{Type: TypeInt},
|
|
},
|
|
"listMap": &Schema{
|
|
Type: TypeList,
|
|
Elem: &Schema{
|
|
Type: TypeMap,
|
|
},
|
|
},
|
|
|
|
// Maps
|
|
"map": &Schema{Type: TypeMap},
|
|
"mapInt": &Schema{
|
|
Type: TypeMap,
|
|
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{
|
|
Type: TypeMap,
|
|
Elem: TypeFloat,
|
|
},
|
|
"mapBool": &Schema{
|
|
Type: TypeMap,
|
|
Elem: TypeBool,
|
|
},
|
|
|
|
// Sets
|
|
"set": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Schema{Type: TypeInt},
|
|
Set: func(a interface{}) int {
|
|
return a.(int)
|
|
},
|
|
},
|
|
"setDeep": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Resource{
|
|
Schema: map[string]*Schema{
|
|
"index": &Schema{Type: TypeInt},
|
|
"value": &Schema{Type: TypeString},
|
|
},
|
|
},
|
|
Set: func(a interface{}) int {
|
|
return a.(map[string]interface{})["index"].(int)
|
|
},
|
|
},
|
|
"setEmpty": &Schema{
|
|
Type: TypeSet,
|
|
Elem: &Schema{Type: TypeInt},
|
|
Set: func(a interface{}) int {
|
|
return a.(int)
|
|
},
|
|
},
|
|
}
|
|
|
|
cases := map[string]struct {
|
|
Addr []string
|
|
Result FieldReadResult
|
|
Err bool
|
|
}{
|
|
"noexist": {
|
|
[]string{"boolNOPE"},
|
|
FieldReadResult{
|
|
Value: nil,
|
|
Exists: false,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"bool": {
|
|
[]string{"bool"},
|
|
FieldReadResult{
|
|
Value: true,
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"float": {
|
|
[]string{"float"},
|
|
FieldReadResult{
|
|
Value: 3.1415,
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"int": {
|
|
[]string{"int"},
|
|
FieldReadResult{
|
|
Value: 42,
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"string": {
|
|
[]string{"string"},
|
|
FieldReadResult{
|
|
Value: "string",
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"list": {
|
|
[]string{"list"},
|
|
FieldReadResult{
|
|
Value: []interface{}{
|
|
"foo",
|
|
"bar",
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"listInt": {
|
|
[]string{"listInt"},
|
|
FieldReadResult{
|
|
Value: []interface{}{
|
|
21,
|
|
42,
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"map": {
|
|
[]string{"map"},
|
|
FieldReadResult{
|
|
Value: map[string]interface{}{
|
|
"foo": "bar",
|
|
"bar": "baz",
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"mapInt": {
|
|
[]string{"mapInt"},
|
|
FieldReadResult{
|
|
Value: map[string]interface{}{
|
|
"one": 1,
|
|
"two": 2,
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"mapIntNestedSchema": {
|
|
[]string{"mapIntNestedSchema"},
|
|
FieldReadResult{
|
|
Value: map[string]interface{}{
|
|
"one": 1,
|
|
"two": 2,
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"mapFloat": {
|
|
[]string{"mapFloat"},
|
|
FieldReadResult{
|
|
Value: map[string]interface{}{
|
|
"oneDotTwo": 1.2,
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"mapBool": {
|
|
[]string{"mapBool"},
|
|
FieldReadResult{
|
|
Value: map[string]interface{}{
|
|
"True": true,
|
|
"False": false,
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"mapelem": {
|
|
[]string{"map", "foo"},
|
|
FieldReadResult{
|
|
Value: "bar",
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"set": {
|
|
[]string{"set"},
|
|
FieldReadResult{
|
|
Value: []interface{}{10, 50},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"setDeep": {
|
|
[]string{"setDeep"},
|
|
FieldReadResult{
|
|
Value: []interface{}{
|
|
map[string]interface{}{
|
|
"index": 10,
|
|
"value": "foo",
|
|
},
|
|
map[string]interface{}{
|
|
"index": 50,
|
|
"value": "bar",
|
|
},
|
|
},
|
|
Exists: true,
|
|
Computed: false,
|
|
},
|
|
false,
|
|
},
|
|
|
|
"setEmpty": {
|
|
[]string{"setEmpty"},
|
|
FieldReadResult{
|
|
Value: []interface{}{},
|
|
Exists: false,
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
r := f(schema)
|
|
out, err := r.ReadField(tc.Addr)
|
|
if err != nil != tc.Err {
|
|
t.Fatalf("%s: err: %s", name, err)
|
|
}
|
|
if s, ok := out.Value.(*Set); ok {
|
|
// If it is a set, convert to a list so its more easily checked.
|
|
out.Value = s.List()
|
|
}
|
|
if !reflect.DeepEqual(tc.Result, out) {
|
|
t.Fatalf("%s: bad: %#v", name, out)
|
|
}
|
|
}
|
|
}
|