Skip to content

Commit

Permalink
Add 'fwplanmodifiers.NormalizeEmptyMap'.
Browse files Browse the repository at this point in the history
  • Loading branch information
ewbankkit committed Nov 4, 2022
1 parent 2fee384 commit 2e2c566
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
60 changes: 60 additions & 0 deletions internal/fwplanmodifiers/normalize_empty_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package fwplanmodifiers

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type normalizeEmptyMap struct{}

// NormalizeEmptyMap return an AttributePlanModifier that normalizes a missing Map.
// Useful for resolving null vs. empty differences for resource tags.
func NormalizeEmptyMap() tfsdk.AttributePlanModifier {
return normalizeEmptyMap{}
}

func (m normalizeEmptyMap) Description(context.Context) string {
return "Resolve differences between null and empty maps"
}

func (m normalizeEmptyMap) MarkdownDescription(ctx context.Context) string {
return m.Description(ctx)
}

func (m normalizeEmptyMap) Modify(ctx context.Context, request tfsdk.ModifyAttributePlanRequest, response *tfsdk.ModifyAttributePlanResponse) {
if request.AttributeState == nil {
response.AttributePlan = request.AttributePlan

return
}

// If the current value is semantically equivalent to the planned value
// then return the current value, else return the planned value.

var planned types.Map

response.Diagnostics = append(response.Diagnostics, tfsdk.ValueAs(ctx, request.AttributePlan, &planned)...)

if response.Diagnostics.HasError() {
return
}

var current types.Map

response.Diagnostics = append(response.Diagnostics, tfsdk.ValueAs(ctx, request.AttributeState, &current)...)

if response.Diagnostics.HasError() {
return
}

if planned.IsNull() && (current.IsNull() || len(current.Elems) == 0) ||
(current.IsNull() && (planned.IsNull() || len(planned.Elems) == 0)) {
response.AttributePlan = request.AttributeState

return
}

response.AttributePlan = request.AttributePlan
}
100 changes: 100 additions & 0 deletions internal/fwplanmodifiers/normalize_empty_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package fwplanmodifiers

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestNormalizeEmptyMap(t *testing.T) {
t.Parallel()

type testCase struct {
plannedValue attr.Value
currentValue attr.Value
expectedValue attr.Value
expectError bool
}
tests := map[string]testCase{
"planned non-empty Map, current non-empty Map": {
plannedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{
"2": types.String{Value: "TWO"},
}},
currentValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{
"1": types.String{Value: "ONE"},
}},
expectedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{
"2": types.String{Value: "TWO"},
}},
},
"planned non-empty Map, current Null Map": {
plannedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{
"2": types.String{Value: "TWO"},
}},
currentValue: types.Map{ElemType: types.StringType, Null: true},
expectedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{
"2": types.String{Value: "TWO"},
}},
},
"planned Null Map, current non-empty Map": {
plannedValue: types.Map{ElemType: types.StringType, Null: true},
currentValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{
"1": types.String{Value: "ONE"},
}},
expectedValue: types.Map{ElemType: types.StringType, Null: true},
},
"planned empty Map, current Null Map": {
plannedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{}},
currentValue: types.Map{ElemType: types.StringType, Null: true},
expectedValue: types.Map{ElemType: types.StringType, Null: true},
},
"planned Null Map, current empty Map": {
plannedValue: types.Map{ElemType: types.StringType, Null: true},
currentValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{}},
expectedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{}},
},
"planned empty Map, current empty Map": {
plannedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{}},
currentValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{}},
expectedValue: types.Map{ElemType: types.StringType, Elems: map[string]attr.Value{}},
},
"planned Null Map, current Null Map": {
plannedValue: types.Map{ElemType: types.StringType, Null: true},
currentValue: types.Map{ElemType: types.StringType, Null: true},
expectedValue: types.Map{ElemType: types.StringType, Null: true},
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
ctx := context.Background()
request := tfsdk.ModifyAttributePlanRequest{
AttributePath: path.Root("test"),
AttributePlan: test.plannedValue,
AttributeState: test.currentValue,
}
response := tfsdk.ModifyAttributePlanResponse{
AttributePlan: request.AttributePlan,
}
NormalizeEmptyMap().Modify(ctx, request, &response)

if !response.Diagnostics.HasError() && test.expectError {
t.Fatal("expected error, got no error")
}

if response.Diagnostics.HasError() && !test.expectError {
t.Fatalf("got unexpected error: %s", response.Diagnostics)
}

if diff := cmp.Diff(response.AttributePlan, test.expectedValue); diff != "" {
t.Errorf("unexpected diff (+wanted, -got): %s", diff)
}
})
}
}

0 comments on commit 2e2c566

Please sign in to comment.