diff --git a/.changelog/440.txt b/.changelog/440.txt new file mode 100644 index 000000000..f7b796d51 --- /dev/null +++ b/.changelog/440.txt @@ -0,0 +1,15 @@ +```release-note:note +tfsdk: The `Schema` type `AttributeAtPath()` method signature will be updated from a `*tftypes.AttributePath` parameter to `path.Path` in the next release. Switch to the `AttributeAtTerraformPath()` method if `*tftypes.AttributePath` handling is still necessary. +``` + +```release-note:note +tfsdk: The `Schema` type `AttributeTypeAtPath()` method has been deprecated for the `TypeAtPath()` and `TypeAtTerraformPath()` methods. +``` + +```release-note:note +tfsdk: The `Schema` type `AttributeType()` method has been deprecated in preference of the `Type()` method. +``` + +```release-note:note +tfsdk: The `Schema` type `TerraformType()` method has been deprecated in preference of calling `Type().TerraformType()`. +``` diff --git a/internal/fromproto5/config.go b/internal/fromproto5/config.go index 3702ade6a..8b29ba70e 100644 --- a/internal/fromproto5/config.go +++ b/internal/fromproto5/config.go @@ -32,7 +32,7 @@ func Config(ctx context.Context, proto5DynamicValue *tfprotov5.DynamicValue, sch return nil, diags } - proto5Value, err := proto5DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto5Value, err := proto5DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto5/importresourcestate.go b/internal/fromproto5/importresourcestate.go index e1b061bef..adaf9da8c 100644 --- a/internal/fromproto5/importresourcestate.go +++ b/internal/fromproto5/importresourcestate.go @@ -37,7 +37,7 @@ func ImportResourceStateRequest(ctx context.Context, proto5 *tfprotov5.ImportRes fw := &fwserver.ImportResourceStateRequest{ EmptyState: tfsdk.State{ - Raw: tftypes.NewValue(resourceSchema.TerraformType(ctx), nil), + Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil), Schema: tfsdkSchema(resourceSchema), }, ID: proto5.ID, diff --git a/internal/fromproto5/importresourcestate_test.go b/internal/fromproto5/importresourcestate_test.go index ca3ee865b..788a52aa7 100644 --- a/internal/fromproto5/importresourcestate_test.go +++ b/internal/fromproto5/importresourcestate_test.go @@ -29,7 +29,7 @@ func TestImportResourceStateRequest(t *testing.T) { } testFwEmptyState := tfsdk.State{ - Raw: tftypes.NewValue(testFwSchema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(testFwSchema.Type().TerraformType(context.Background()), nil), Schema: *testFwSchema, } diff --git a/internal/fromproto5/plan.go b/internal/fromproto5/plan.go index 9237a5c14..c7607a656 100644 --- a/internal/fromproto5/plan.go +++ b/internal/fromproto5/plan.go @@ -32,7 +32,7 @@ func Plan(ctx context.Context, proto5DynamicValue *tfprotov5.DynamicValue, schem return nil, diags } - proto5Value, err := proto5DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto5Value, err := proto5DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto5/providermeta.go b/internal/fromproto5/providermeta.go index 23832ea60..dd0c1465d 100644 --- a/internal/fromproto5/providermeta.go +++ b/internal/fromproto5/providermeta.go @@ -24,7 +24,7 @@ func ProviderMeta(ctx context.Context, proto5DynamicValue *tfprotov5.DynamicValu var diags diag.Diagnostics fw := &tfsdk.Config{ - Raw: tftypes.NewValue(schema.TerraformType(ctx), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(ctx), nil), Schema: tfsdkSchema(schema), } @@ -32,7 +32,7 @@ func ProviderMeta(ctx context.Context, proto5DynamicValue *tfprotov5.DynamicValu return fw, nil } - proto5Value, err := proto5DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto5Value, err := proto5DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto5/state.go b/internal/fromproto5/state.go index 0253b9f24..bd9b8d633 100644 --- a/internal/fromproto5/state.go +++ b/internal/fromproto5/state.go @@ -32,7 +32,7 @@ func State(ctx context.Context, proto5DynamicValue *tfprotov5.DynamicValue, sche return nil, diags } - proto5Value, err := proto5DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto5Value, err := proto5DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto6/config.go b/internal/fromproto6/config.go index 99be4283c..90ce2900c 100644 --- a/internal/fromproto6/config.go +++ b/internal/fromproto6/config.go @@ -32,7 +32,7 @@ func Config(ctx context.Context, proto6DynamicValue *tfprotov6.DynamicValue, sch return nil, diags } - proto6Value, err := proto6DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto6Value, err := proto6DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto6/importresourcestate.go b/internal/fromproto6/importresourcestate.go index 09266037d..43cd03477 100644 --- a/internal/fromproto6/importresourcestate.go +++ b/internal/fromproto6/importresourcestate.go @@ -37,7 +37,7 @@ func ImportResourceStateRequest(ctx context.Context, proto6 *tfprotov6.ImportRes fw := &fwserver.ImportResourceStateRequest{ EmptyState: tfsdk.State{ - Raw: tftypes.NewValue(resourceSchema.TerraformType(ctx), nil), + Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil), Schema: tfsdkSchema(resourceSchema), }, ID: proto6.ID, diff --git a/internal/fromproto6/importresourcestate_test.go b/internal/fromproto6/importresourcestate_test.go index 8b5b8e71b..09061d6a3 100644 --- a/internal/fromproto6/importresourcestate_test.go +++ b/internal/fromproto6/importresourcestate_test.go @@ -29,7 +29,7 @@ func TestImportResourceStateRequest(t *testing.T) { } testFwEmptyState := tfsdk.State{ - Raw: tftypes.NewValue(testFwSchema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(testFwSchema.Type().TerraformType(context.Background()), nil), Schema: *testFwSchema, } diff --git a/internal/fromproto6/plan.go b/internal/fromproto6/plan.go index 15848660f..04d110d0a 100644 --- a/internal/fromproto6/plan.go +++ b/internal/fromproto6/plan.go @@ -32,7 +32,7 @@ func Plan(ctx context.Context, proto6DynamicValue *tfprotov6.DynamicValue, schem return nil, diags } - proto6Value, err := proto6DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto6Value, err := proto6DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto6/providermeta.go b/internal/fromproto6/providermeta.go index e28a63f87..7e06e11ec 100644 --- a/internal/fromproto6/providermeta.go +++ b/internal/fromproto6/providermeta.go @@ -24,7 +24,7 @@ func ProviderMeta(ctx context.Context, proto6DynamicValue *tfprotov6.DynamicValu var diags diag.Diagnostics fw := &tfsdk.Config{ - Raw: tftypes.NewValue(schema.TerraformType(ctx), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(ctx), nil), Schema: tfsdkSchema(schema), } @@ -32,7 +32,7 @@ func ProviderMeta(ctx context.Context, proto6DynamicValue *tfprotov6.DynamicValu return fw, nil } - proto6Value, err := proto6DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto6Value, err := proto6DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/internal/fromproto6/state.go b/internal/fromproto6/state.go index 0b56e620b..b8b6f1191 100644 --- a/internal/fromproto6/state.go +++ b/internal/fromproto6/state.go @@ -32,7 +32,7 @@ func State(ctx context.Context, proto6DynamicValue *tfprotov6.DynamicValue, sche return nil, diags } - proto6Value, err := proto6DynamicValue.Unmarshal(schema.TerraformType(ctx)) + proto6Value, err := proto6DynamicValue.Unmarshal(schema.Type().TerraformType(ctx)) if err != nil { diags.AddError( diff --git a/tfsdk/tftypes_attribute_path.go b/internal/fromtftypes/attribute_path.go similarity index 78% rename from tfsdk/tftypes_attribute_path.go rename to internal/fromtftypes/attribute_path.go index 70aac6a0a..d3e848676 100644 --- a/tfsdk/tftypes_attribute_path.go +++ b/internal/fromtftypes/attribute_path.go @@ -1,30 +1,23 @@ -package tfsdk +package fromtftypes import ( "context" "fmt" "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-framework/path" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// attributePath returns the path.Path equivalent of a *tftypes.AttributePath. -// -// TODO: This function should be exported as internal/fromtftypes.AttributePath -// except that doing so would currently introduce an import cycle due to the -// tfsdk.Schema parameter here and Config/Plan/State.PathMatches needing to -// call this function until the schema data is migrated to attr.Value. -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/172 -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 -func attributePath(ctx context.Context, tfType *tftypes.AttributePath, schema Schema) (path.Path, diag.Diagnostics) { +// AttributePath returns the path.Path equivalent of a *tftypes.AttributePath. +func AttributePath(ctx context.Context, tfType *tftypes.AttributePath, schema fwschema.Schema) (path.Path, diag.Diagnostics) { fwPath := path.Empty() for tfTypeStepIndex, tfTypeStep := range tfType.Steps() { currentTfTypeSteps := tfType.Steps()[:tfTypeStepIndex+1] currentTfTypePath := tftypes.NewAttributePathWithSteps(currentTfTypeSteps) - attrType, err := schema.AttributeTypeAtPath(currentTfTypePath) + attrType, err := schema.TypeAtTerraformPath(ctx, currentTfTypePath) if err != nil { return path.Empty(), diag.Diagnostics{ @@ -43,7 +36,7 @@ func attributePath(ctx context.Context, tfType *tftypes.AttributePath, schema Sc } } - fwStep, err := fromtftypes.AttributePathStep(ctx, tfTypeStep, attrType) + fwStep, err := AttributePathStep(ctx, tfTypeStep, attrType) if err != nil { return path.Empty(), diag.Diagnostics{ diff --git a/tfsdk/tftypes_attribute_path_test.go b/internal/fromtftypes/attribute_path_test.go similarity index 86% rename from tfsdk/tftypes_attribute_path_test.go rename to internal/fromtftypes/attribute_path_test.go index bb6eb826c..192f108f5 100644 --- a/tfsdk/tftypes_attribute_path_test.go +++ b/internal/fromtftypes/attribute_path_test.go @@ -1,4 +1,4 @@ -package tfsdk +package fromtftypes_test import ( "context" @@ -6,8 +6,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -17,7 +20,7 @@ func TestAttributePath(t *testing.T) { testCases := map[string]struct { tfType *tftypes.AttributePath - schema Schema + schema fwschema.Schema expected path.Path expectedDiags diag.Diagnostics }{ @@ -31,8 +34,8 @@ func TestAttributePath(t *testing.T) { }, "AttributeName": { tfType: tftypes.NewAttributePath().WithAttributeName("test"), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: types.StringType, }, @@ -42,8 +45,8 @@ func TestAttributePath(t *testing.T) { }, "AttributeName-nonexistent-attribute": { tfType: tftypes.NewAttributePath().WithAttributeName("test"), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "not-test": { Type: testtypes.StringType{}, }, @@ -63,8 +66,8 @@ func TestAttributePath(t *testing.T) { }, "AttributeName-ElementKeyInt": { tfType: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(1), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: types.ListType{ ElemType: types.StringType, @@ -76,8 +79,8 @@ func TestAttributePath(t *testing.T) { }, "AttributeName-ElementKeyValue": { tfType: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "test-value")), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: types.SetType{ ElemType: types.StringType, @@ -89,8 +92,8 @@ func TestAttributePath(t *testing.T) { }, "AttributeName-ElementKeyValue-value-conversion-error": { tfType: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "test-value")), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: types.SetType{ ElemType: testtypes.InvalidType{}, @@ -112,8 +115,8 @@ func TestAttributePath(t *testing.T) { }, "ElementKeyInt": { tfType: tftypes.NewAttributePath().WithElementKeyInt(1), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: testtypes.StringType{}, }, @@ -133,8 +136,8 @@ func TestAttributePath(t *testing.T) { }, "ElementKeyString": { tfType: tftypes.NewAttributePath().WithElementKeyString("test"), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: testtypes.StringType{}, }, @@ -154,8 +157,8 @@ func TestAttributePath(t *testing.T) { }, "ElementKeyValue": { tfType: tftypes.NewAttributePath().WithElementKeyValue(tftypes.NewValue(tftypes.String, "test-value")), - schema: Schema{ - Attributes: map[string]Attribute{ + schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "test": { Type: testtypes.StringType{}, }, @@ -181,7 +184,7 @@ func TestAttributePath(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - got, diags := attributePath(context.Background(), testCase.tfType, testCase.schema) + got, diags := fromtftypes.AttributePath(context.Background(), testCase.tfType, testCase.schema) if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/internal/fwschema/attribute.go b/internal/fwschema/attribute.go index 4ccfae86a..81cbad5fc 100644 --- a/internal/fwschema/attribute.go +++ b/internal/fwschema/attribute.go @@ -21,6 +21,13 @@ type Attribute interface { // Equal should return true if the other attribute is exactly equivalent. Equal(o Attribute) bool + // FrameworkType should return the framework type, whether a direct type + // or nested attributes type, for the attribute. + // + // When tfsdk.Attribute is removed, this should be deprecated and renamed + // to Type() to match other interfaces. + FrameworkType() attr.Type + // GetAttributes should return the nested attributes of an attribute, if // applicable. This is named differently than Attribute to prevent a // conflict with the tfsdk.Attribute field name. diff --git a/internal/fwschema/nested_attributes.go b/internal/fwschema/nested_attributes.go index a5823a3d1..c53f3324d 100644 --- a/internal/fwschema/nested_attributes.go +++ b/internal/fwschema/nested_attributes.go @@ -62,11 +62,26 @@ const ( // attributes must be associated with a unique key. Unlike SetNestedAttributes, // the key must be explicitly set by the user. type NestedAttributes interface { + // Implementations should include the tftypes.AttributePathStepper + // interface methods for proper path and data handling. tftypes.AttributePathStepper + + // AttributeType should return the framework type of the nested attributes. + // This method should be deprecated in preference of Type(). AttributeType() attr.Type + + // Equal should return true if the other NestedAttributes is equivalent. + Equal(NestedAttributes) bool + + // GetNestingMode should return the nesting mode (list, map, set, or + // single) of the nested attributes. GetNestingMode() NestingMode + + // GetAttributes() should return the mapping of names to nested attributes. GetAttributes() map[string]Attribute - Equal(NestedAttributes) bool + + // Type should return the framework type of the nested attributes. + Type() attr.Type } type UnderlyingAttributes map[string]Attribute @@ -87,16 +102,11 @@ func (n UnderlyingAttributes) ApplyTerraform5AttributePathStep(step tftypes.Attr return res, nil } -// AttributeType returns an attr.Type corresponding to the nested attributes. -func (n UnderlyingAttributes) AttributeType() attr.Type { +// Type returns the framework type of the nested attributes. +func (n UnderlyingAttributes) Type() attr.Type { attrTypes := map[string]attr.Type{} for name, attr := range n { - if attr.GetType() != nil { - attrTypes[name] = attr.GetType() - } - if attr.GetAttributes() != nil { - attrTypes[name] = attr.GetAttributes().AttributeType() - } + attrTypes[name] = attr.FrameworkType() } return types.ObjectType{ AttrTypes: attrTypes, @@ -123,8 +133,9 @@ func (s SingleNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.At return res, nil } +// Deprecated: Use Type() instead. func (s SingleNestedAttributes) AttributeType() attr.Type { - return s.UnderlyingAttributes.AttributeType() + return s.Type() } func (s SingleNestedAttributes) GetAttributes() map[string]Attribute { @@ -155,6 +166,11 @@ func (s SingleNestedAttributes) Equal(o NestedAttributes) bool { return true } +// Type returns the framework type of the nested attributes. +func (s SingleNestedAttributes) Type() attr.Type { + return s.UnderlyingAttributes.Type() +} + type ListNestedAttributes struct { UnderlyingAttributes } @@ -168,10 +184,9 @@ func (l ListNestedAttributes) GetNestingMode() NestingMode { } // AttributeType returns an attr.Type corresponding to the nested attributes. +// Deprecated: Use Type() instead. func (l ListNestedAttributes) AttributeType() attr.Type { - return types.ListType{ - ElemType: l.UnderlyingAttributes.AttributeType(), - } + return l.Type() } func (l ListNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { @@ -202,6 +217,13 @@ func (l ListNestedAttributes) Equal(o NestedAttributes) bool { return true } +// Type returns the framework type of the nested attributes. +func (l ListNestedAttributes) Type() attr.Type { + return types.ListType{ + ElemType: l.UnderlyingAttributes.Type(), + } +} + type SetNestedAttributes struct { UnderlyingAttributes } @@ -215,10 +237,9 @@ func (s SetNestedAttributes) GetNestingMode() NestingMode { } // AttributeType returns an attr.Type corresponding to the nested attributes. +// Deprecated: Use Type() instead. func (s SetNestedAttributes) AttributeType() attr.Type { - return types.SetType{ - ElemType: s.UnderlyingAttributes.AttributeType(), - } + return s.Type() } func (s SetNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { @@ -249,6 +270,13 @@ func (s SetNestedAttributes) Equal(o NestedAttributes) bool { return true } +// Type returns the framework type of the nested attributes. +func (s SetNestedAttributes) Type() attr.Type { + return types.SetType{ + ElemType: s.UnderlyingAttributes.Type(), + } +} + type MapNestedAttributes struct { UnderlyingAttributes } @@ -262,10 +290,9 @@ func (m MapNestedAttributes) GetNestingMode() NestingMode { } // AttributeType returns an attr.Type corresponding to the nested attributes. +// Deprecated: Use Type() instead. func (m MapNestedAttributes) AttributeType() attr.Type { - return types.MapType{ - ElemType: m.UnderlyingAttributes.AttributeType(), - } + return m.Type() } func (m MapNestedAttributes) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { @@ -295,3 +322,10 @@ func (m MapNestedAttributes) Equal(o NestedAttributes) bool { } return true } + +// Type returns the framework type of the nested attributes. +func (m MapNestedAttributes) Type() attr.Type { + return types.MapType{ + ElemType: m.UnderlyingAttributes.Type(), + } +} diff --git a/internal/fwschema/nested_attributes_test.go b/internal/fwschema/nested_attributes_test.go new file mode 100644 index 000000000..6f21823bc --- /dev/null +++ b/internal/fwschema/nested_attributes_test.go @@ -0,0 +1,173 @@ +package fwschema_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestListNestedAttributesType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + listNestedAttributes fwschema.ListNestedAttributes + expected attr.Type + }{ + "tfsdk-attribute": { + listNestedAttributes: fwschema.ListNestedAttributes{ + UnderlyingAttributes: map[string]fwschema.Attribute{ + "test_nested_attribute": tfsdk.Attribute{ + Required: true, + Type: types.StringType, + }, + }, + }, + expected: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.listNestedAttributes.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestMapNestedAttributesType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + mapNestedAttributes fwschema.MapNestedAttributes + expected attr.Type + }{ + "tfsdk-attribute": { + mapNestedAttributes: fwschema.MapNestedAttributes{ + UnderlyingAttributes: map[string]fwschema.Attribute{ + "test_nested_attribute": tfsdk.Attribute{ + Required: true, + Type: types.StringType, + }, + }, + }, + expected: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.mapNestedAttributes.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSetNestedAttributesType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + setNestedAttributes fwschema.SetNestedAttributes + expected attr.Type + }{ + "tfsdk-attribute": { + setNestedAttributes: fwschema.SetNestedAttributes{ + UnderlyingAttributes: map[string]fwschema.Attribute{ + "test_nested_attribute": tfsdk.Attribute{ + Required: true, + Type: types.StringType, + }, + }, + }, + expected: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.setNestedAttributes.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributesType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + singleNestedAttributes fwschema.SingleNestedAttributes + expected attr.Type + }{ + "tfsdk-attribute": { + singleNestedAttributes: fwschema.SingleNestedAttributes{ + UnderlyingAttributes: map[string]fwschema.Attribute{ + "test_nested_attribute": tfsdk.Attribute{ + Required: true, + Type: types.StringType, + }, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, + }, + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.singleNestedAttributes.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/fwschema/schema.go b/internal/fwschema/schema.go index 5826449fd..3dccb1ec2 100644 --- a/internal/fwschema/schema.go +++ b/internal/fwschema/schema.go @@ -4,6 +4,8 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -15,27 +17,13 @@ type Schema interface { tftypes.AttributePathStepper // AttributeAtPath should return the Attribute at the given path or return - // an error. This signature matches the existing tfsdk.Schema type - // AttributeAtPath method signature to prevent the need for a breaking - // change or deprecation of that method to create this interface. - AttributeAtPath(path *tftypes.AttributePath) (Attribute, error) + // an error. This will be added next release. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 + // AttributeAtPath(context.Context, path.Path) (Attribute, diag.Diagnostics) - // AttributeType should return the framework type of the schema. This is - // named differently than the Attribute interface GetType method name to - // match the existing tfsdk.Schema type AttributeType method signature and - // to prevent the need for a breaking change or deprecation of that method - // to create this interface. - AttributeType() attr.Type - - // AttributeTypeAtPath should return the framework type of the Attribute at - // the given path or return an error. This signature matches the existing - // tfsdk.Schema type AttributeAtPath method signature to prevent the need - // for a breaking change or deprecation of that method to create this - // interface. - // - // This will likely be removed in the future in preference of - // AttributeAtPath. - AttributeTypeAtPath(path *tftypes.AttributePath) (attr.Type, error) + // AttributeAtTerraformPath should return the Attribute at the given + // Terraform path or return an error. + AttributeAtTerraformPath(context.Context, *tftypes.AttributePath) (Attribute, error) // GetAttributes should return the attributes of a schema. This is named // differently than Attributes to prevent a conflict with the tfsdk.Schema @@ -68,11 +56,14 @@ type Schema interface { // field name. GetVersion() int64 - // TerraformType should return the Terraform type of the schema. This - // signature matches the existing tfsdk.Schema type TerraformType method - // signature to prevent the need for a breaking change or deprecation of - // that method to create this interface. - // - // This will likely be removed in the future in preferene of AttributeType. - TerraformType(ctx context.Context) tftypes.Type + // Type should return the framework type of the schema. + Type() attr.Type + + // TypeAtPath should return the framework type of the Attribute at the + // the given path or return an error. + TypeAtPath(context.Context, path.Path) (attr.Type, diag.Diagnostics) + + // AttributeTypeAtPath should return the framework type of the Attribute at + // the given Terraform path or return an error. + TypeAtTerraformPath(context.Context, *tftypes.AttributePath) (attr.Type, error) } diff --git a/internal/fwserver/attribute_plan_modification_test.go b/internal/fwserver/attribute_plan_modification_test.go index d4b9c8f1e..aeda70b3a 100644 --- a/internal/fwserver/attribute_plan_modification_test.go +++ b/internal/fwserver/attribute_plan_modification_test.go @@ -1628,9 +1628,11 @@ func TestAttributeModifyPlan(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() + ctx := context.Background() + // TODO: Remove after schema refactoring // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 - tftypesPath, diags := totftypes.AttributePath(context.Background(), tc.req.AttributePath) + tftypesPath, diags := totftypes.AttributePath(ctx, tc.req.AttributePath) if diags.HasError() { for _, diagnostic := range diags { @@ -1640,7 +1642,7 @@ func TestAttributeModifyPlan(t *testing.T) { return } - attribute, err := tc.req.Config.Schema.AttributeAtPath(tftypesPath) + attribute, err := tc.req.Config.Schema.AttributeAtTerraformPath(ctx, tftypesPath) if err != nil { t.Fatalf("Unexpected error getting %s", err) diff --git a/internal/fwserver/attribute_validation_test.go b/internal/fwserver/attribute_validation_test.go index 552780a65..7a4f77292 100644 --- a/internal/fwserver/attribute_validation_test.go +++ b/internal/fwserver/attribute_validation_test.go @@ -1080,11 +1080,13 @@ func TestAttributeValidate(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() + ctx := context.Background() + var got tfsdk.ValidateAttributeResponse // TODO: Remove after schema refactoring // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 - tftypesPath, diags := totftypes.AttributePath(context.Background(), tc.req.AttributePath) + tftypesPath, diags := totftypes.AttributePath(ctx, tc.req.AttributePath) if diags.HasError() { for _, diagnostic := range diags { @@ -1094,7 +1096,7 @@ func TestAttributeValidate(t *testing.T) { return } - attribute, err := tc.req.Config.Schema.AttributeAtPath(tftypesPath) + attribute, err := tc.req.Config.Schema.AttributeAtTerraformPath(ctx, tftypesPath) if err != nil { t.Fatalf("Unexpected error getting %s", err) diff --git a/internal/fwserver/config.go b/internal/fwserver/config.go index 009e0aaef..8a0ff1966 100644 --- a/internal/fwserver/config.go +++ b/internal/fwserver/config.go @@ -36,7 +36,9 @@ func ConfigGetAttributeValue(ctx context.Context, c tfsdk.Config, path path.Path return nil, diags } - attrType, err := c.Schema.AttributeTypeAtPath(tftypesPath) + // TODO: Use TypeAtPath instead. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 + attrType, err := c.Schema.TypeAtTerraformPath(ctx, tftypesPath) if err != nil { diags.AddAttributeError( path, diff --git a/internal/fwserver/plan.go b/internal/fwserver/plan.go index 40172288a..66dd70c9d 100644 --- a/internal/fwserver/plan.go +++ b/internal/fwserver/plan.go @@ -36,7 +36,9 @@ func PlanGetAttributeValue(ctx context.Context, p tfsdk.Plan, path path.Path) (a return nil, diags } - attrType, err := p.Schema.AttributeTypeAtPath(tftypesPath) + // TODO: Use TypeAtPath instead. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 + attrType, err := p.Schema.TypeAtTerraformPath(ctx, tftypesPath) if err != nil { err = fmt.Errorf("error getting attribute type in schema: %w", err) diags.AddAttributeError( diff --git a/internal/fwserver/server_createresource.go b/internal/fwserver/server_createresource.go index 443713d2c..6c24c408f 100644 --- a/internal/fwserver/server_createresource.go +++ b/internal/fwserver/server_createresource.go @@ -49,7 +49,7 @@ func (s *Server) CreateResource(ctx context.Context, req *CreateResourceRequest, return } - nullSchemaData := tftypes.NewValue(req.ResourceSchema.TerraformType(ctx), nil) + nullSchemaData := tftypes.NewValue(req.ResourceSchema.Type().TerraformType(ctx), nil) createReq := resource.CreateRequest{ Config: tfsdk.Config{ diff --git a/internal/fwserver/server_deleteresource.go b/internal/fwserver/server_deleteresource.go index 5a894f30f..22a64dfb5 100644 --- a/internal/fwserver/server_deleteresource.go +++ b/internal/fwserver/server_deleteresource.go @@ -51,13 +51,13 @@ func (s *Server) DeleteResource(ctx context.Context, req *DeleteResourceRequest, deleteReq := resource.DeleteRequest{ State: tfsdk.State{ Schema: schema(req.ResourceSchema), - Raw: tftypes.NewValue(req.ResourceSchema.TerraformType(ctx), nil), + Raw: tftypes.NewValue(req.ResourceSchema.Type().TerraformType(ctx), nil), }, } deleteResp := resource.DeleteResponse{ State: tfsdk.State{ Schema: schema(req.ResourceSchema), - Raw: tftypes.NewValue(req.ResourceSchema.TerraformType(ctx), nil), + Raw: tftypes.NewValue(req.ResourceSchema.Type().TerraformType(ctx), nil), }, } diff --git a/internal/fwserver/server_planresourcechange.go b/internal/fwserver/server_planresourcechange.go index 586fae9b6..c6a86dbb2 100644 --- a/internal/fwserver/server_planresourcechange.go +++ b/internal/fwserver/server_planresourcechange.go @@ -54,7 +54,7 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange return } - nullTfValue := tftypes.NewValue(req.ResourceSchema.TerraformType(ctx), nil) + nullTfValue := tftypes.NewValue(req.ResourceSchema.Type().TerraformType(ctx), nil) // Prevent potential panics by ensuring incoming Config/Plan/State are null // instead of nil. @@ -253,7 +253,7 @@ func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour return val, nil } - attribute, err := resourceSchema.AttributeAtPath(path) + attribute, err := resourceSchema.AttributeAtTerraformPath(ctx, path) if err != nil { if errors.Is(err, tfsdk.ErrPathInsideAtomicAttribute) { diff --git a/internal/fwserver/server_planresourcechange_test.go b/internal/fwserver/server_planresourcechange_test.go index 5841d3cbd..94f0950b1 100644 --- a/internal/fwserver/server_planresourcechange_test.go +++ b/internal/fwserver/server_planresourcechange_test.go @@ -114,19 +114,39 @@ func TestMarkComputedNilsAsUnknown(t *testing.T) { }, }, } - input := tftypes.NewValue(s.TerraformType(context.Background()), map[string]tftypes.Value{ + input := tftypes.NewValue(s.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "string-value": tftypes.NewValue(tftypes.String, "hello, world"), "string-nil": tftypes.NewValue(tftypes.String, nil), "string-nil-computed": tftypes.NewValue(tftypes.String, nil), "string-nil-optional-computed": tftypes.NewValue(tftypes.String, nil), "string-value-optional-computed": tftypes.NewValue(tftypes.String, "hello, world"), - "object-nil-optional-computed": tftypes.NewValue(s.Attributes["object-nil-optional-computed"].Type.TerraformType(context.Background()), nil), - "object-value-optional-computed": tftypes.NewValue(s.Attributes["object-value-optional-computed"].Type.TerraformType(context.Background()), map[string]tftypes.Value{ + "object-nil-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, nil), + "object-value-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, map[string]tftypes.Value{ "string-nil": tftypes.NewValue(tftypes.String, nil), "string-set": tftypes.NewValue(tftypes.String, "foo"), }), - "nested-nil-optional-computed": tftypes.NewValue(s.Attributes["nested-nil-optional-computed"].Attributes.AttributeType().TerraformType(context.Background()), nil), - "nested-value-optional-computed": tftypes.NewValue(s.Attributes["nested-value-optional-computed"].Attributes.AttributeType().TerraformType(context.Background()), map[string]tftypes.Value{ + "nested-nil-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, nil), + "nested-value-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, map[string]tftypes.Value{ "string-nil": tftypes.NewValue(tftypes.String, nil), "string-set": tftypes.NewValue(tftypes.String, "bar"), }), @@ -137,13 +157,33 @@ func TestMarkComputedNilsAsUnknown(t *testing.T) { "string-nil-computed": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), "string-nil-optional-computed": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), "string-value-optional-computed": tftypes.NewValue(tftypes.String, "hello, world"), - "object-nil-optional-computed": tftypes.NewValue(s.Attributes["object-nil-optional-computed"].Type.TerraformType(context.Background()), tftypes.UnknownValue), - "object-value-optional-computed": tftypes.NewValue(s.Attributes["object-value-optional-computed"].Type.TerraformType(context.Background()), map[string]tftypes.Value{ + "object-nil-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, tftypes.UnknownValue), + "object-value-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, map[string]tftypes.Value{ "string-nil": tftypes.NewValue(tftypes.String, nil), "string-set": tftypes.NewValue(tftypes.String, "foo"), }), - "nested-nil-optional-computed": tftypes.NewValue(s.Attributes["nested-nil-optional-computed"].Attributes.AttributeType().TerraformType(context.Background()), tftypes.UnknownValue), - "nested-value-optional-computed": tftypes.NewValue(s.Attributes["nested-value-optional-computed"].Attributes.AttributeType().TerraformType(context.Background()), map[string]tftypes.Value{ + "nested-nil-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, tftypes.UnknownValue), + "nested-value-optional-computed": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string-nil": tftypes.String, + "string-set": tftypes.String, + }, + }, map[string]tftypes.Value{ "string-nil": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), "string-set": tftypes.NewValue(tftypes.String, "bar"), }), diff --git a/internal/fwserver/server_updateresource.go b/internal/fwserver/server_updateresource.go index d8ee7b3ec..46a965548 100644 --- a/internal/fwserver/server_updateresource.go +++ b/internal/fwserver/server_updateresource.go @@ -50,7 +50,7 @@ func (s *Server) UpdateResource(ctx context.Context, req *UpdateResourceRequest, return } - nullSchemaData := tftypes.NewValue(req.ResourceSchema.TerraformType(ctx), nil) + nullSchemaData := tftypes.NewValue(req.ResourceSchema.Type().TerraformType(ctx), nil) updateReq := resource.UpdateRequest{ Config: tfsdk.Config{ diff --git a/internal/fwserver/server_upgraderesourcestate.go b/internal/fwserver/server_upgraderesourcestate.go index 1d9a3a688..54031ef46 100644 --- a/internal/fwserver/server_upgraderesourcestate.go +++ b/internal/fwserver/server_upgraderesourcestate.go @@ -77,7 +77,7 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS if req.Version == req.ResourceSchema.GetVersion() { logging.FrameworkTrace(ctx, "UpgradeResourceState request version matches current Schema version, using framework defined passthrough implementation") - resourceSchemaType := req.ResourceSchema.TerraformType(ctx) + resourceSchemaType := req.ResourceSchema.Type().TerraformType(ctx) rawStateValue, err := req.RawState.UnmarshalWithOpts(resourceSchemaType, unmarshalOpts) @@ -153,7 +153,7 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS if resourceStateUpgrader.PriorSchema != nil { logging.FrameworkTrace(ctx, "Initializing populated UpgradeResourceStateRequest state from provider defined prior schema and request RawState") - priorSchemaType := resourceStateUpgrader.PriorSchema.TerraformType(ctx) + priorSchemaType := resourceStateUpgrader.PriorSchema.Type().TerraformType(ctx) rawStateValue, err := req.RawState.UnmarshalWithOpts(priorSchemaType, unmarshalOpts) @@ -197,7 +197,7 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS if upgradeResourceStateResponse.DynamicValue != nil { logging.FrameworkTrace(ctx, "UpgradeResourceStateResponse DynamicValue set, overriding State") - upgradedStateValue, err := upgradeResourceStateResponse.DynamicValue.Unmarshal(req.ResourceSchema.TerraformType(ctx)) + upgradedStateValue, err := upgradeResourceStateResponse.DynamicValue.Unmarshal(req.ResourceSchema.Type().TerraformType(ctx)) if err != nil { resp.Diagnostics.AddError( diff --git a/internal/fwserver/server_upgraderesourcestate_test.go b/internal/fwserver/server_upgraderesourcestate_test.go index e6855e3d4..d7222adc3 100644 --- a/internal/fwserver/server_upgraderesourcestate_test.go +++ b/internal/fwserver/server_upgraderesourcestate_test.go @@ -41,7 +41,7 @@ func TestServerUpgradeResourceState(t *testing.T) { }, Version: 1, // Must be above 0 } - schemaType := schema.TerraformType(ctx) + schemaType := schema.Type().TerraformType(ctx) testCases := map[string]struct { server *fwserver.Server diff --git a/internal/fwserver/state.go b/internal/fwserver/state.go index 49aeebfbf..2e009bf67 100644 --- a/internal/fwserver/state.go +++ b/internal/fwserver/state.go @@ -36,7 +36,9 @@ func StateGetAttributeValue(ctx context.Context, s tfsdk.State, path path.Path) return nil, diags } - attrType, err := s.Schema.AttributeTypeAtPath(tftypesPath) + // TODO: Use TypeAtPath instead. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 + attrType, err := s.Schema.TypeAtTerraformPath(ctx, tftypesPath) if err != nil { err = fmt.Errorf("error getting attribute type in schema: %w", err) diags.AddAttributeError( diff --git a/internal/proto5server/server_upgraderesourcestate_test.go b/internal/proto5server/server_upgraderesourcestate_test.go index 8c0f11718..905717aa0 100644 --- a/internal/proto5server/server_upgraderesourcestate_test.go +++ b/internal/proto5server/server_upgraderesourcestate_test.go @@ -37,7 +37,7 @@ func TestServerUpgradeResourceState(t *testing.T) { }, Version: 1, // Must be above 0 } - schemaType := schema.TerraformType(ctx) + schemaType := schema.Type().TerraformType(ctx) testCases := map[string]struct { server *Server diff --git a/internal/proto6server/server_upgraderesourcestate_test.go b/internal/proto6server/server_upgraderesourcestate_test.go index 23c6e1a94..2ca597334 100644 --- a/internal/proto6server/server_upgraderesourcestate_test.go +++ b/internal/proto6server/server_upgraderesourcestate_test.go @@ -37,7 +37,7 @@ func TestServerUpgradeResourceState(t *testing.T) { }, Version: 1, // Must be above 0 } - schemaType := schema.TerraformType(ctx) + schemaType := schema.Type().TerraformType(ctx) testCases := map[string]struct { server *Server diff --git a/internal/toproto5/config.go b/internal/toproto5/config.go index 6c543df90..08b57e6f0 100644 --- a/internal/toproto5/config.go +++ b/internal/toproto5/config.go @@ -16,7 +16,7 @@ func Config(ctx context.Context, fw *tfsdk.Config) (*tfprotov5.DynamicValue, dia var diags diag.Diagnostics - proto5, err := tfprotov5.NewDynamicValue(fw.Schema.TerraformType(ctx), fw.Raw) + proto5, err := tfprotov5.NewDynamicValue(fw.Schema.Type().TerraformType(ctx), fw.Raw) if err != nil { diags.AddError( diff --git a/internal/toproto5/state.go b/internal/toproto5/state.go index fbf967066..4afc16c8e 100644 --- a/internal/toproto5/state.go +++ b/internal/toproto5/state.go @@ -16,7 +16,7 @@ func State(ctx context.Context, fw *tfsdk.State) (*tfprotov5.DynamicValue, diag. var diags diag.Diagnostics - proto5, err := tfprotov5.NewDynamicValue(fw.Schema.TerraformType(ctx), fw.Raw) + proto5, err := tfprotov5.NewDynamicValue(fw.Schema.Type().TerraformType(ctx), fw.Raw) if err != nil { diags.AddError( diff --git a/internal/toproto6/config.go b/internal/toproto6/config.go index 617ada939..a47ce8e0c 100644 --- a/internal/toproto6/config.go +++ b/internal/toproto6/config.go @@ -16,7 +16,7 @@ func Config(ctx context.Context, fw *tfsdk.Config) (*tfprotov6.DynamicValue, dia var diags diag.Diagnostics - proto6, err := tfprotov6.NewDynamicValue(fw.Schema.TerraformType(ctx), fw.Raw) + proto6, err := tfprotov6.NewDynamicValue(fw.Schema.Type().TerraformType(ctx), fw.Raw) if err != nil { diags.AddError( diff --git a/internal/toproto6/state.go b/internal/toproto6/state.go index 5dc0b8da0..d0a21ee86 100644 --- a/internal/toproto6/state.go +++ b/internal/toproto6/state.go @@ -16,7 +16,7 @@ func State(ctx context.Context, fw *tfsdk.State) (*tfprotov6.DynamicValue, diag. var diags diag.Diagnostics - proto6, err := tfprotov6.NewDynamicValue(fw.Schema.TerraformType(ctx), fw.Raw) + proto6, err := tfprotov6.NewDynamicValue(fw.Schema.Type().TerraformType(ctx), fw.Raw) if err != nil { diags.AddError( diff --git a/resource/plan_modifiers.go b/resource/plan_modifiers.go index f61f45b7a..daf28edfa 100644 --- a/resource/plan_modifiers.go +++ b/resource/plan_modifiers.go @@ -100,7 +100,7 @@ func (r requiresReplaceModifier) Modify(ctx context.Context, req tfsdk.ModifyAtt return } - attrSchema, err := req.State.Schema.AttributeAtPath(tftypesPath) + attrSchema, err := req.State.Schema.AttributeAtTerraformPath(ctx, tftypesPath) // Path may lead to block instead of attribute. Blocks cannot be Computed. // If ErrPathIsBlock, attrSchema.Computed will still be false later. @@ -245,7 +245,7 @@ func (r requiresReplaceIfModifier) Modify(ctx context.Context, req tfsdk.ModifyA return } - attrSchema, err := req.State.Schema.AttributeAtPath(tftypesPath) + attrSchema, err := req.State.Schema.AttributeAtTerraformPath(ctx, tftypesPath) // Path may lead to block instead of attribute. Blocks cannot be Computed. // If ErrPathIsBlock, attrSchema.Computed will still be false later. diff --git a/resource/plan_modifiers_test.go b/resource/plan_modifiers_test.go index 8416fb3f7..b5df6482b 100644 --- a/resource/plan_modifiers_test.go +++ b/resource/plan_modifiers_test.go @@ -127,19 +127,19 @@ func TestUseStateForUnknownModifier(t *testing.T) { AttributePath: path.Empty(), Config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "a": configVal, }), }, State: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "a": stateVal, }), }, Plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "a": planVal, }), }, @@ -215,18 +215,18 @@ func TestRequiresReplaceModifier(t *testing.T) { // require replacing immediately state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), nil), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), @@ -244,18 +244,18 @@ func TestRequiresReplaceModifier(t *testing.T) { // does, let's make sure we handle it right plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), nil), }, state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), nil), }, path: path.Root("optional-computed"), expectedPlan: nil, @@ -267,21 +267,21 @@ func TestRequiresReplaceModifier(t *testing.T) { // created state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), @@ -296,21 +296,21 @@ func TestRequiresReplaceModifier(t *testing.T) { // destroyed state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), @@ -324,21 +324,21 @@ func TestRequiresReplaceModifier(t *testing.T) { // should require replacing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -352,21 +352,21 @@ func TestRequiresReplaceModifier(t *testing.T) { // require replacing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -387,21 +387,21 @@ func TestRequiresReplaceModifier(t *testing.T) { // the value. state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, nil), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -423,21 +423,21 @@ func TestRequiresReplaceModifier(t *testing.T) { // be explicit about what each case is actually testing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), @@ -449,7 +449,7 @@ func TestRequiresReplaceModifier(t *testing.T) { "block-no-change": { state: tfsdk.State{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -473,7 +473,7 @@ func TestRequiresReplaceModifier(t *testing.T) { }, plan: tfsdk.Plan{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -497,7 +497,7 @@ func TestRequiresReplaceModifier(t *testing.T) { }, config: tfsdk.Config{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -545,7 +545,7 @@ func TestRequiresReplaceModifier(t *testing.T) { "block-element-count-change": { state: tfsdk.State{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -569,7 +569,7 @@ func TestRequiresReplaceModifier(t *testing.T) { }, plan: tfsdk.Plan{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -602,7 +602,7 @@ func TestRequiresReplaceModifier(t *testing.T) { }, config: tfsdk.Config{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -669,7 +669,7 @@ func TestRequiresReplaceModifier(t *testing.T) { "block-nested-attribute-change": { state: tfsdk.State{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -693,7 +693,7 @@ func TestRequiresReplaceModifier(t *testing.T) { }, plan: tfsdk.Plan{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -717,7 +717,7 @@ func TestRequiresReplaceModifier(t *testing.T) { }, config: tfsdk.Config{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -871,18 +871,18 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // require replacing immediately state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), nil), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), @@ -902,18 +902,18 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // does, let's make sure we handle it right plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), nil), }, state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), nil), + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), nil), }, priorRR: false, path: path.Root("optional-computed"), @@ -927,21 +927,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // created state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), @@ -958,21 +958,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // destroyed state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), @@ -989,21 +989,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // replacing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -1020,21 +1020,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // replacing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -1052,21 +1052,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // be recreated, we shouldn't override that state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -1082,21 +1082,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // require replacing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -1119,21 +1119,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // the value. state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, nil), "optional": tftypes.NewValue(tftypes.String, "quux"), }), @@ -1157,21 +1157,21 @@ func TestRequiresReplaceIfModifier(t *testing.T) { // be explicit about what each case is actually testing state: tfsdk.State{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, "bar"), }), }, plan: tfsdk.Plan{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), }, config: tfsdk.Config{ Schema: schema, - Raw: tftypes.NewValue(schema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(schema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "optional-computed": tftypes.NewValue(tftypes.String, "foo"), "optional": tftypes.NewValue(tftypes.String, nil), }), @@ -1185,7 +1185,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { "block-no-change": { state: tfsdk.State{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1209,7 +1209,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { }, plan: tfsdk.Plan{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1233,7 +1233,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { }, config: tfsdk.Config{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1282,7 +1282,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { "block-element-count-change": { state: tfsdk.State{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1306,7 +1306,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { }, plan: tfsdk.Plan{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1339,7 +1339,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { }, config: tfsdk.Config{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1407,7 +1407,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { "block-nested-attribute-change": { state: tfsdk.State{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1431,7 +1431,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { }, plan: tfsdk.Plan{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ @@ -1455,7 +1455,7 @@ func TestRequiresReplaceIfModifier(t *testing.T) { }, config: tfsdk.Config{ Schema: blockSchema, - Raw: tftypes.NewValue(blockSchema.TerraformType(context.Background()), map[string]tftypes.Value{ + Raw: tftypes.NewValue(blockSchema.Type().TerraformType(context.Background()), map[string]tftypes.Value{ "block": tftypes.NewValue( tftypes.List{ ElementType: tftypes.Object{ diff --git a/tfsdk/attribute.go b/tfsdk/attribute.go index 45ba1049a..eca1155a8 100644 --- a/tfsdk/attribute.go +++ b/tfsdk/attribute.go @@ -1,7 +1,6 @@ package tfsdk import ( - "context" "errors" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -198,6 +197,16 @@ func (a Attribute) Equal(o fwschema.Attribute) bool { return true } +// FrameworkType returns the framework type, whether the direct type or nested +// attributes type, of the attribute. +func (a Attribute) FrameworkType() attr.Type { + if a.Attributes != nil { + return a.Attributes.Type() + } + + return a.Type +} + // GetAttributes satisfies the fwschema.Attribute interface. func (a Attribute) GetAttributes() fwschema.NestedAttributes { return a.Attributes @@ -253,12 +262,3 @@ func (a Attribute) IsRequired() bool { func (a Attribute) IsSensitive() bool { return a.Sensitive } - -// terraformType returns an tftypes.Type corresponding to the attribute. -func (a Attribute) terraformType(ctx context.Context) tftypes.Type { - if a.Attributes != nil { - return a.Attributes.AttributeType().TerraformType(ctx) - } - - return a.Type.TerraformType(ctx) -} diff --git a/tfsdk/attribute_test.go b/tfsdk/attribute_test.go index 3281ac8c7..4067c186c 100644 --- a/tfsdk/attribute_test.go +++ b/tfsdk/attribute_test.go @@ -1,128 +1,19 @@ package tfsdk import ( - "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestAttributeGetType(t *testing.T) { +func TestAttributeFrameworkType(t *testing.T) { t.Parallel() testCases := map[string]struct { attribute Attribute expected attr.Type - }{ - "Type-BoolType": { - attribute: Attribute{ - Required: true, - Type: types.BoolType, - }, - expected: types.BoolType, - }, - "Type-Float64Type": { - attribute: Attribute{ - Required: true, - Type: types.Float64Type, - }, - expected: types.Float64Type, - }, - "Type-Int64Type": { - attribute: Attribute{ - Required: true, - Type: types.Int64Type, - }, - expected: types.Int64Type, - }, - "Type-ListType": { - attribute: Attribute{ - Required: true, - Type: types.ListType{ - ElemType: types.StringType, - }, - }, - expected: types.ListType{ - ElemType: types.StringType, - }, - }, - "Type-MapType": { - attribute: Attribute{ - Required: true, - Type: types.MapType{ - ElemType: types.StringType, - }, - }, - expected: types.MapType{ - ElemType: types.StringType, - }, - }, - "Type-ObjectType": { - attribute: Attribute{ - Required: true, - Type: types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "test_object_attribute": types.StringType, - }, - }, - }, - expected: types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "test_object_attribute": types.StringType, - }, - }, - }, - "Type-NumberType": { - attribute: Attribute{ - Required: true, - Type: types.NumberType, - }, - expected: types.NumberType, - }, - "Type-SetType": { - attribute: Attribute{ - Required: true, - Type: types.SetType{ - ElemType: types.StringType, - }, - }, - expected: types.SetType{ - ElemType: types.StringType, - }, - }, - "Type-StringType": { - attribute: Attribute{ - Required: true, - Type: types.StringType, - }, - expected: types.StringType, - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - got := testCase.attribute.GetType() - - if diff := cmp.Diff(got, testCase.expected); diff != "" { - t.Errorf("unexpected difference: %s", diff) - } - }) - } -} - -func TestAttributeTerraformType(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - attribute Attribute - expected tftypes.Type }{ "Attributes-ListNestedAttributes": { attribute: Attribute{ @@ -134,10 +25,10 @@ func TestAttributeTerraformType(t *testing.T) { }), Required: true, }, - expected: tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_nested_attribute": tftypes.String, + expected: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, }, }, }, @@ -152,10 +43,10 @@ func TestAttributeTerraformType(t *testing.T) { }), Required: true, }, - expected: tftypes.Map{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_nested_attribute": tftypes.String, + expected: types.MapType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, }, }, }, @@ -170,10 +61,10 @@ func TestAttributeTerraformType(t *testing.T) { }), Required: true, }, - expected: tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_nested_attribute": tftypes.String, + expected: types.SetType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, }, }, }, @@ -188,9 +79,9 @@ func TestAttributeTerraformType(t *testing.T) { }), Required: true, }, - expected: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_nested_attribute": tftypes.String, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_nested_attribute": types.StringType, }, }, }, @@ -199,21 +90,21 @@ func TestAttributeTerraformType(t *testing.T) { Required: true, Type: types.BoolType, }, - expected: tftypes.Bool, + expected: types.BoolType, }, "Type-Float64Type": { attribute: Attribute{ Required: true, Type: types.Float64Type, }, - expected: tftypes.Number, + expected: types.Float64Type, }, "Type-Int64Type": { attribute: Attribute{ Required: true, Type: types.Int64Type, }, - expected: tftypes.Number, + expected: types.Int64Type, }, "Type-ListType": { attribute: Attribute{ @@ -222,8 +113,8 @@ func TestAttributeTerraformType(t *testing.T) { ElemType: types.StringType, }, }, - expected: tftypes.List{ - ElementType: tftypes.String, + expected: types.ListType{ + ElemType: types.StringType, }, }, "Type-MapType": { @@ -233,8 +124,8 @@ func TestAttributeTerraformType(t *testing.T) { ElemType: types.StringType, }, }, - expected: tftypes.Map{ - ElementType: tftypes.String, + expected: types.MapType{ + ElemType: types.StringType, }, }, "Type-NumberType": { @@ -242,7 +133,7 @@ func TestAttributeTerraformType(t *testing.T) { Required: true, Type: types.NumberType, }, - expected: tftypes.Number, + expected: types.NumberType, }, "Type-ObjectType": { attribute: Attribute{ @@ -253,9 +144,9 @@ func TestAttributeTerraformType(t *testing.T) { }, }, }, - expected: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_object_attribute": tftypes.String, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "test_object_attribute": types.StringType, }, }, }, @@ -266,8 +157,8 @@ func TestAttributeTerraformType(t *testing.T) { ElemType: types.StringType, }, }, - expected: tftypes.Set{ - ElementType: tftypes.String, + expected: types.SetType{ + ElemType: types.StringType, }, }, "Type-StringType": { @@ -275,7 +166,7 @@ func TestAttributeTerraformType(t *testing.T) { Required: true, Type: types.StringType, }, - expected: tftypes.String, + expected: types.StringType, }, } @@ -285,7 +176,7 @@ func TestAttributeTerraformType(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - got := testCase.attribute.terraformType(context.Background()) + got := testCase.attribute.FrameworkType() if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/tfsdk/block.go b/tfsdk/block.go index f1c128ead..2ae2d0b8e 100644 --- a/tfsdk/block.go +++ b/tfsdk/block.go @@ -1,7 +1,6 @@ package tfsdk import ( - "context" "fmt" "github.com/google/go-cmp/cmp" @@ -208,7 +207,7 @@ func (b Block) Type() attr.Type { } for attrName, attr := range b.Attributes { - attrType.AttrTypes[attrName] = attr.GetType() + attrType.AttrTypes[attrName] = attr.FrameworkType() } for blockName, block := range b.Blocks { @@ -228,8 +227,3 @@ func (b Block) Type() attr.Type { panic(fmt.Sprintf("unsupported block nesting mode: %v", b.NestingMode)) } } - -// terraformType returns an tftypes.Type corresponding to the block. -func (b Block) terraformType(ctx context.Context) tftypes.Type { - return b.Type().TerraformType(ctx) -} diff --git a/tfsdk/block_test.go b/tfsdk/block_test.go index 44092e2dc..9a7840eff 100644 --- a/tfsdk/block_test.go +++ b/tfsdk/block_test.go @@ -1,16 +1,14 @@ package tfsdk import ( - "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestBlockAttributeType(t *testing.T) { +func TestBlockType(t *testing.T) { t.Parallel() testCases := map[string]struct { @@ -105,99 +103,3 @@ func TestBlockAttributeType(t *testing.T) { }) } } - -func TestBlockTerraformType(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - block Block - expected tftypes.Type - }{ - "NestingMode-List": { - block: Block{ - Attributes: map[string]Attribute{ - "test_attribute": { - Required: true, - Type: types.StringType, - }, - }, - Blocks: map[string]Block{ - "test_block": { - Attributes: map[string]Attribute{ - "test_block_attribute": { - Required: true, - Type: types.StringType, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - NestingMode: BlockNestingModeList, - }, - expected: tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_attribute": tftypes.String, - "test_block": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_block_attribute": tftypes.String, - }, - }, - }, - }, - }, - }, - }, - "NestingMode-Set": { - block: Block{ - Attributes: map[string]Attribute{ - "test_attribute": { - Required: true, - Type: types.StringType, - }, - }, - Blocks: map[string]Block{ - "test_block": { - Attributes: map[string]Attribute{ - "test_block_attribute": { - Required: true, - Type: types.StringType, - }, - }, - NestingMode: BlockNestingModeSet, - }, - }, - NestingMode: BlockNestingModeSet, - }, - expected: tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_attribute": tftypes.String, - "test_block": tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test_block_attribute": tftypes.String, - }, - }, - }, - }, - }, - }, - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - got := testCase.block.terraformType(context.Background()) - - if diff := cmp.Diff(got, testCase.expected); diff != "" { - t.Errorf("unexpected difference: %s", diff) - } - }) - } -} diff --git a/tfsdk/config.go b/tfsdk/config.go index 888420199..5e26aaeef 100644 --- a/tfsdk/config.go +++ b/tfsdk/config.go @@ -23,7 +23,7 @@ type Config struct { // Get populates the struct passed as `target` with the entire config. func (c Config) Get(ctx context.Context, target interface{}) diag.Diagnostics { - return reflect.Into(ctx, c.Schema.AttributeType(), c.Raw, target, reflect.Options{}) + return reflect.Into(ctx, c.Schema.Type(), c.Raw, target, reflect.Options{}) } // GetAttribute retrieves the attribute found at `path` and populates the diff --git a/tfsdk/path_matches.go b/tfsdk/path_matches.go index dae644768..25e9d9bc7 100644 --- a/tfsdk/path_matches.go +++ b/tfsdk/path_matches.go @@ -4,6 +4,8 @@ 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-framework/path" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -20,12 +22,12 @@ import ( // call this function until the schema data is migrated to attr.Value. // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/172 // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/365 -func pathMatches(ctx context.Context, schema Schema, tfTypeValue tftypes.Value, pathExpr path.Expression) (path.Paths, diag.Diagnostics) { +func pathMatches(ctx context.Context, schema fwschema.Schema, tfTypeValue tftypes.Value, pathExpr path.Expression) (path.Paths, diag.Diagnostics) { var diags diag.Diagnostics var paths path.Paths _ = tftypes.Walk(tfTypeValue, func(tfTypePath *tftypes.AttributePath, tfTypeValue tftypes.Value) (bool, error) { - fwPath, fwPathDiags := attributePath(ctx, tfTypePath, schema) + fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, schema) diags.Append(fwPathDiags...) diff --git a/tfsdk/path_matches_test.go b/tfsdk/path_matches_test.go index 07ec50475..dbd820297 100644 --- a/tfsdk/path_matches_test.go +++ b/tfsdk/path_matches_test.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -15,7 +16,7 @@ func TestPathMatches(t *testing.T) { t.Parallel() testCases := map[string]struct { - schema Schema + schema fwschema.Schema tfTypeValue tftypes.Value expression path.Expression expected path.Paths diff --git a/tfsdk/plan.go b/tfsdk/plan.go index 5efb8bef6..15be9303d 100644 --- a/tfsdk/plan.go +++ b/tfsdk/plan.go @@ -23,7 +23,7 @@ type Plan struct { // Get populates the struct passed as `target` with the entire plan. func (p Plan) Get(ctx context.Context, target interface{}) diag.Diagnostics { - return reflect.Into(ctx, p.Schema.AttributeType(), p.Raw, target, reflect.Options{}) + return reflect.Into(ctx, p.Schema.Type(), p.Raw, target, reflect.Options{}) } // GetAttribute retrieves the attribute found at `path` and populates the @@ -143,7 +143,7 @@ func (p Plan) PathMatches(ctx context.Context, pathExpr path.Expression) (path.P // should be a struct whose values have one of the attr.Value types. Each field // must be tagged with the corresponding schema field. func (p *Plan) Set(ctx context.Context, val interface{}) diag.Diagnostics { - newPlanAttrValue, diags := reflect.FromValue(ctx, p.Schema.AttributeType(), val, path.Empty()) + newPlanAttrValue, diags := reflect.FromValue(ctx, p.Schema.Type(), val, path.Empty()) if diags.HasError() { return diags } diff --git a/tfsdk/schema.go b/tfsdk/schema.go index 6dc89e6c6..2153b1985 100644 --- a/tfsdk/schema.go +++ b/tfsdk/schema.go @@ -6,7 +6,10 @@ import ( "fmt" "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/totftypes" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -99,24 +102,49 @@ func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) } // AttributeType returns a types.ObjectType composed from the schema types. +// Deprecated: Use Type() instead. func (s Schema) AttributeType() attr.Type { - attrTypes := map[string]attr.Type{} - for name, attr := range s.Attributes { - if attr.GetAttributes() != nil { - attrTypes[name] = attr.GetAttributes().AttributeType() - continue - } + return s.Type() +} + +// AttributeTypeAtPath returns the attr.Type of the attribute at the given path. +// +// Deprecated: Use the TypeAtPath() or TypeAtTerraformPath() method. +func (s Schema) AttributeTypeAtPath(path *tftypes.AttributePath) (attr.Type, error) { + return s.TypeAtTerraformPath(context.Background(), path) +} + +// TypeAtPath returns the framework type at the given schema path. +func (s Schema) TypeAtPath(ctx context.Context, schemaPath path.Path) (attr.Type, diag.Diagnostics) { + var diags diag.Diagnostics + + tftypesPath, tftypesDiags := totftypes.AttributePath(ctx, schemaPath) - attrTypes[name] = attr.GetType() + diags.Append(tftypesDiags...) + + if diags.HasError() { + return nil, diags } - for name, block := range s.Blocks { - attrTypes[name] = block.Type() + + attrType, err := s.TypeAtTerraformPath(ctx, tftypesPath) + + if err != nil { + diags.AddAttributeError( + schemaPath, + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. "+ + "This is either an issue with the provider or terraform-plugin-framework. Please report this to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", schemaPath.String())+ + fmt.Sprintf("Original Error: %s", err), + ) + return nil, diags } - return types.ObjectType{AttrTypes: attrTypes} + + return attrType, diags } -// AttributeTypeAtPath returns the attr.Type of the attribute at the given path. -func (s Schema) AttributeTypeAtPath(path *tftypes.AttributePath) (attr.Type, error) { +// TypeAtTerraformPath returns the framework type at the given tftypes path. +func (s Schema) TypeAtTerraformPath(_ context.Context, path *tftypes.AttributePath) (attr.Type, error) { rawType, remaining, err := tftypes.WalkAttributePath(s, path) if err != nil { return nil, fmt.Errorf("%v still remains in the path: %w", remaining, err) @@ -126,19 +154,15 @@ func (s Schema) AttributeTypeAtPath(path *tftypes.AttributePath) (attr.Type, err case attr.Type: return typ, nil case fwschema.UnderlyingAttributes: - return typ.AttributeType(), nil + return typ.Type(), nil case fwschema.NestedBlock: return typ.Block.Type(), nil case Attribute: - if typ.GetAttributes() != nil { - return typ.GetAttributes().AttributeType(), nil - } - - return typ.GetType(), nil + return typ.FrameworkType(), nil case Block: return typ.Type(), nil case Schema: - return typ.AttributeType(), nil + return typ.Type(), nil default: return nil, fmt.Errorf("got unexpected type %T", rawType) } @@ -175,21 +199,41 @@ func (s Schema) GetVersion() int64 { } // TerraformType returns a tftypes.Type that can represent the schema. +// Deprecated: Use Type().TerraformType() instead. func (s Schema) TerraformType(ctx context.Context) tftypes.Type { - attrTypes := map[string]tftypes.Type{} + return s.Type().TerraformType(ctx) +} + +// Type returns the framework type of the schema. +func (s Schema) Type() attr.Type { + attrTypes := map[string]attr.Type{} + for name, attr := range s.Attributes { - attrTypes[name] = attr.terraformType(ctx) + attrTypes[name] = attr.FrameworkType() } + for name, block := range s.Blocks { - attrTypes[name] = block.terraformType(ctx) + attrTypes[name] = block.Type() } - return tftypes.Object{AttributeTypes: attrTypes} + + return types.ObjectType{AttrTypes: attrTypes} } // AttributeAtPath returns the Attribute at the passed path. If the path points // to an element or attribute of a complex type, rather than to an Attribute, // it will return an ErrPathInsideAtomicAttribute error. +// +// Deprecated: The signature will be updated in the next release. +// Use AttributeAtTerraformPath() if the *tftypes.AttributePath parameter is +// still needed. func (s Schema) AttributeAtPath(path *tftypes.AttributePath) (fwschema.Attribute, error) { + return s.AttributeAtTerraformPath(context.Background(), path) +} + +// AttributeAtPath returns the Attribute at the passed path. If the path points +// to an element or attribute of a complex type, rather than to an Attribute, +// it will return an ErrPathInsideAtomicAttribute error. +func (s Schema) AttributeAtTerraformPath(_ context.Context, path *tftypes.AttributePath) (fwschema.Attribute, error) { res, remaining, err := tftypes.WalkAttributePath(s, path) if err != nil { return Attribute{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) diff --git a/tfsdk/schema_test.go b/tfsdk/schema_test.go index 7da1e6934..546ab85da 100644 --- a/tfsdk/schema_test.go +++ b/tfsdk/schema_test.go @@ -1,10 +1,15 @@ package tfsdk import ( + "context" + "fmt" + "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -1154,7 +1159,1152 @@ func TestSchemaAttributeAtPath(t *testing.T) { } } -func TestSchemaAttributeType(t *testing.T) { +func TestSchemaAttributeAtTerraformPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema Schema + path *tftypes.AttributePath + expected Attribute + expectedErr string + }{ + "empty-root": { + schema: Schema{}, + path: tftypes.NewAttributePath(), + expected: Attribute{}, + expectedErr: "got unexpected type tfsdk.Schema", + }, + "empty-nil": { + schema: Schema{}, + path: nil, + expected: Attribute{}, + expectedErr: "got unexpected type tfsdk.Schema", + }, + "root": { + schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath(), + expected: Attribute{}, + expectedErr: "got unexpected type tfsdk.Schema", + }, + "nil": { + schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: nil, + expected: Attribute{}, + expectedErr: "got unexpected type tfsdk.Schema", + }, + "WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-ListNestedAttributes-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{}, + expectedErr: "AttributeName(\"sub_test\") still remains in the path: can't apply tftypes.AttributeName to ListNestedAttributes", + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyInt-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"sub_test\") still remains in the path: can't apply tftypes.ElementKeyString to ListNestedAttributes", + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "sub_test")), + expected: Attribute{}, + expectedErr: "ElementKeyValue(tftypes.String<\"sub_test\">) still remains in the path: can't apply tftypes.ElementKeyValue to ListNestedAttributes", + }, + "WithAttributeName-ListNestedBlocks-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{}, + expectedErr: "AttributeName(\"sub_test\") still remains in the path: can't apply tftypes.AttributeName to block NestingModeList", + }, + "WithAttributeName-ListNestedBlocks-WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-ListNestedBlocks-WithElementKeyInt-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-ListNestedBlocks-WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"sub_test\") still remains in the path: can't apply tftypes.ElementKeyString to block NestingModeList", + }, + "WithAttributeName-ListNestedBlocks-WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeList, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "sub_test")), + expected: Attribute{}, + expectedErr: "ElementKeyValue(tftypes.String<\"sub_test\">) still remains in the path: can't apply tftypes.ElementKeyValue to block NestingModeList", + }, + "WithAttributeName-MapNestedAttributes-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{}, + expectedErr: "AttributeName(\"sub_test\") still remains in the path: can't use tftypes.AttributeName on maps", + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: "ElementKeyInt(0) still remains in the path: can't use tftypes.ElementKeyInt on maps", + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyString-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "sub_test")), + expected: Attribute{}, + expectedErr: "ElementKeyValue(tftypes.String<\"sub_test\">) still remains in the path: can't use tftypes.ElementKeyValue on maps", + }, + "WithAttributeName-SetNestedAttributes-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{}, + expectedErr: "AttributeName(\"sub_test\") still remains in the path: can't use tftypes.AttributeName on sets", + }, + "WithAttributeName-SetNestedAttributes-WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: "ElementKeyInt(0) still remains in the path: can't use tftypes.ElementKeyInt on sets", + }, + "WithAttributeName-SetNestedAttributes-WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"sub_test\") still remains in the path: can't use tftypes.ElementKeyString on sets", + }, + "WithAttributeName-SetNestedAttributes-WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "sub_test")), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-SetNestedAttributes-WithElementKeyValue-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "element")).WithAttributeName("sub_test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-SetNestedBlocks-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{}, + expectedErr: "AttributeName(\"sub_test\") still remains in the path: can't apply tftypes.AttributeName to block NestingModeSet", + }, + "WithAttributeName-SetNestedBlocks-WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: "ElementKeyInt(0) still remains in the path: can't apply tftypes.ElementKeyInt to block NestingModeSet", + }, + "WithAttributeName-SetNestedBlocks-WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"sub_test\") still remains in the path: can't apply tftypes.ElementKeyString to block NestingModeSet", + }, + "WithAttributeName-SetNestedBlocks-WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "sub_test")), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-SetNestedBlocks-WithElementKeyValue-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + }, + Blocks: map[string]Block{ + "other_block": { + Attributes: map[string]Attribute{ + "sub_test": { + Type: types.BoolType, + Optional: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + "test": { + Attributes: map[string]Attribute{ + "other_attr": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "element")).WithAttributeName("sub_test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-SingleNestedAttributes-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{ + Type: types.StringType, + Required: true, + }, + }, + "WithAttributeName-SingleNestedAttributes-WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: "ElementKeyInt(0) still remains in the path: can't apply tftypes.ElementKeyInt to Attributes", + }, + "WithAttributeName-SingleNestedAttributes-WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"sub_test\") still remains in the path: can't apply tftypes.ElementKeyString to Attributes", + }, + "WithAttributeName-SingleNestedAttributes-WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "sub_test")), + expected: Attribute{}, + expectedErr: "ElementKeyValue(tftypes.String<\"sub_test\">) still remains in the path: can't apply tftypes.ElementKeyValue to Attributes", + }, + "WithAttributeName-Object-WithAttributeName": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "sub_test": types.StringType, + }, + }, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-WithElementKeyInt-invalid-parent": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: "ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to types.StringType", + }, + "WithAttributeName-WithElementKeyInt-valid-parent": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-WithElementKeyString-invalid-parent": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"element\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to types.StringType", + }, + "WithAttributeName-WithElementKeyString-valid-parent": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element"), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithAttributeName-WithElementKeyValue-invalid-parent": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "element")), + expected: Attribute{}, + expectedErr: "ElementKeyValue(tftypes.String<\"element\">) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to types.StringType", + }, + "WithAttributeName-WithElementKeyValue-valid-parent": { + schema: Schema{ + Attributes: map[string]Attribute{ + "other": { + Type: types.BoolType, + Optional: true, + }, + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "element")), + expected: Attribute{}, + expectedErr: ErrPathInsideAtomicAttribute.Error(), + }, + "WithElementKeyInt": { + schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithElementKeyInt(0), + expected: Attribute{}, + expectedErr: "ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema", + }, + "WithElementKeyString": { + schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithElementKeyString("test"), + expected: Attribute{}, + expectedErr: "ElementKeyString(\"test\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema", + }, + "WithElementKeyValue": { + schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + path: tftypes.NewAttributePath().WithElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: Attribute{}, + expectedErr: "ElementKeyValue(tftypes.String<\"test\">) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema", + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := tc.schema.AttributeAtTerraformPath(context.Background(), tc.path) + + if err != nil { + if tc.expectedErr == "" { + t.Errorf("Unexpected error: %s", err) + return + } + if err.Error() != tc.expectedErr { + t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) + return + } + // got expected error + return + } + + if err == nil && tc.expectedErr != "" { + t.Errorf("Expected error to be %q, got nil", tc.expectedErr) + return + } + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("Unexpected result (+wanted, -got): %s", diff) + } + }) + } +} + +func TestSchemaType(t *testing.T) { testSchema := Schema{ Attributes: map[string]Attribute{ "foo": { @@ -1284,9 +2434,325 @@ func TestSchemaAttributeType(t *testing.T) { }, } - actualType := testSchema.AttributeType() + actualType := testSchema.Type() if !expectedType.Equal(actualType) { t.Fatalf("types not equal (+wanted, -got): %s", cmp.Diff(expectedType, actualType)) } } + +func TestSchemaTypeAtPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema Schema + path path.Path + expected attr.Type + expectedDiags diag.Diagnostics + }{ + "empty-schema-empty-path": { + schema: Schema{}, + path: path.Empty(), + expected: types.ObjectType{}, + }, + "empty-path": { + schema: Schema{ + Attributes: map[string]Attribute{ + "bool": { + Required: true, + Type: types.BoolType, + }, + "string": { + Required: true, + Type: types.StringType, + }, + }, + }, + path: path.Empty(), + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "string": types.StringType, + }, + }, + }, + "AttributeName-Attribute": { + schema: Schema{ + Attributes: map[string]Attribute{ + "bool": { + Required: true, + Type: types.BoolType, + }, + "string": { + Required: true, + Type: types.StringType, + }, + }, + }, + path: path.Root("string"), + expected: types.StringType, + }, + "AttributeName-Block": { + schema: Schema{ + Blocks: map[string]Block{ + "list_block": { + Attributes: map[string]Attribute{ + "list_block_nested": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeList, + }, + "set_block": { + Attributes: map[string]Attribute{ + "set_block_nested": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: path.Root("list_block"), + expected: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list_block_nested": types.StringType, + }, + }, + }, + }, + "AttributeName-non-existent": { + schema: Schema{}, + path: path.Root("non-existent"), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("non-existent"), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is either an issue with the provider or terraform-plugin-framework. Please report this to the provider developers.\n\n"+ + "Path: non-existent\n"+ + "Original Error: AttributeName(\"non-existent\") still remains in the path: could not find attribute or block \"non-existent\" in schema", + ), + }, + }, + "ElementKeyInt": { + schema: Schema{}, + path: path.Empty().AtListIndex(0), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtListIndex(0), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is either an issue with the provider or terraform-plugin-framework. Please report this to the provider developers.\n\n"+ + "Path: [0]\n"+ + "Original Error: ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema", + ), + }, + }, + "ElementKeyString": { + schema: Schema{}, + path: path.Empty().AtMapKey("invalid"), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtMapKey("invalid"), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is either an issue with the provider or terraform-plugin-framework. Please report this to the provider developers.\n\n"+ + "Path: [\"invalid\"]\n"+ + "Original Error: ElementKeyString(\"invalid\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema", + ), + }, + }, + "ElementKeyValue": { + schema: Schema{}, + path: path.Empty().AtSetValue(types.String{Null: true}), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtSetValue(types.String{Null: true}), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is either an issue with the provider or terraform-plugin-framework. Please report this to the provider developers.\n\n"+ + "Path: [Value()]\n"+ + "Original Error: ElementKeyValue(tftypes.String) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema", + ), + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := testCase.schema.TypeAtPath(context.Background(), testCase.path) + + if diff := cmp.Diff(diags, testCase.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaTypeAtTerraformPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema Schema + path *tftypes.AttributePath + expected attr.Type + expectedError error + }{ + "empty-schema-nil-path": { + schema: Schema{}, + path: nil, + expected: types.ObjectType{}, + }, + "empty-schema-empty-path": { + schema: Schema{}, + path: tftypes.NewAttributePath(), + expected: types.ObjectType{}, + }, + "nil-path": { + schema: Schema{ + Attributes: map[string]Attribute{ + "bool": { + Required: true, + Type: types.BoolType, + }, + "string": { + Required: true, + Type: types.StringType, + }, + }, + }, + path: nil, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "string": types.StringType, + }, + }, + }, + "empty-path": { + schema: Schema{ + Attributes: map[string]Attribute{ + "bool": { + Required: true, + Type: types.BoolType, + }, + "string": { + Required: true, + Type: types.StringType, + }, + }, + }, + path: tftypes.NewAttributePath(), + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "string": types.StringType, + }, + }, + }, + "AttributeName-Attribute": { + schema: Schema{ + Attributes: map[string]Attribute{ + "bool": { + Required: true, + Type: types.BoolType, + }, + "string": { + Required: true, + Type: types.StringType, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("string"), + expected: types.StringType, + }, + "AttributeName-Block": { + schema: Schema{ + Blocks: map[string]Block{ + "list_block": { + Attributes: map[string]Attribute{ + "list_block_nested": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeList, + }, + "set_block": { + Attributes: map[string]Attribute{ + "set_block_nested": { + Required: true, + Type: types.StringType, + }, + }, + NestingMode: BlockNestingModeSet, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("list_block"), + expected: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "list_block_nested": types.StringType, + }, + }, + }, + }, + "AttributeName-non-existent": { + schema: Schema{}, + path: tftypes.NewAttributePath().WithAttributeName("non-existent"), + expectedError: fmt.Errorf("AttributeName(\"non-existent\") still remains in the path: could not find attribute or block \"non-existent\" in schema"), + }, + "ElementKeyInt": { + schema: Schema{}, + path: tftypes.NewAttributePath().WithElementKeyInt(0), + expectedError: fmt.Errorf("ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema"), + }, + "ElementKeyString": { + schema: Schema{}, + path: tftypes.NewAttributePath().WithElementKeyString("invalid"), + expectedError: fmt.Errorf("ElementKeyString(\"invalid\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema"), + }, + "ElementKeyValue": { + schema: Schema{}, + path: tftypes.NewAttributePath().WithElementKeyValue(tftypes.NewValue(tftypes.String, nil)), + expectedError: fmt.Errorf("ElementKeyValue(tftypes.String) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.schema.TypeAtTerraformPath(context.Background(), testCase.path) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/tfsdk/state.go b/tfsdk/state.go index 540615b20..aa239660e 100644 --- a/tfsdk/state.go +++ b/tfsdk/state.go @@ -23,7 +23,7 @@ type State struct { // Get populates the struct passed as `target` with the entire state. func (s State) Get(ctx context.Context, target interface{}) diag.Diagnostics { - return reflect.Into(ctx, s.Schema.AttributeType(), s.Raw, target, reflect.Options{}) + return reflect.Into(ctx, s.Schema.Type(), s.Raw, target, reflect.Options{}) } // GetAttribute retrieves the attribute found at `path` and populates the @@ -152,7 +152,7 @@ func (s *State) Set(ctx context.Context, val interface{}) diag.Diagnostics { ), } } - newStateAttrValue, diags := reflect.FromValue(ctx, s.Schema.AttributeType(), val, path.Empty()) + newStateAttrValue, diags := reflect.FromValue(ctx, s.Schema.Type(), val, path.Empty()) if diags.HasError() { return diags } @@ -389,7 +389,7 @@ func (s State) setAttributeTransformFunc(ctx context.Context, path path.Path, tf // If a Resource type Delete method is completed without error, this is // automatically called on the DeleteResourceResponse.State. func (s *State) RemoveResource(ctx context.Context) { - s.Raw = tftypes.NewValue(s.Schema.TerraformType(ctx), nil) + s.Raw = tftypes.NewValue(s.Schema.Type().TerraformType(ctx), nil) } // TODO: Potentially remove this when Raw is changed to attr.Value or similar