Skip to content

Commit

Permalink
Implementing custom type for blocks (#508)
Browse files Browse the repository at this point in the history
  • Loading branch information
bendbennett committed Oct 28, 2022
1 parent 1ee1a4f commit 6f0b6bc
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 49 deletions.
44 changes: 23 additions & 21 deletions internal/fwserver/attr_value.go
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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)
}
Expand All @@ -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{
Expand All @@ -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)
}
Expand All @@ -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{
Expand All @@ -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)
}
Expand All @@ -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))

Expand All @@ -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)
}
Expand All @@ -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{
Expand Down
18 changes: 10 additions & 8 deletions internal/fwserver/attribute_plan_modification.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
24 changes: 12 additions & 12 deletions internal/fwserver/block_plan_modification.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions internal/fwserver/block_validation.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
20 changes: 15 additions & 5 deletions tfsdk/block.go
Expand Up @@ -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{}
Expand All @@ -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
Expand Down Expand Up @@ -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{
Expand Down

0 comments on commit 6f0b6bc

Please sign in to comment.