Skip to content

Commit

Permalink
internal/toproto*: Convert null list/set block values to empty (#621)
Browse files Browse the repository at this point in the history
Reference: #604
Reference: #620

This logic implements the inverse of what was added to `internal/fromproto*` where empty block values are converted to null. While Terraform implements some logic to automatically convert these for state responses from `ReadResource` and others, the same null values can cause errors with Terraform's plan validity logic.

This fixes the potential new error caused by only introducing the `internal/fromproto*` logic, which was not released and why it does not contain a changelog entry in that regard, However, this change is considered an overall enhancement prior and including those changes since it means the provider developers will no longer be burdened with understanding Terraform's implementation details with null versus empty collection blocks when setting values for responses.
  • Loading branch information
bflad authored Jan 13, 2023
1 parent beaf82a commit 620823f
Show file tree
Hide file tree
Showing 32 changed files with 3,579 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .changelog/621.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
tfsdk: Automatically prevented Terraform `nested blocks must be empty to indicate no blocks` errors for responses containing `Plan` and `State` types
```
2 changes: 0 additions & 2 deletions internal/fromproto5/dynamic_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
// developers from needing to understand Terraform's differences between
// block and attribute values where blocks are technically never null, but from
// a developer perspective this distinction introduces unnecessary complexity.
// This null block value translation is automatically handled by Terraform from
// provider responses.
func DynamicValue(ctx context.Context, proto5 *tfprotov5.DynamicValue, schema fwschema.Schema, description fwschemadata.DataDescription) (fwschemadata.Data, diag.Diagnostics) {
var diags diag.Diagnostics

Expand Down
2 changes: 0 additions & 2 deletions internal/fromproto6/dynamic_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
// developers from needing to understand Terraform's differences between
// block and attribute values where blocks are technically never null, but from
// a developer perspective this distinction introduces unnecessary complexity.
// This null block value translation is automatically handled by Terraform from
// provider responses.
func DynamicValue(ctx context.Context, proto6 *tfprotov6.DynamicValue, schema fwschema.Schema, description fwschemadata.DataDescription) (fwschemadata.Data, diag.Diagnostics) {
var diags diag.Diagnostics

Expand Down
2 changes: 1 addition & 1 deletion internal/fwschemadata/data_nullify_collection_blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// NullifyCollectionBlocks converts list and set block empty values to null
// values.
// values. The reverse conversion is ReifyNullCollectionBlocks.
func (d *Data) NullifyCollectionBlocks(ctx context.Context) diag.Diagnostics {
var diags diag.Diagnostics

Expand Down
46 changes: 46 additions & 0 deletions internal/fwschemadata/data_reify_null_collection_blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package fwschemadata

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// ReifyNullCollectionBlocks converts list and set block null values to empty
// values. This is the reverse conversion of NullifyCollectionBlocks.
func (d *Data) ReifyNullCollectionBlocks(ctx context.Context) diag.Diagnostics {
var diags diag.Diagnostics

blockPathExpressions := fwschema.SchemaBlockPathExpressions(ctx, d.Schema)

// Errors are handled as richer diag.Diagnostics instead.
d.TerraformValue, _ = tftypes.Transform(d.TerraformValue, func(tfTypePath *tftypes.AttributePath, tfTypeValue tftypes.Value) (tftypes.Value, error) {
// Only transform null values.
if !tfTypeValue.IsNull() {
return tfTypeValue, nil
}

fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, d.Schema)

diags.Append(fwPathDiags...)

// Do not transform if path cannot be converted.
// Checking against fwPathDiags will capture all errors.
if fwPathDiags.HasError() {
return tfTypeValue, nil
}

// Do not transform if path is not a block.
if !blockPathExpressions.Matches(fwPath) {
return tfTypeValue, nil
}

// Transform to empty value.
return tftypes.NewValue(tfTypeValue.Type(), []tftypes.Value{}), nil
})

return diags
}
Loading

0 comments on commit 620823f

Please sign in to comment.