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 5, 2022
1 parent 79303b8 commit cf19ec1
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 36 deletions.
35 changes: 20 additions & 15 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,7 +76,7 @@ func listElemObject(ctx context.Context, schemaPath path.Path, list types.List,
return coerceObjectValue(schemaPath, list.Elems[index])
}

func listElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, list types.List, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) {
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 {
Expand All @@ -87,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 @@ -105,7 +106,7 @@ 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) {
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 {
Expand All @@ -117,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 @@ -126,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.Attrs[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.AttrTypes[attributeName]
attrTypes := object.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes()
attrType := attrTypes[attributeName]

elemValue, err := attrType.ValueFromTerraform(ctx, tftypes.NewValue(attrType.TerraformType(ctx), tfValue))

Expand All @@ -147,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 @@ -163,7 +168,7 @@ func setElemObject(ctx context.Context, schemaPath path.Path, set types.Set, ind
return coerceObjectValue(schemaPath, set.Elems[index])
}

func setElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, set types.Set, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) {
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 {
Expand Down
18 changes: 13 additions & 5 deletions internal/fwserver/attribute_plan_modification.go
Expand Up @@ -143,6 +143,8 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo
return
}

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 @@ -187,7 +189,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[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 @@ -249,6 +251,8 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo
return
}

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 @@ -293,7 +297,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[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 @@ -355,6 +359,8 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo
return
}

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 @@ -399,7 +405,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[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 @@ -434,7 +440,9 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo
return
}

if len(planObject.Attrs) == 0 {
planObjectAttrs := planObject.Type(ctx).(attr.TypeWithAttributeTypes).AttributeTypes()

if len(planObjectAttrs) == 0 {
return
}

Expand Down Expand Up @@ -482,7 +490,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[name] = attrResp.AttributePlan
planObjectAttrs[name] = attrResp.AttributePlan.Type(ctx)
resp.Diagnostics.Append(attrResp.Diagnostics...)
resp.RequiresReplace = attrResp.RequiresReplace
resp.Private = attrResp.Private
Expand Down
22 changes: 14 additions & 8 deletions internal/fwserver/block_plan_modification.go
Expand Up @@ -109,6 +109,8 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr
return
}

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 @@ -153,7 +155,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[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 @@ -203,7 +205,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr

BlockModifyPlan(ctx, block, blockReq, &blockResp)

planObject.Attrs[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 @@ -265,6 +267,8 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr
return
}

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 @@ -309,7 +313,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[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 @@ -359,7 +363,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr

BlockModifyPlan(ctx, block, blockReq, &blockResp)

planObject.Attrs[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 @@ -394,10 +398,12 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr
return
}

if planObject.Attrs == nil {
planObject.Attrs = 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 @@ -442,7 +448,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr

AttributeModifyPlan(ctx, attr, attrReq, &attrResp)

planObject.Attrs[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 @@ -492,7 +498,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr

BlockModifyPlan(ctx, block, blockReq, &blockResp)

planObject.Attrs[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.Elems)))
}
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 cf19ec1

Please sign in to comment.