diff --git a/.changelog/371.txt b/.changelog/371.txt new file mode 100644 index 000000000..046059f4d --- /dev/null +++ b/.changelog/371.txt @@ -0,0 +1,3 @@ +```release-note:bug +tfsdk: Prevented configuration handling error when `Schema` contained `Blocks` +``` diff --git a/tfsdk/attribute_test.go b/tfsdk/attribute_test.go new file mode 100644 index 000000000..f963b93e4 --- /dev/null +++ b/tfsdk/attribute_test.go @@ -0,0 +1,365 @@ +package tfsdk + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestAttributeAttributeType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute Attribute + expected attr.Type + }{ + "Attributes-ListNestedAttributes": { + attribute: Attribute{ + Attributes: ListNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + }, + "Attributes-MapNestedAttributes": { + attribute: Attribute{ + Attributes: MapNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + }, + "Attributes-SetNestedAttributes": { + attribute: Attribute{ + Attributes: SetNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + }, + "Attributes-SingleNestedAttributes": { + attribute: Attribute{ + Attributes: SingleNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + "Type-BoolType": { + attribute: Attribute{ + Required: true, + Type: types.BoolType, + }, + expected: types.BoolType, + }, + "Type-Float64Type": { + attribute: Attribute{ + Required: true, + Type: types.Float64Type, + }, + expected: types.Float64Type, + }, + "Type-Int64Type": { + attribute: Attribute{ + Required: true, + Type: types.Int64Type, + }, + expected: types.Int64Type, + }, + "Type-ListType": { + attribute: Attribute{ + Required: true, + Type: types.ListType{ + ElemType: types.StringType, + }, + }, + expected: types.ListType{ + ElemType: types.StringType, + }, + }, + "Type-MapType": { + attribute: Attribute{ + Required: true, + Type: types.MapType{ + ElemType: types.StringType, + }, + }, + expected: types.MapType{ + ElemType: types.StringType, + }, + }, + "Type-ObjectType": { + attribute: Attribute{ + Required: true, + Type: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_object_attribute": types.StringType, + }, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_object_attribute": types.StringType, + }, + }, + }, + "Type-NumberType": { + attribute: Attribute{ + Required: true, + Type: types.NumberType, + }, + expected: types.NumberType, + }, + "Type-SetType": { + attribute: Attribute{ + Required: true, + Type: types.SetType{ + ElemType: types.StringType, + }, + }, + expected: types.SetType{ + ElemType: types.StringType, + }, + }, + "Type-StringType": { + attribute: Attribute{ + Required: true, + Type: types.StringType, + }, + expected: types.StringType, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.attributeType() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestAttributeTerraformType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute Attribute + expected tftypes.Type + }{ + "Attributes-ListNestedAttributes": { + attribute: Attribute{ + Attributes: ListNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_nested_attribute": tftypes.String, + }, + }, + }, + }, + "Attributes-MapNestedAttributes": { + attribute: Attribute{ + Attributes: MapNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_nested_attribute": tftypes.String, + }, + }, + }, + }, + "Attributes-SetNestedAttributes": { + attribute: Attribute{ + Attributes: SetNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_nested_attribute": tftypes.String, + }, + }, + }, + }, + "Attributes-SingleNestedAttributes": { + attribute: Attribute{ + Attributes: SingleNestedAttributes(map[string]Attribute{ + "test_nested_attribute": { + Required: true, + Type: types.StringType, + }, + }), + Required: true, + }, + expected: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_nested_attribute": tftypes.String, + }, + }, + }, + "Type-BoolType": { + attribute: Attribute{ + Required: true, + Type: types.BoolType, + }, + expected: tftypes.Bool, + }, + "Type-Float64Type": { + attribute: Attribute{ + Required: true, + Type: types.Float64Type, + }, + expected: tftypes.Number, + }, + "Type-Int64Type": { + attribute: Attribute{ + Required: true, + Type: types.Int64Type, + }, + expected: tftypes.Number, + }, + "Type-ListType": { + attribute: Attribute{ + Required: true, + Type: types.ListType{ + ElemType: types.StringType, + }, + }, + expected: tftypes.List{ + ElementType: tftypes.String, + }, + }, + "Type-MapType": { + attribute: Attribute{ + Required: true, + Type: types.MapType{ + ElemType: types.StringType, + }, + }, + expected: tftypes.Map{ + ElementType: tftypes.String, + }, + }, + "Type-NumberType": { + attribute: Attribute{ + Required: true, + Type: types.NumberType, + }, + expected: tftypes.Number, + }, + "Type-ObjectType": { + attribute: Attribute{ + Required: true, + Type: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_object_attribute": types.StringType, + }, + }, + }, + expected: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_object_attribute": tftypes.String, + }, + }, + }, + "Type-SetType": { + attribute: Attribute{ + Required: true, + Type: types.SetType{ + ElemType: types.StringType, + }, + }, + expected: tftypes.Set{ + ElementType: tftypes.String, + }, + }, + "Type-StringType": { + attribute: Attribute{ + Required: true, + Type: types.StringType, + }, + expected: tftypes.String, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.terraformType(context.Background()) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/tfsdk/block.go b/tfsdk/block.go index 30d5c7309..c761a865a 100644 --- a/tfsdk/block.go +++ b/tfsdk/block.go @@ -134,7 +134,7 @@ func (b Block) attributeType() attr.Type { attrType.AttrTypes[attrName] = attr.attributeType() } - for blockName, block := range b.Attributes { + for blockName, block := range b.Blocks { attrType.AttrTypes[blockName] = block.attributeType() } diff --git a/tfsdk/block_test.go b/tfsdk/block_test.go new file mode 100644 index 000000000..ed9786230 --- /dev/null +++ b/tfsdk/block_test.go @@ -0,0 +1,203 @@ +package tfsdk + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestBlockAttributeType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block Block + expected attr.Type + }{ + "NestingMode-List": { + block: Block{ + Attributes: map[string]Attribute{ + "test_attribute": { + Required: true, + Type: types.StringType, + }, + }, + Blocks: map[string]Block{ + "test_block": { + Attributes: map[string]Attribute{ + "test_block_attribute": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + NestingMode: BlockNestingModeList, + }, + expected: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_attribute": types.StringType, + "test_block": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_block_attribute": types.StringType, + }, + }, + }, + }, + }, + }, + }, + "NestingMode-Set": { + block: Block{ + Attributes: map[string]Attribute{ + "test_attribute": { + Required: true, + Type: types.StringType, + }, + }, + Blocks: map[string]Block{ + "test_block": { + Attributes: map[string]Attribute{ + "test_block_attribute": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + NestingMode: BlockNestingModeSet, + }, + expected: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_attribute": types.StringType, + "test_block": types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_block_attribute": types.StringType, + }, + }, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.attributeType() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBlockTerraformType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block Block + expected tftypes.Type + }{ + "NestingMode-List": { + block: Block{ + Attributes: map[string]Attribute{ + "test_attribute": { + Required: true, + Type: types.StringType, + }, + }, + Blocks: map[string]Block{ + "test_block": { + Attributes: map[string]Attribute{ + "test_block_attribute": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + NestingMode: BlockNestingModeList, + }, + expected: tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_attribute": tftypes.String, + "test_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_block_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + }, + "NestingMode-Set": { + block: Block{ + Attributes: map[string]Attribute{ + "test_attribute": { + Required: true, + Type: types.StringType, + }, + }, + Blocks: map[string]Block{ + "test_block": { + Attributes: map[string]Attribute{ + "test_block_attribute": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + NestingMode: BlockNestingModeSet, + }, + expected: tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_attribute": tftypes.String, + "test_block": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_block_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.terraformType(context.Background()) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +}