From 6709ad0dcd7c28b4b3257ad5fe499ca2bfe4cc93 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 28 Oct 2022 13:10:29 +0100 Subject: [PATCH 1/4] Refactoring so that custom nested attributes type can embed NestedAttributes interface --- attr/value.go | 4 +++ internal/fwschema/block.go | 4 ++- .../fwxschema/attribute_plan_modification.go | 4 +-- .../fwxschema/attribute_validation.go | 4 +-- internal/fwschema/schema.go | 8 +++-- internal/fwserver/attr_value.go | 16 +++++++-- .../fwserver/attribute_plan_modification.go | 11 +++---- .../attribute_plan_modification_test.go | 6 ++-- internal/fwserver/attribute_validation.go | 33 ++++++++++++++----- internal/toproto5/schema_attribute.go | 5 +-- internal/toproto5/schema_attribute_test.go | 8 ++--- internal/toproto6/schema_attribute.go | 13 ++++---- internal/toproto6/schema_attribute_test.go | 8 ++--- tfsdk/attribute.go | 11 ++++--- tfsdk/block.go | 5 +-- tfsdk/nested_attributes.go | 18 +++++----- tfsdk/schema.go | 19 ++++++----- tfsdk/schema_test.go | 6 ++-- {internal/fwschema => types}/attribute.go | 5 +-- types/list.go | 4 +++ .../fwschema => types}/nested_attributes.go | 12 +++---- .../nested_attributes_test.go | 28 ++++++++-------- 22 files changed, 137 insertions(+), 95 deletions(-) rename {internal/fwschema => types}/attribute.go (99%) rename {internal/fwschema => types}/nested_attributes.go (98%) rename {internal/fwschema => types}/nested_attributes_test.go (81%) diff --git a/attr/value.go b/attr/value.go index cbfd53691..14e3b0909 100644 --- a/attr/value.go +++ b/attr/value.go @@ -46,3 +46,7 @@ type Value interface { // compatibility guarantees within the framework. String() string } + +type FrameworkValue interface { + ToFrameworkValue() Value +} diff --git a/internal/fwschema/block.go b/internal/fwschema/block.go index 0ee4e8672..d94998477 100644 --- a/internal/fwschema/block.go +++ b/internal/fwschema/block.go @@ -2,6 +2,8 @@ package fwschema import ( "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -24,7 +26,7 @@ type Block interface { // GetAttributes should return the nested attributes of a block, if // applicable. This is named differently than Attributes to prevent a // conflict with the tfsdk.Block field name. - GetAttributes() map[string]Attribute + GetAttributes() map[string]types.Attribute // GetBlocks should return the nested blocks of a block, if // applicable. This is named differently than Blocks to prevent a diff --git a/internal/fwschema/fwxschema/attribute_plan_modification.go b/internal/fwschema/fwxschema/attribute_plan_modification.go index 50a6ccbdb..e490506a3 100644 --- a/internal/fwschema/fwxschema/attribute_plan_modification.go +++ b/internal/fwschema/fwxschema/attribute_plan_modification.go @@ -1,8 +1,8 @@ package fwxschema import ( - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" ) // AttributeWithPlanModifiers is an optional interface on Attribute which enables @@ -10,7 +10,7 @@ import ( type AttributeWithPlanModifiers interface { // Implementations should include the fwschema.Attribute interface methods // for proper attribute handling. - fwschema.Attribute + types.Attribute // GetPlanModifiers should return a list of attribute-based plan modifiers. // This is named differently than PlanModifiers to prevent a conflict with diff --git a/internal/fwschema/fwxschema/attribute_validation.go b/internal/fwschema/fwxschema/attribute_validation.go index f75b76773..d06fd585c 100644 --- a/internal/fwschema/fwxschema/attribute_validation.go +++ b/internal/fwschema/fwxschema/attribute_validation.go @@ -1,8 +1,8 @@ package fwxschema import ( - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" ) // AttributeWithValidators is an optional interface on Attribute which enables @@ -10,7 +10,7 @@ import ( type AttributeWithValidators interface { // Implementations should include the fwschema.Attribute interface methods // for proper attribute handling. - fwschema.Attribute + types.Attribute // GetValidators should return a list of attribute-based validators. This // is named differently than PlanModifiers to prevent a conflict with the diff --git a/internal/fwschema/schema.go b/internal/fwschema/schema.go index f74bdaf9f..8754f0ae3 100644 --- a/internal/fwschema/schema.go +++ b/internal/fwschema/schema.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -18,16 +20,16 @@ type Schema interface { // AttributeAtPath should return the Attribute at the given path or return // an error. - AttributeAtPath(context.Context, path.Path) (Attribute, diag.Diagnostics) + AttributeAtPath(context.Context, path.Path) (types.Attribute, diag.Diagnostics) // AttributeAtTerraformPath should return the Attribute at the given // Terraform path or return an error. - AttributeAtTerraformPath(context.Context, *tftypes.AttributePath) (Attribute, error) + AttributeAtTerraformPath(context.Context, *tftypes.AttributePath) (types.Attribute, error) // GetAttributes should return the attributes of a schema. This is named // differently than Attributes to prevent a conflict with the tfsdk.Schema // field name. - GetAttributes() map[string]Attribute + GetAttributes() map[string]types.Attribute // GetBlocks should return the blocks of a schema. This is named // differently than Blocks to prevent a conflict with the tfsdk.Schema diff --git a/internal/fwserver/attr_value.go b/internal/fwserver/attr_value.go index 70731345d..706b09f1b 100644 --- a/internal/fwserver/attr_value.go +++ b/internal/fwserver/attr_value.go @@ -3,24 +3,34 @@ package fwserver import ( "context" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func coerceListValue(schemaPath path.Path, value attr.Value) (types.List, diag.Diagnostics) { - list, ok := value.(types.List) + // TODO: This assertion can be removed once attr.Value has been updated to include FrameworkValue interface for all attr.Value(s) + _, ok := value.(attr.FrameworkValue) + if !ok { + return types.List{Null: true}, diag.Diagnostics{ + attributePlanModificationWalkError(schemaPath, value), + } + } + + l := value.(attr.FrameworkValue).ToFrameworkValue() + _, ok = l.(types.List) if !ok { return types.List{Null: true}, diag.Diagnostics{ attributePlanModificationWalkError(schemaPath, value), } } - return list, nil + return l.(types.List), nil } func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Diagnostics) { diff --git a/internal/fwserver/attribute_plan_modification.go b/internal/fwserver/attribute_plan_modification.go index c28f77f20..6b24f1805 100644 --- a/internal/fwserver/attribute_plan_modification.go +++ b/internal/fwserver/attribute_plan_modification.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata" "github.com/hashicorp/terraform-plugin-framework/internal/logging" @@ -29,7 +28,7 @@ type ModifyAttributePlanResponse struct { // The extra Attribute parameter is a carry-over of creating the proto6server // package from the tfsdk package and not wanting to export the method. // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 -func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func AttributeModifyPlan(ctx context.Context, a types.Attribute, req tfsdk.ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { ctx = logging.FrameworkWithAttributePath(ctx, req.AttributePath.String()) var requiresReplace bool @@ -97,7 +96,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo nm := a.GetAttributes().GetNestingMode() switch nm { - case fwschema.NestingModeList: + case types.NestingModeList: configList, diags := coerceListValue(req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -219,7 +218,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo if resp.Diagnostics.HasError() { return } - case fwschema.NestingModeSet: + case types.NestingModeSet: configSet, diags := coerceSetValue(req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -341,7 +340,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo if resp.Diagnostics.HasError() { return } - case fwschema.NestingModeMap: + case types.NestingModeMap: configMap, diags := coerceMapValue(req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -463,7 +462,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo if resp.Diagnostics.HasError() { return } - case fwschema.NestingModeSingle: + case types.NestingModeSingle: configObject, diags := coerceObjectValue(req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) diff --git a/internal/fwserver/attribute_plan_modification_test.go b/internal/fwserver/attribute_plan_modification_test.go index 7b00a49b4..0f6fa7399 100644 --- a/internal/fwserver/attribute_plan_modification_test.go +++ b/internal/fwserver/attribute_plan_modification_test.go @@ -6,16 +6,16 @@ import ( "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/privatestate" "github.com/hashicorp/terraform-plugin-framework/internal/testing/planmodifiers" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestAttributeModifyPlan(t *testing.T) { @@ -30,7 +30,7 @@ func TestAttributeModifyPlan(t *testing.T) { testEmptyProviderData := privatestate.EmptyProviderData(context.Background()) testCases := map[string]struct { - attribute fwschema.Attribute + attribute types.Attribute req tfsdk.ModifyAttributePlanRequest expectedResp ModifyAttributePlanResponse }{ diff --git a/internal/fwserver/attribute_validation.go b/internal/fwserver/attribute_validation.go index b781053b4..c67eeca46 100644 --- a/internal/fwserver/attribute_validation.go +++ b/internal/fwserver/attribute_validation.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata" "github.com/hashicorp/terraform-plugin-framework/internal/logging" @@ -18,7 +18,7 @@ import ( // The extra Attribute parameter is a carry-over of creating the proto6server // package from the tfsdk package and not wanting to export the method. // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 -func AttributeValidate(ctx context.Context, a fwschema.Attribute, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { +func AttributeValidate(ctx context.Context, a types.Attribute, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { ctx = logging.FrameworkWithAttributePath(ctx, req.AttributePath.String()) if (a.GetAttributes() == nil || len(a.GetAttributes().GetAttributes()) == 0) && a.GetType() == nil { @@ -132,15 +132,16 @@ func AttributeValidate(ctx context.Context, a fwschema.Attribute, req tfsdk.Vali // The extra Attribute parameter is a carry-over of creating the proto6server // package from the tfsdk package and not wanting to export the method. // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 -func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { +func AttributeValidateNestedAttributes(ctx context.Context, a types.Attribute, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { if a.GetAttributes() == nil || len(a.GetAttributes().GetAttributes()) == 0 { return } nm := a.GetAttributes().GetNestingMode() switch nm { - case fwschema.NestingModeList: - l, ok := req.AttributeConfig.(types.List) + case types.NestingModeList: + // TODO: This assertion can be removed once attr.Value has been updated to include FrameworkValue interface for all attr.Value(s) + _, ok := req.AttributeConfig.(attr.FrameworkValue) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) @@ -153,7 +154,21 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute return } - for idx := range l.Elements() { + l := req.AttributeConfig.(attr.FrameworkValue).ToFrameworkValue() + + _, ok = l.(types.List) + if !ok { + err := fmt.Errorf("unknown framework value (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for idx := range l.(types.List).Elements() { for nestedName, nestedAttr := range a.GetAttributes().GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ AttributePath: req.AttributePath.AtListIndex(idx).AtName(nestedName), @@ -169,7 +184,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute resp.Diagnostics = nestedAttrResp.Diagnostics } } - case fwschema.NestingModeSet: + case types.NestingModeSet: s, ok := req.AttributeConfig.(types.Set) if !ok { @@ -199,7 +214,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute resp.Diagnostics = nestedAttrResp.Diagnostics } } - case fwschema.NestingModeMap: + case types.NestingModeMap: m, ok := req.AttributeConfig.(types.Map) if !ok { @@ -229,7 +244,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute resp.Diagnostics = nestedAttrResp.Diagnostics } } - case fwschema.NestingModeSingle: + case types.NestingModeSingle: o, ok := req.AttributeConfig.(types.Object) if !ok { diff --git a/internal/toproto5/schema_attribute.go b/internal/toproto5/schema_attribute.go index 563e8f912..8d5063933 100644 --- a/internal/toproto5/schema_attribute.go +++ b/internal/toproto5/schema_attribute.go @@ -3,7 +3,8 @@ package toproto5 import ( "context" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -11,7 +12,7 @@ import ( // SchemaAttribute returns the *tfprotov5.SchemaAttribute equivalent of an // Attribute. Errors will be tftypes.AttributePathErrors based on `path`. // `name` is the name of the attribute. -func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePath, a fwschema.Attribute) (*tfprotov5.SchemaAttribute, error) { +func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePath, a types.Attribute) (*tfprotov5.SchemaAttribute, error) { if a.GetAttributes() != nil && len(a.GetAttributes().GetAttributes()) > 0 { return nil, path.NewErrorf("protocol version 5 cannot have Attributes set") } diff --git a/internal/toproto5/schema_attribute_test.go b/internal/toproto5/schema_attribute_test.go index d9512007b..fe3e905ad 100644 --- a/internal/toproto5/schema_attribute_test.go +++ b/internal/toproto5/schema_attribute_test.go @@ -5,13 +5,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov5" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestSchemaAttribute(t *testing.T) { @@ -19,7 +19,7 @@ func TestSchemaAttribute(t *testing.T) { type testCase struct { name string - attr fwschema.Attribute + attr types.Attribute path *tftypes.AttributePath expected *tfprotov5.SchemaAttribute expectedErr string diff --git a/internal/toproto6/schema_attribute.go b/internal/toproto6/schema_attribute.go index 4f22174e5..9f56df746 100644 --- a/internal/toproto6/schema_attribute.go +++ b/internal/toproto6/schema_attribute.go @@ -4,7 +4,8 @@ import ( "context" "sort" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -12,7 +13,7 @@ import ( // SchemaAttribute returns the *tfprotov6.SchemaAttribute equivalent of an // Attribute. Errors will be tftypes.AttributePathErrors based on `path`. // `name` is the name of the attribute. -func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePath, a fwschema.Attribute) (*tfprotov6.SchemaAttribute, error) { +func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePath, a types.Attribute) (*tfprotov6.SchemaAttribute, error) { if a.GetAttributes() != nil && len(a.GetAttributes().GetAttributes()) > 0 && a.GetType() != nil { return nil, path.NewErrorf("cannot have both Attributes and Type set") } @@ -56,13 +57,13 @@ func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePa object := &tfprotov6.SchemaObject{} nm := a.GetAttributes().GetNestingMode() switch nm { - case fwschema.NestingModeSingle: + case types.NestingModeSingle: object.Nesting = tfprotov6.SchemaObjectNestingModeSingle - case fwschema.NestingModeList: + case types.NestingModeList: object.Nesting = tfprotov6.SchemaObjectNestingModeList - case fwschema.NestingModeSet: + case types.NestingModeSet: object.Nesting = tfprotov6.SchemaObjectNestingModeSet - case fwschema.NestingModeMap: + case types.NestingModeMap: object.Nesting = tfprotov6.SchemaObjectNestingModeMap default: return nil, path.NewErrorf("unrecognized nesting mode %v", nm) diff --git a/internal/toproto6/schema_attribute_test.go b/internal/toproto6/schema_attribute_test.go index 78c6947b7..29e3482d8 100644 --- a/internal/toproto6/schema_attribute_test.go +++ b/internal/toproto6/schema_attribute_test.go @@ -5,13 +5,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestSchemaAttribute(t *testing.T) { @@ -19,7 +19,7 @@ func TestSchemaAttribute(t *testing.T) { type testCase struct { name string - attr fwschema.Attribute + attr types.Attribute path *tftypes.AttributePath expected *tfprotov6.SchemaAttribute expectedErr string diff --git a/tfsdk/attribute.go b/tfsdk/attribute.go index e046e5100..2241bdc44 100644 --- a/tfsdk/attribute.go +++ b/tfsdk/attribute.go @@ -4,7 +4,8 @@ import ( "errors" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -12,7 +13,7 @@ import ( // fwxschema.AttributeWithPlanModifiers and fwxschema.AttributeWithValidators // interfaces, however we cannot check that here or it would introduce an // import cycle. -var _ fwschema.Attribute = Attribute{} +var _ types.Attribute = Attribute{} // Attribute defines the constraints and behaviors of a single value field in a // schema. Attributes are the fields that show up in Terraform state files and @@ -29,7 +30,7 @@ type Attribute struct { // type. // // If Attributes is set, Type cannot be. - Attributes fwschema.NestedAttributes + Attributes types.NestedAttributes // Description is used in various tooling, like the language server, to // give practitioners more information about what this attribute is, @@ -159,7 +160,7 @@ func (a Attribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathSt } // Equal returns true if `a` and `o` should be considered Equal. -func (a Attribute) Equal(o fwschema.Attribute) bool { +func (a Attribute) Equal(o types.Attribute) bool { if _, ok := o.(Attribute); !ok { return false } @@ -212,7 +213,7 @@ func (a Attribute) FrameworkType() attr.Type { } // GetAttributes satisfies the fwschema.Attribute interface. -func (a Attribute) GetAttributes() fwschema.NestedAttributes { +func (a Attribute) GetAttributes() types.NestedAttributes { return a.Attributes } diff --git a/tfsdk/block.go b/tfsdk/block.go index 187a01052..8654c62f1 100644 --- a/tfsdk/block.go +++ b/tfsdk/block.go @@ -4,10 +4,11 @@ import ( "fmt" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var _ tftypes.AttributePathStepper = Block{} @@ -158,7 +159,7 @@ func (b Block) Equal(o fwschema.Block) bool { } // GetAttributes satisfies the fwschema.Block interface. -func (b Block) GetAttributes() map[string]fwschema.Attribute { +func (b Block) GetAttributes() map[string]types.Attribute { return schemaAttributes(b.Attributes) } diff --git a/tfsdk/nested_attributes.go b/tfsdk/nested_attributes.go index 48bf792f2..aa709dba8 100644 --- a/tfsdk/nested_attributes.go +++ b/tfsdk/nested_attributes.go @@ -1,14 +1,14 @@ package tfsdk import ( - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" ) // ListNestedAttributes nests `attributes` under another attribute, allowing // multiple instances of that group of attributes to appear in the // configuration. -func ListNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttributes { - return fwschema.ListNestedAttributes{ +func ListNestedAttributes(attributes map[string]Attribute) types.NestedAttributes { + return types.ListNestedAttributes{ UnderlyingAttributes: schemaAttributes(attributes), } } @@ -17,8 +17,8 @@ func ListNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttrib // multiple instances of that group of attributes to appear in the // configuration. Each group will need to be associated with a unique string by // the user. -func MapNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttributes { - return fwschema.MapNestedAttributes{ +func MapNestedAttributes(attributes map[string]Attribute) types.NestedAttributes { + return types.MapNestedAttributes{ UnderlyingAttributes: schemaAttributes(attributes), } } @@ -26,8 +26,8 @@ func MapNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttribu // SetNestedAttributes nests `attributes` under another attribute, allowing // multiple instances of that group of attributes to appear in the // configuration, while requiring each group of values be unique. -func SetNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttributes { - return fwschema.SetNestedAttributes{ +func SetNestedAttributes(attributes map[string]Attribute) types.NestedAttributes { + return types.SetNestedAttributes{ UnderlyingAttributes: schemaAttributes(attributes), } } @@ -35,8 +35,8 @@ func SetNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttribu // SingleNestedAttributes nests `attributes` under another attribute, only // allowing one instance of that group of attributes to appear in the // configuration. -func SingleNestedAttributes(attributes map[string]Attribute) fwschema.NestedAttributes { - return fwschema.SingleNestedAttributes{ +func SingleNestedAttributes(attributes map[string]Attribute) types.NestedAttributes { + return types.SingleNestedAttributes{ UnderlyingAttributes: schemaAttributes(attributes), } } diff --git a/tfsdk/schema.go b/tfsdk/schema.go index b89be1c75..91f0d5011 100644 --- a/tfsdk/schema.go +++ b/tfsdk/schema.go @@ -5,13 +5,14 @@ import ( "errors" "fmt" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/totftypes" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( @@ -140,7 +141,7 @@ func (s Schema) TypeAtTerraformPath(_ context.Context, path *tftypes.AttributePa switch typ := rawType.(type) { case attr.Type: return typ, nil - case fwschema.UnderlyingAttributes: + case types.UnderlyingAttributes: return typ.Type(), nil case fwschema.NestedBlock: return typ.Block.Type(), nil @@ -156,7 +157,7 @@ func (s Schema) TypeAtTerraformPath(_ context.Context, path *tftypes.AttributePa } // GetAttributes satisfies the fwschema.Schema interface. -func (s Schema) GetAttributes() map[string]fwschema.Attribute { +func (s Schema) GetAttributes() map[string]types.Attribute { return schemaAttributes(s.Attributes) } @@ -203,7 +204,7 @@ func (s Schema) Type() attr.Type { // AttributeAtPath returns the Attribute at the passed path. If the path points // to an element or attribute of a complex type, rather than to an Attribute, // it will return an ErrPathInsideAtomicAttribute error. -func (s Schema) AttributeAtPath(ctx context.Context, schemaPath path.Path) (fwschema.Attribute, diag.Diagnostics) { +func (s Schema) AttributeAtPath(ctx context.Context, schemaPath path.Path) (types.Attribute, diag.Diagnostics) { var diags diag.Diagnostics tftypesPath, tftypesDiags := totftypes.AttributePath(ctx, schemaPath) @@ -234,7 +235,7 @@ func (s Schema) AttributeAtPath(ctx context.Context, schemaPath path.Path) (fwsc // AttributeAtPath returns the Attribute at the passed path. If the path points // to an element or attribute of a complex type, rather than to an Attribute, // it will return an ErrPathInsideAtomicAttribute error. -func (s Schema) AttributeAtTerraformPath(_ context.Context, path *tftypes.AttributePath) (fwschema.Attribute, error) { +func (s Schema) AttributeAtTerraformPath(_ context.Context, path *tftypes.AttributePath) (types.Attribute, error) { res, remaining, err := tftypes.WalkAttributePath(s, path) if err != nil { return Attribute{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) @@ -243,11 +244,11 @@ func (s Schema) AttributeAtTerraformPath(_ context.Context, path *tftypes.Attrib switch r := res.(type) { case attr.Type: return Attribute{}, ErrPathInsideAtomicAttribute - case fwschema.UnderlyingAttributes: + case types.UnderlyingAttributes: return Attribute{}, ErrPathInsideAtomicAttribute case fwschema.NestedBlock: return Attribute{}, ErrPathInsideAtomicAttribute - case fwschema.Attribute: + case types.Attribute: return r, nil case Block: return Attribute{}, ErrPathIsBlock @@ -257,8 +258,8 @@ func (s Schema) AttributeAtTerraformPath(_ context.Context, path *tftypes.Attrib } // schemaAttributes is a tfsdk to fwschema type conversion function. -func schemaAttributes(attributes map[string]Attribute) map[string]fwschema.Attribute { - result := make(map[string]fwschema.Attribute, len(attributes)) +func schemaAttributes(attributes map[string]Attribute) map[string]types.Attribute { + result := make(map[string]types.Attribute, len(attributes)) for name, attribute := range attributes { result[name] = attribute diff --git a/tfsdk/schema_test.go b/tfsdk/schema_test.go index 5af63077a..627e4b86b 100644 --- a/tfsdk/schema_test.go +++ b/tfsdk/schema_test.go @@ -7,12 +7,12 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestSchemaAttributeAtPath(t *testing.T) { @@ -21,7 +21,7 @@ func TestSchemaAttributeAtPath(t *testing.T) { testCases := map[string]struct { schema Schema path path.Path - expected fwschema.Attribute + expected types.Attribute expectedDiags diag.Diagnostics }{ "empty-root": { diff --git a/internal/fwschema/attribute.go b/types/attribute.go similarity index 99% rename from internal/fwschema/attribute.go rename to types/attribute.go index 81cbad5fc..2415f8130 100644 --- a/internal/fwschema/attribute.go +++ b/types/attribute.go @@ -1,8 +1,9 @@ -package fwschema +package types import ( - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" ) // Attribute is the core interface required for implementing Terraform schema diff --git a/types/list.go b/types/list.go index 8b3cb9e96..c5a1ad85e 100644 --- a/types/list.go +++ b/types/list.go @@ -352,6 +352,10 @@ type List struct { state valueState } +func (l List) ToFrameworkValue() attr.Value { + return l +} + // Elements returns the collection of elements for the List. Returns nil if the // List is null or unknown. func (l List) Elements() []attr.Value { diff --git a/internal/fwschema/nested_attributes.go b/types/nested_attributes.go similarity index 98% rename from internal/fwschema/nested_attributes.go rename to types/nested_attributes.go index c53f3324d..1350233ba 100644 --- a/internal/fwschema/nested_attributes.go +++ b/types/nested_attributes.go @@ -1,10 +1,10 @@ -package fwschema +package types import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -108,7 +108,7 @@ func (n UnderlyingAttributes) Type() attr.Type { for name, attr := range n { attrTypes[name] = attr.FrameworkType() } - return types.ObjectType{ + return ObjectType{ AttrTypes: attrTypes, } } @@ -219,7 +219,7 @@ func (l ListNestedAttributes) Equal(o NestedAttributes) bool { // Type returns the framework type of the nested attributes. func (l ListNestedAttributes) Type() attr.Type { - return types.ListType{ + return ListType{ ElemType: l.UnderlyingAttributes.Type(), } } @@ -272,7 +272,7 @@ func (s SetNestedAttributes) Equal(o NestedAttributes) bool { // Type returns the framework type of the nested attributes. func (s SetNestedAttributes) Type() attr.Type { - return types.SetType{ + return SetType{ ElemType: s.UnderlyingAttributes.Type(), } } @@ -325,7 +325,7 @@ func (m MapNestedAttributes) Equal(o NestedAttributes) bool { // Type returns the framework type of the nested attributes. func (m MapNestedAttributes) Type() attr.Type { - return types.MapType{ + return MapType{ ElemType: m.UnderlyingAttributes.Type(), } } diff --git a/internal/fwschema/nested_attributes_test.go b/types/nested_attributes_test.go similarity index 81% rename from internal/fwschema/nested_attributes_test.go rename to types/nested_attributes_test.go index 6f21823bc..079ed01ae 100644 --- a/internal/fwschema/nested_attributes_test.go +++ b/types/nested_attributes_test.go @@ -1,11 +1,11 @@ -package fwschema_test +package types_test import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -14,12 +14,12 @@ func TestListNestedAttributesType(t *testing.T) { t.Parallel() testCases := map[string]struct { - listNestedAttributes fwschema.ListNestedAttributes + listNestedAttributes types.ListNestedAttributes expected attr.Type }{ "tfsdk-attribute": { - listNestedAttributes: fwschema.ListNestedAttributes{ - UnderlyingAttributes: map[string]fwschema.Attribute{ + listNestedAttributes: types.ListNestedAttributes{ + UnderlyingAttributes: map[string]types.Attribute{ "test_nested_attribute": tfsdk.Attribute{ Required: true, Type: types.StringType, @@ -55,12 +55,12 @@ func TestMapNestedAttributesType(t *testing.T) { t.Parallel() testCases := map[string]struct { - mapNestedAttributes fwschema.MapNestedAttributes + mapNestedAttributes types.MapNestedAttributes expected attr.Type }{ "tfsdk-attribute": { - mapNestedAttributes: fwschema.MapNestedAttributes{ - UnderlyingAttributes: map[string]fwschema.Attribute{ + mapNestedAttributes: types.MapNestedAttributes{ + UnderlyingAttributes: map[string]types.Attribute{ "test_nested_attribute": tfsdk.Attribute{ Required: true, Type: types.StringType, @@ -96,12 +96,12 @@ func TestSetNestedAttributesType(t *testing.T) { t.Parallel() testCases := map[string]struct { - setNestedAttributes fwschema.SetNestedAttributes + setNestedAttributes types.SetNestedAttributes expected attr.Type }{ "tfsdk-attribute": { - setNestedAttributes: fwschema.SetNestedAttributes{ - UnderlyingAttributes: map[string]fwschema.Attribute{ + setNestedAttributes: types.SetNestedAttributes{ + UnderlyingAttributes: map[string]types.Attribute{ "test_nested_attribute": tfsdk.Attribute{ Required: true, Type: types.StringType, @@ -137,12 +137,12 @@ func TestSingleNestedAttributesType(t *testing.T) { t.Parallel() testCases := map[string]struct { - singleNestedAttributes fwschema.SingleNestedAttributes + singleNestedAttributes types.SingleNestedAttributes expected attr.Type }{ "tfsdk-attribute": { - singleNestedAttributes: fwschema.SingleNestedAttributes{ - UnderlyingAttributes: map[string]fwschema.Attribute{ + singleNestedAttributes: types.SingleNestedAttributes{ + UnderlyingAttributes: map[string]types.Attribute{ "test_nested_attribute": tfsdk.Attribute{ Required: true, Type: types.StringType, From 0bdbfa29173de26948cbde794c50a0220e2c193c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 31 Oct 2022 10:16:13 +0000 Subject: [PATCH 2/4] Adding FrameworkValue interface to types. and refactoring to remove unneeded assertions --- attr/value.go | 2 ++ internal/fwserver/attr_value.go | 14 +++----------- internal/fwserver/attribute_validation.go | 21 +++------------------ internal/testing/types/invalid.go | 7 ++++++- internal/testing/types/string.go | 7 ++++++- types/bool.go | 7 ++++++- types/float64.go | 7 ++++++- types/int64.go | 7 ++++++- types/map.go | 4 ++++ types/number.go | 7 ++++++- types/object.go | 4 ++++ types/set.go | 4 ++++ types/string.go | 7 ++++++- 13 files changed, 62 insertions(+), 36 deletions(-) diff --git a/attr/value.go b/attr/value.go index 14e3b0909..7c1c5f1e4 100644 --- a/attr/value.go +++ b/attr/value.go @@ -45,6 +45,8 @@ type Value interface { // logging and error reporting, as they are not protected by // compatibility guarantees within the framework. String() string + + FrameworkValue } type FrameworkValue interface { diff --git a/internal/fwserver/attr_value.go b/internal/fwserver/attr_value.go index 706b09f1b..80e6ea514 100644 --- a/internal/fwserver/attr_value.go +++ b/internal/fwserver/attr_value.go @@ -13,24 +13,16 @@ import ( ) func coerceListValue(schemaPath path.Path, value attr.Value) (types.List, diag.Diagnostics) { - // TODO: This assertion can be removed once attr.Value has been updated to include FrameworkValue interface for all attr.Value(s) - _, ok := value.(attr.FrameworkValue) - if !ok { - return types.List{Null: true}, diag.Diagnostics{ - attributePlanModificationWalkError(schemaPath, value), - } - } - - l := value.(attr.FrameworkValue).ToFrameworkValue() + v := value.ToFrameworkValue() - _, ok = l.(types.List) + l, ok := v.(types.List) if !ok { return types.List{Null: true}, diag.Diagnostics{ attributePlanModificationWalkError(schemaPath, value), } } - return l.(types.List), nil + return l, nil } func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Diagnostics) { diff --git a/internal/fwserver/attribute_validation.go b/internal/fwserver/attribute_validation.go index c67eeca46..fa903b72c 100644 --- a/internal/fwserver/attribute_validation.go +++ b/internal/fwserver/attribute_validation.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata" "github.com/hashicorp/terraform-plugin-framework/internal/logging" @@ -140,23 +139,9 @@ func AttributeValidateNestedAttributes(ctx context.Context, a types.Attribute, r nm := a.GetAttributes().GetNestingMode() switch nm { case types.NestingModeList: - // TODO: This assertion can be removed once attr.Value has been updated to include FrameworkValue interface for all attr.Value(s) - _, ok := req.AttributeConfig.(attr.FrameworkValue) + v := req.AttributeConfig.ToFrameworkValue() - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - l := req.AttributeConfig.(attr.FrameworkValue).ToFrameworkValue() - - _, ok = l.(types.List) + l, ok := v.(types.List) if !ok { err := fmt.Errorf("unknown framework value (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( @@ -168,7 +153,7 @@ func AttributeValidateNestedAttributes(ctx context.Context, a types.Attribute, r return } - for idx := range l.(types.List).Elements() { + for idx := range l.Elements() { for nestedName, nestedAttr := range a.GetAttributes().GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ AttributePath: req.AttributePath.AtListIndex(idx).AtName(nestedName), diff --git a/internal/testing/types/invalid.go b/internal/testing/types/invalid.go index 485cc82e0..fb23d8d01 100644 --- a/internal/testing/types/invalid.go +++ b/internal/testing/types/invalid.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" ) var ( @@ -46,6 +47,10 @@ func (t InvalidType) ValueType(_ context.Context) attr.Value { // Invalid is an attr.Value that returns errors for methods than can return errors. type Invalid struct{} +func (i Invalid) ToFrameworkValue() attr.Value { + return i +} + func (i Invalid) Equal(o attr.Value) bool { _, ok := o.(Invalid) diff --git a/internal/testing/types/string.go b/internal/testing/types/string.go index 56c4a69bd..fa6a4e8e4 100644 --- a/internal/testing/types/string.go +++ b/internal/testing/types/string.go @@ -4,9 +4,10 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( @@ -77,6 +78,10 @@ func (s String) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { return s.InternalString.ToTerraformValue(ctx) } +func (s String) ToFrameworkValue() attr.Value { + return s.InternalString +} + func (s String) Type(_ context.Context) attr.Type { return s.CreatedBy } diff --git a/types/bool.go b/types/bool.go index 924e98400..5cc9d85f5 100644 --- a/types/bool.go +++ b/types/bool.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" ) var ( @@ -114,6 +115,10 @@ type Bool struct { value bool } +func (b Bool) ToFrameworkValue() attr.Value { + return b +} + // Type returns a BoolType. func (b Bool) Type(_ context.Context) attr.Type { return BoolType diff --git a/types/float64.go b/types/float64.go index 04c1031f6..03aba2361 100644 --- a/types/float64.go +++ b/types/float64.go @@ -5,10 +5,11 @@ import ( "fmt" "math/big" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( @@ -175,6 +176,10 @@ type Float64 struct { value float64 } +func (f Float64) ToFrameworkValue() attr.Value { + return f +} + // Equal returns true if `other` is a Float64 and has the same value as `f`. func (f Float64) Equal(other attr.Value) bool { o, ok := other.(Float64) diff --git a/types/int64.go b/types/int64.go index 6d5d2f67c..2be6c783c 100644 --- a/types/int64.go +++ b/types/int64.go @@ -5,10 +5,11 @@ import ( "fmt" "math/big" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( @@ -188,6 +189,10 @@ type Int64 struct { value int64 } +func (i Int64) ToFrameworkValue() attr.Value { + return i +} + // Equal returns true if `other` is an Int64 and has the same value as `i`. func (i Int64) Equal(other attr.Value) bool { o, ok := other.(Int64) diff --git a/types/map.go b/types/map.go index 0b2319f03..96bd96d2d 100644 --- a/types/map.go +++ b/types/map.go @@ -357,6 +357,10 @@ type Map struct { state valueState } +func (m Map) ToFrameworkValue() attr.Value { + return m +} + // Elements returns the mapping of elements for the Map. Returns nil if the // Map is null or unknown. func (m Map) Elements() map[string]attr.Value { diff --git a/types/number.go b/types/number.go index 4e75fa1f9..af977db75 100644 --- a/types/number.go +++ b/types/number.go @@ -5,8 +5,9 @@ import ( "fmt" "math/big" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" ) var ( @@ -120,6 +121,10 @@ type Number struct { value *big.Float } +func (n Number) ToFrameworkValue() attr.Value { + return n +} + // Type returns a NumberType. func (n Number) Type(_ context.Context) attr.Type { return NumberType diff --git a/types/object.go b/types/object.go index 65f929714..2894f0746 100644 --- a/types/object.go +++ b/types/object.go @@ -377,6 +377,10 @@ type ObjectAsOptions struct { UnhandledUnknownAsEmpty bool } +func (o Object) ToFrameworkValue() attr.Value { + return o +} + // As populates `target` with the data in the Object, throwing an error if the // data cannot be stored in `target`. func (o Object) As(ctx context.Context, target interface{}, opts ObjectAsOptions) diag.Diagnostics { diff --git a/types/set.go b/types/set.go index fbfc5e66d..b0492cd91 100644 --- a/types/set.go +++ b/types/set.go @@ -384,6 +384,10 @@ type Set struct { state valueState } +func (s Set) ToFrameworkValue() attr.Value { + return s +} + // Elements returns the collection of elements for the Set. Returns nil if the // Set is null or unknown. func (s Set) Elements() []attr.Value { diff --git a/types/string.go b/types/string.go index 16c41ec45..022d0ce16 100644 --- a/types/string.go +++ b/types/string.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" ) var ( @@ -114,6 +115,10 @@ type String struct { value string } +func (s String) ToFrameworkValue() attr.Value { + return s +} + // Type returns a StringType. func (s String) Type(_ context.Context) attr.Type { return StringType From 225ade9ebcf780ae5401b1c1b27527df719dab3c Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 31 Oct 2022 12:31:20 +0000 Subject: [PATCH 3/4] Adding attribute validation tests for custom nested types --- internal/fwserver/attribute_validation.go | 9 +- .../fwserver/attribute_validation_test.go | 627 ++++++++++++++++++ 2 files changed, 633 insertions(+), 3 deletions(-) diff --git a/internal/fwserver/attribute_validation.go b/internal/fwserver/attribute_validation.go index fa903b72c..cf92200ac 100644 --- a/internal/fwserver/attribute_validation.go +++ b/internal/fwserver/attribute_validation.go @@ -170,8 +170,9 @@ func AttributeValidateNestedAttributes(ctx context.Context, a types.Attribute, r } } case types.NestingModeSet: - s, ok := req.AttributeConfig.(types.Set) + v := req.AttributeConfig.ToFrameworkValue() + s, ok := v.(types.Set) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( @@ -200,8 +201,9 @@ func AttributeValidateNestedAttributes(ctx context.Context, a types.Attribute, r } } case types.NestingModeMap: - m, ok := req.AttributeConfig.(types.Map) + v := req.AttributeConfig.ToFrameworkValue() + m, ok := v.(types.Map) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( @@ -230,8 +232,9 @@ func AttributeValidateNestedAttributes(ctx context.Context, a types.Attribute, r } } case types.NestingModeSingle: - o, ok := req.AttributeConfig.(types.Object) + v := req.AttributeConfig.ToFrameworkValue() + o, ok := v.(types.Object) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( diff --git a/internal/fwserver/attribute_validation_test.go b/internal/fwserver/attribute_validation_test.go index 423080c48..c878a4cbc 100644 --- a/internal/fwserver/attribute_validation_test.go +++ b/internal/fwserver/attribute_validation_test.go @@ -2,11 +2,13 @@ package fwserver import ( "context" + "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/path" @@ -14,6 +16,158 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +type ListNestedAttributesCustomType struct { + types.NestedAttributes +} + +func (t ListNestedAttributesCustomType) Type() attr.Type { + return ListNestedAttributesCustomTypeType{ + t.NestedAttributes.Type(), + } +} + +type ListNestedAttributesCustomTypeType struct { + attr.Type +} + +func (tt ListNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.Type.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + list, ok := val.(types.List) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.List", val) + } + + return ListNestedAttributesCustomValue{ + list, + }, nil +} + +type ListNestedAttributesCustomValue struct { + types.List +} + +func (v ListNestedAttributesCustomValue) ToFrameworkValue() attr.Value { + return v.List +} + +type MapNestedAttributesCustomType struct { + types.NestedAttributes +} + +func (t MapNestedAttributesCustomType) Type() attr.Type { + return MapNestedAttributesCustomTypeType{ + t.NestedAttributes.Type(), + } +} + +type MapNestedAttributesCustomTypeType struct { + attr.Type +} + +func (tt MapNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.Type.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + m, ok := val.(types.Map) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.Map", val) + } + + return MapNestedAttributesCustomValue{ + m, + }, nil +} + +type MapNestedAttributesCustomValue struct { + types.Map +} + +func (v MapNestedAttributesCustomValue) ToFrameworkValue() attr.Value { + return v.Map +} + +type SetNestedAttributesCustomType struct { + types.NestedAttributes +} + +func (t SetNestedAttributesCustomType) Type() attr.Type { + return SetNestedAttributesCustomTypeType{ + t.NestedAttributes.Type(), + } +} + +type SetNestedAttributesCustomTypeType struct { + attr.Type +} + +func (tt SetNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.Type.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + s, ok := val.(types.Set) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.Set", val) + } + + return SetNestedAttributesCustomValue{ + s, + }, nil +} + +type SetNestedAttributesCustomValue struct { + types.Set +} + +func (v SetNestedAttributesCustomValue) ToFrameworkValue() attr.Value { + return v.Set +} + +type SingleNestedAttributesCustomType struct { + types.NestedAttributes +} + +func (t SingleNestedAttributesCustomType) Type() attr.Type { + return SingleNestedAttributesCustomTypeType{ + t.NestedAttributes.Type(), + } +} + +type SingleNestedAttributesCustomTypeType struct { + attr.Type +} + +func (tt SingleNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.Type.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + s, ok := val.(types.Object) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.Object", val) + } + + return SingleNestedAttributesCustomValue{ + s, + }, nil +} + +type SingleNestedAttributesCustomValue struct { + types.Object +} + +func (v SingleNestedAttributesCustomValue) ToFrameworkValue() attr.Value { + return v.Object +} + func TestAttributeValidate(t *testing.T) { t.Parallel() @@ -729,6 +883,131 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-list-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: ListNestedAttributesCustomType{ + tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-custom-attr-list-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: ListNestedAttributesCustomType{ + tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, "nested-attr-map-no-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -850,6 +1129,131 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-map-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "testkey": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: MapNestedAttributesCustomType{ + tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-custom-attr-map-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "testkey": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: MapNestedAttributesCustomType{ + tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, "nested-attr-set-no-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -971,6 +1375,131 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-set-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: SetNestedAttributesCustomType{ + tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-custom-attr-set-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: SetNestedAttributesCustomType{ + tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, "nested-attr-single-no-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -1065,6 +1594,104 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-single-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: SingleNestedAttributesCustomType{ + tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-custom-attr-single-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: SingleNestedAttributesCustomType{ + tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, } for name, tc := range testCases { From e3f720a4f0ebb775da9172a071e9dd0f0e6117f5 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 31 Oct 2022 17:42:18 +0000 Subject: [PATCH 4/4] Adding attribute plan modification tests for custom nested types --- internal/fwserver/attr_value.go | 10 +- .../attribute_plan_modification_test.go | 352 ++++++++++++++++++ 2 files changed, 359 insertions(+), 3 deletions(-) diff --git a/internal/fwserver/attr_value.go b/internal/fwserver/attr_value.go index 80e6ea514..d5f913b3e 100644 --- a/internal/fwserver/attr_value.go +++ b/internal/fwserver/attr_value.go @@ -26,8 +26,9 @@ func coerceListValue(schemaPath path.Path, value attr.Value) (types.List, diag.D } func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Diagnostics) { - m, ok := value.(types.Map) + v := value.ToFrameworkValue() + m, ok := v.(types.Map) if !ok { return types.Map{Null: true}, diag.Diagnostics{ attributePlanModificationWalkError(schemaPath, value), @@ -38,8 +39,9 @@ func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Dia } func coerceObjectValue(schemaPath path.Path, value attr.Value) (types.Object, diag.Diagnostics) { - object, ok := value.(types.Object) + v := value.ToFrameworkValue() + object, ok := v.(types.Object) if !ok { return types.Object{Null: true}, diag.Diagnostics{ attributePlanModificationWalkError(schemaPath, value), @@ -50,7 +52,9 @@ func coerceObjectValue(schemaPath path.Path, value attr.Value) (types.Object, di } func coerceSetValue(schemaPath path.Path, value attr.Value) (types.Set, diag.Diagnostics) { - set, ok := value.(types.Set) + v := value.ToFrameworkValue() + + set, ok := v.(types.Set) if !ok { return types.Set{Null: true}, diag.Diagnostics{ diff --git a/internal/fwserver/attribute_plan_modification_test.go b/internal/fwserver/attribute_plan_modification_test.go index 0f6fa7399..9837058b1 100644 --- a/internal/fwserver/attribute_plan_modification_test.go +++ b/internal/fwserver/attribute_plan_modification_test.go @@ -199,6 +199,103 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-list-nested-custom": { + attribute: tfsdk.Attribute{ + Attributes: ListNestedAttributesCustomType{ + tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: ListNestedAttributesCustomValue{ + types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: ListNestedAttributesCustomValue{ + types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributeState: ListNestedAttributesCustomValue{ + types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("MODIFIED_TWO"), + }, + ), + }, + ), + Private: testEmptyProviderData, + }, + }, "attribute-set-nested-private": { attribute: tfsdk.Attribute{ Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ @@ -290,6 +387,103 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-custom-set-nested": { + attribute: tfsdk.Attribute{ + Attributes: SetNestedAttributesCustomType{ + tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: SetNestedAttributesCustomValue{ + types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: SetNestedAttributesCustomValue{ + types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributeState: SetNestedAttributesCustomValue{ + types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("MODIFIED_TWO"), + }, + ), + }, + ), + Private: testEmptyProviderData, + }, + }, "attribute-set-nested-usestateforunknown": { attribute: tfsdk.Attribute{ Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ @@ -525,6 +719,103 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-custom-map-nested": { + attribute: tfsdk.Attribute{ + Attributes: MapNestedAttributesCustomType{ + tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: MapNestedAttributesCustomValue{ + types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: MapNestedAttributesCustomValue{ + types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributeState: MapNestedAttributesCustomValue{ + types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("MODIFIED_TWO"), + }, + ), + }, + ), + Private: testEmptyProviderData, + }, + }, "attribute-single-nested-private": { attribute: tfsdk.Attribute{ Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ @@ -580,6 +871,67 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-custom-single-nested": { + attribute: tfsdk.Attribute{ + Attributes: SingleNestedAttributesCustomType{ + tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "testing": { + Type: types.StringType, + Optional: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: SingleNestedAttributesCustomValue{ + types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("TESTATTRONE"), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: SingleNestedAttributesCustomValue{ + types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("TESTATTRONE"), + }, + ), + }, + AttributeState: SingleNestedAttributesCustomValue{ + types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("TESTATTRONE"), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("MODIFIED_TWO"), + }, + ), + Private: testEmptyProviderData, + }, + }, "requires-replacement": { attribute: tfsdk.Attribute{ Type: types.StringType,