From 6f0b6bc323b4795e6d043cb6a1dd95306a25f5b0 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 4 Oct 2022 16:16:57 +0100 Subject: [PATCH] Implementing custom type for blocks (#508) --- internal/fwserver/attr_value.go | 44 ++++++++++--------- .../fwserver/attribute_plan_modification.go | 18 ++++---- internal/fwserver/block_plan_modification.go | 24 +++++----- internal/fwserver/block_validation.go | 6 +-- tfsdk/block.go | 20 ++++++--- 5 files changed, 63 insertions(+), 49 deletions(-) diff --git a/internal/fwserver/attr_value.go b/internal/fwserver/attr_value.go index 70731345d..93b7c790d 100644 --- a/internal/fwserver/attr_value.go +++ b/internal/fwserver/attr_value.go @@ -3,12 +3,13 @@ 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) { @@ -35,16 +36,16 @@ func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Dia return m, nil } -func coerceObjectValue(schemaPath path.Path, value attr.Value) (types.Object, diag.Diagnostics) { - object, ok := value.(types.Object) +func coerceObjectValue(schemaPath path.Path, value attr.Value) (attr.Value, diag.Diagnostics) { + object, ok := value.Type(context.Background()).(attr.TypeWithAttributeTypes) if !ok { - return types.Object{Null: true}, diag.Diagnostics{ + return object.ValueType(context.Background()), diag.Diagnostics{ attributePlanModificationWalkError(schemaPath, value), } } - return object, nil + return value, nil } func coerceSetValue(schemaPath path.Path, value attr.Value) (types.Set, diag.Diagnostics) { @@ -59,7 +60,7 @@ func coerceSetValue(schemaPath path.Path, value attr.Value) (types.Set, diag.Dia return set, nil } -func listElemObject(ctx context.Context, schemaPath path.Path, list types.List, index int, description fwschemadata.DataDescription) (types.Object, diag.Diagnostics) { +func listElemObject(ctx context.Context, schemaPath path.Path, list types.List, index int, description fwschemadata.DataDescription) (attr.Value, diag.Diagnostics) { if list.IsNull() { return listElemObjectFromTerraformValue(ctx, schemaPath, list, description, nil) } @@ -75,9 +76,8 @@ func listElemObject(ctx context.Context, schemaPath path.Path, list types.List, return coerceObjectValue(schemaPath, list.Elements()[index]) } -func listElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, list types.List, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) { - elemType := list.ElementType(ctx) - elemValue, err := elemType.ValueFromTerraform(ctx, tftypes.NewValue(elemType.TerraformType(ctx), tfValue)) +func listElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, list types.List, description fwschemadata.DataDescription, tfValue any) (attr.Value, diag.Diagnostics) { + elemValue, err := list.ElemType.ValueFromTerraform(ctx, tftypes.NewValue(list.ElemType.TerraformType(ctx), tfValue)) if err != nil { return types.Object{Null: true}, diag.Diagnostics{ @@ -88,7 +88,7 @@ func listElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, return coerceObjectValue(schemaPath, elemValue) } -func mapElemObject(ctx context.Context, schemaPath path.Path, m types.Map, key string, description fwschemadata.DataDescription) (types.Object, diag.Diagnostics) { +func mapElemObject(ctx context.Context, schemaPath path.Path, m types.Map, key string, description fwschemadata.DataDescription) (attr.Value, diag.Diagnostics) { if m.IsNull() { return mapElemObjectFromTerraformValue(ctx, schemaPath, m, description, nil) } @@ -106,9 +106,8 @@ func mapElemObject(ctx context.Context, schemaPath path.Path, m types.Map, key s return coerceObjectValue(schemaPath, elemValue) } -func mapElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, m types.Map, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) { - elemType := m.ElementType(ctx) - elemValue, err := elemType.ValueFromTerraform(ctx, tftypes.NewValue(elemType.TerraformType(ctx), tfValue)) +func mapElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, m types.Map, description fwschemadata.DataDescription, tfValue any) (attr.Value, diag.Diagnostics) { + elemValue, err := m.ElemType.ValueFromTerraform(ctx, tftypes.NewValue(m.ElemType.TerraformType(ctx), tfValue)) if err != nil { return types.Object{Null: true}, diag.Diagnostics{ @@ -119,7 +118,7 @@ func mapElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, return coerceObjectValue(schemaPath, elemValue) } -func objectAttributeValue(ctx context.Context, object types.Object, attributeName string, description fwschemadata.DataDescription) (attr.Value, diag.Diagnostics) { +func objectAttributeValue(ctx context.Context, object attr.Value, attributeName string, description fwschemadata.DataDescription) (attr.Value, diag.Diagnostics) { if object.IsNull() { return objectAttributeValueFromTerraformValue(ctx, object, attributeName, description, nil) } @@ -128,15 +127,19 @@ func objectAttributeValue(ctx context.Context, object types.Object, attributeNam return objectAttributeValueFromTerraformValue(ctx, object, attributeName, description, tftypes.UnknownValue) } + attrTypes := object.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() + attrType := attrTypes[attributeName] + // A panic here indicates a bug somewhere else in the framework or an // invalid test case. - return object.Attributes()[attributeName], nil + return attrType.ValueType(ctx), nil } -func objectAttributeValueFromTerraformValue(ctx context.Context, object types.Object, attributeName string, description fwschemadata.DataDescription, tfValue any) (attr.Value, diag.Diagnostics) { +func objectAttributeValueFromTerraformValue(ctx context.Context, object attr.Value, attributeName string, description fwschemadata.DataDescription, tfValue any) (attr.Value, diag.Diagnostics) { // A panic here indicates a bug somewhere else in the framework or an // invalid test case. - attrType := object.AttributeTypes(ctx)[attributeName] + attrTypes := object.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() + attrType := attrTypes[attributeName] elemValue, err := attrType.ValueFromTerraform(ctx, tftypes.NewValue(attrType.TerraformType(ctx), tfValue)) @@ -149,7 +152,7 @@ func objectAttributeValueFromTerraformValue(ctx context.Context, object types.Ob return elemValue, nil } -func setElemObject(ctx context.Context, schemaPath path.Path, set types.Set, index int, description fwschemadata.DataDescription) (types.Object, diag.Diagnostics) { +func setElemObject(ctx context.Context, schemaPath path.Path, set types.Set, index int, description fwschemadata.DataDescription) (attr.Value, diag.Diagnostics) { if set.IsNull() { return setElemObjectFromTerraformValue(ctx, schemaPath, set, description, nil) } @@ -165,9 +168,8 @@ func setElemObject(ctx context.Context, schemaPath path.Path, set types.Set, ind return coerceObjectValue(schemaPath, set.Elements()[index]) } -func setElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, set types.Set, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) { - elemType := set.ElementType(ctx) - elemValue, err := elemType.ValueFromTerraform(ctx, tftypes.NewValue(elemType.TerraformType(ctx), tfValue)) +func setElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, set types.Set, description fwschemadata.DataDescription, tfValue any) (attr.Value, diag.Diagnostics) { + elemValue, err := set.ElemType.ValueFromTerraform(ctx, tftypes.NewValue(set.ElemType.TerraformType(ctx), tfValue)) if err != nil { return types.Object{Null: true}, diag.Diagnostics{ diff --git a/internal/fwserver/attribute_plan_modification.go b/internal/fwserver/attribute_plan_modification.go index c28f77f20..143f64a38 100644 --- a/internal/fwserver/attribute_plan_modification.go +++ b/internal/fwserver/attribute_plan_modification.go @@ -151,7 +151,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planAttributes := planObject.Attributes() + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() for name, attr := range a.GetAttributes().GetAttributes() { attrConfig, diags := objectAttributeValue(ctx, configObject, name, fwschemadata.DataDescriptionConfiguration) @@ -197,7 +197,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private @@ -273,7 +273,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planAttributes := planObject.Attributes() + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() for name, attr := range a.GetAttributes().GetAttributes() { attrConfig, diags := objectAttributeValue(ctx, configObject, name, fwschemadata.DataDescriptionConfiguration) @@ -319,7 +319,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private @@ -395,7 +395,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planAttributes := planObject.Attributes() + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() for name, attr := range a.GetAttributes().GetAttributes() { attrConfig, diags := objectAttributeValue(ctx, configObject, name, fwschemadata.DataDescriptionConfiguration) @@ -441,7 +441,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private @@ -488,7 +488,9 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - if len(planObject.Attributes()) == 0 { + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() + + if len(planObjectAttrs) == 0 { return } @@ -538,7 +540,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private diff --git a/internal/fwserver/block_plan_modification.go b/internal/fwserver/block_plan_modification.go index 453ba7add..aff1ab35d 100644 --- a/internal/fwserver/block_plan_modification.go +++ b/internal/fwserver/block_plan_modification.go @@ -117,7 +117,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planAttributes := planObject.Attributes() + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() for name, attr := range b.GetAttributes() { attrConfig, diags := objectAttributeValue(ctx, configObject, name, fwschemadata.DataDescriptionConfiguration) @@ -163,7 +163,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private @@ -213,7 +213,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr BlockModifyPlan(ctx, block, blockReq, &blockResp) - planAttributes[name] = blockResp.AttributePlan + planObjectAttrs[name] = blockResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(blockResp.Diagnostics...) resp.RequiresReplace = blockResp.RequiresReplace resp.Private = blockResp.Private @@ -289,7 +289,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planAttributes := planObject.Attributes() + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() for name, attr := range b.GetAttributes() { attrConfig, diags := objectAttributeValue(ctx, configObject, name, fwschemadata.DataDescriptionConfiguration) @@ -335,7 +335,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private @@ -385,7 +385,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr BlockModifyPlan(ctx, block, blockReq, &blockResp) - planAttributes[name] = blockResp.AttributePlan + planObjectAttrs[name] = blockResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(blockResp.Diagnostics...) resp.RequiresReplace = blockResp.RequiresReplace resp.Private = blockResp.Private @@ -432,12 +432,12 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planAttributes := planObject.Attributes() - - if planAttributes == nil { - planAttributes = make(map[string]attr.Value) + if planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() == nil { + planObject.Type(ctx).(attr.TypeWithAttributeTypes).WithAttributeTypes(make(map[string]attr.Type)) } + planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes() + for name, attr := range b.GetAttributes() { attrConfig, diags := objectAttributeValue(ctx, configObject, name, fwschemadata.DataDescriptionConfiguration) @@ -482,7 +482,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr AttributeModifyPlan(ctx, attr, attrReq, &attrResp) - planAttributes[name] = attrResp.AttributePlan + planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(attrResp.Diagnostics...) resp.RequiresReplace = attrResp.RequiresReplace resp.Private = attrResp.Private @@ -532,7 +532,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr BlockModifyPlan(ctx, block, blockReq, &blockResp) - planAttributes[name] = blockResp.AttributePlan + planObjectAttrs[name] = blockResp.AttributePlan.Type(ctx) resp.Diagnostics.Append(blockResp.Diagnostics...) resp.RequiresReplace = blockResp.RequiresReplace resp.Private = blockResp.Private diff --git a/internal/fwserver/block_validation.go b/internal/fwserver/block_validation.go index 77d929a86..5496dd1f0 100644 --- a/internal/fwserver/block_validation.go +++ b/internal/fwserver/block_validation.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "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" @@ -185,8 +186,7 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req tfsdk.ValidateAttr resp.Diagnostics.Append(blockMinItemsDiagnostic(req.AttributePath, b.GetMinItems(), len(s.Elements()))) } case fwschema.BlockNestingModeSingle: - s, ok := req.AttributeConfig.(types.Object) - + _, ok := req.AttributeConfig.Type(ctx).(attr.TypeWithAttributeTypes) if !ok { err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) resp.Diagnostics.AddAttributeError( @@ -228,7 +228,7 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req tfsdk.ValidateAttr resp.Diagnostics = nestedAttrResp.Diagnostics } - if b.GetMinItems() == 1 && s.IsNull() { + if b.GetMinItems() == 1 && req.AttributeConfig.IsNull() { resp.Diagnostics.Append(blockMinItemsDiagnostic(req.AttributePath, b.GetMinItems(), 0)) } default: diff --git a/tfsdk/block.go b/tfsdk/block.go index 187a01052..415cee0a4 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{} @@ -24,6 +25,8 @@ var _ fwschema.Block = Block{} // The NestingMode field must be set or a runtime error will be raised by the // framework when fetching the schema. type Block struct { + Typ attr.TypeWithAttributeTypes + // Attributes are value fields inside the block. This map of attributes // behaves exactly like the map of attributes on the Schema type. Attributes map[string]Attribute @@ -210,18 +213,25 @@ func (b Block) GetValidators() []AttributeValidator { // attributeType returns an attr.Type corresponding to the block. func (b Block) Type() attr.Type { - attrType := types.ObjectType{ - AttrTypes: map[string]attr.Type{}, + var attrType attr.TypeWithAttributeTypes + attrType = types.ObjectType{} + + if b.Typ != nil { + attrType = b.Typ } + attrTypes := make(map[string]attr.Type, len(b.Attributes)+len(b.Blocks)) + for attrName, attr := range b.Attributes { - attrType.AttrTypes[attrName] = attr.FrameworkType() + attrTypes[attrName] = attr.FrameworkType() } for blockName, block := range b.Blocks { - attrType.AttrTypes[blockName] = block.Type() + attrTypes[blockName] = block.Type() } + attrType = attrType.WithAttributeTypes(attrTypes) + switch b.NestingMode { case BlockNestingModeList: return types.ListType{