diff --git a/.changelog/536.txt b/.changelog/536.txt new file mode 100644 index 000000000..468439c0b --- /dev/null +++ b/.changelog/536.txt @@ -0,0 +1,35 @@ +```release-note:enhancement +types: Added `BoolTypable` and `BoolValuable` interface types, which enable embedding existing boolean types for custom types +``` + +```release-note:enhancement +types: Added `Float64Typable` and `Float64Valuable` interface types, which enable embedding existing float64 types for custom types +``` + +```release-note:enhancement +types: Added `Int64Typable` and `Int64Valuable` interface types, which enable embedding existing int64 types for custom types +``` + +```release-note:enhancement +types: Added `ListTypable` and `ListValuable` interface types, which enable embedding existing list types for custom types +``` + +```release-note:enhancement +types: Added `MapTypable` and `MapValuable` interface types, which enable embedding existing map types for custom types +``` + +```release-note:enhancement +types: Added `NumberTypable` and `NumberValuable` interface types, which enable embedding existing number types for custom types +``` + +```release-note:enhancement +types: Added `ObjectTypable` and `ObjectValuable` interface types, which enable embedding existing object types for custom types +``` + +```release-note:enhancement +types: Added `SetTypable` and `SetValuable` interface types, which enable embedding existing set types for custom types +``` + +```release-note:enhancement +types: Added `StringTypable` and `StringValuable` interface types, which enable embedding existing string types for custom types +``` diff --git a/internal/fwserver/attr_value.go b/internal/fwserver/attr_value.go index 8ee6dd8e6..a2e855236 100644 --- a/internal/fwserver/attr_value.go +++ b/internal/fwserver/attr_value.go @@ -3,16 +3,17 @@ package fwserver import ( "context" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func coerceListValue(schemaPath path.Path, value attr.Value) (types.List, diag.Diagnostics) { - list, ok := value.(types.List) +func coerceListValue(ctx context.Context, schemaPath path.Path, value attr.Value) (types.List, diag.Diagnostics) { + listVal, ok := value.(types.ListValuable) if !ok { return types.ListNull(nil), diag.Diagnostics{ @@ -20,11 +21,11 @@ func coerceListValue(schemaPath path.Path, value attr.Value) (types.List, diag.D } } - return list, nil + return listVal.ToListValue(ctx) } -func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Diagnostics) { - m, ok := value.(types.Map) +func coerceMapValue(ctx context.Context, schemaPath path.Path, value attr.Value) (types.Map, diag.Diagnostics) { + mapVal, ok := value.(types.MapValuable) if !ok { return types.MapNull(nil), diag.Diagnostics{ @@ -32,11 +33,11 @@ func coerceMapValue(schemaPath path.Path, value attr.Value) (types.Map, diag.Dia } } - return m, nil + return mapVal.ToMapValue(ctx) } -func coerceObjectValue(schemaPath path.Path, value attr.Value) (types.Object, diag.Diagnostics) { - object, ok := value.(types.Object) +func coerceObjectValue(ctx context.Context, schemaPath path.Path, value attr.Value) (types.Object, diag.Diagnostics) { + objectVal, ok := value.(types.ObjectValuable) if !ok { return types.ObjectNull(nil), diag.Diagnostics{ @@ -44,11 +45,11 @@ func coerceObjectValue(schemaPath path.Path, value attr.Value) (types.Object, di } } - return object, nil + return objectVal.ToObjectValue(ctx) } -func coerceSetValue(schemaPath path.Path, value attr.Value) (types.Set, diag.Diagnostics) { - set, ok := value.(types.Set) +func coerceSetValue(ctx context.Context, schemaPath path.Path, value attr.Value) (types.Set, diag.Diagnostics) { + setVal, ok := value.(types.SetValuable) if !ok { return types.SetNull(nil), diag.Diagnostics{ @@ -56,7 +57,7 @@ func coerceSetValue(schemaPath path.Path, value attr.Value) (types.Set, diag.Dia } } - return set, nil + return setVal.ToSetValue(ctx) } func listElemObject(ctx context.Context, schemaPath path.Path, list types.List, index int, description fwschemadata.DataDescription) (types.Object, diag.Diagnostics) { @@ -72,7 +73,7 @@ func listElemObject(ctx context.Context, schemaPath path.Path, list types.List, return listElemObjectFromTerraformValue(ctx, schemaPath, list, description, nil) } - return coerceObjectValue(schemaPath, list.Elements()[index]) + return coerceObjectValue(ctx, schemaPath, list.Elements()[index]) } func listElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, list types.List, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) { @@ -85,7 +86,7 @@ func listElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, } } - return coerceObjectValue(schemaPath, elemValue) + return coerceObjectValue(ctx, schemaPath, elemValue) } func mapElemObject(ctx context.Context, schemaPath path.Path, m types.Map, key string, description fwschemadata.DataDescription) (types.Object, diag.Diagnostics) { @@ -103,7 +104,7 @@ func mapElemObject(ctx context.Context, schemaPath path.Path, m types.Map, key s return mapElemObjectFromTerraformValue(ctx, schemaPath, m, description, nil) } - return coerceObjectValue(schemaPath, elemValue) + return coerceObjectValue(ctx, schemaPath, elemValue) } func mapElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, m types.Map, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) { @@ -116,7 +117,7 @@ func mapElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, } } - return coerceObjectValue(schemaPath, elemValue) + return coerceObjectValue(ctx, schemaPath, elemValue) } func objectAttributeValue(ctx context.Context, object types.Object, attributeName string, description fwschemadata.DataDescription) (attr.Value, diag.Diagnostics) { @@ -162,7 +163,7 @@ func setElemObject(ctx context.Context, schemaPath path.Path, set types.Set, ind return setElemObjectFromTerraformValue(ctx, schemaPath, set, description, nil) } - return coerceObjectValue(schemaPath, set.Elements()[index]) + return coerceObjectValue(ctx, schemaPath, set.Elements()[index]) } func setElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, set types.Set, description fwschemadata.DataDescription, tfValue any) (types.Object, diag.Diagnostics) { @@ -175,5 +176,5 @@ func setElemObjectFromTerraformValue(ctx context.Context, schemaPath path.Path, } } - return coerceObjectValue(schemaPath, elemValue) + return coerceObjectValue(ctx, schemaPath, elemValue) } diff --git a/internal/fwserver/attribute_plan_modification.go b/internal/fwserver/attribute_plan_modification.go index c28f77f20..8dfd3f970 100644 --- a/internal/fwserver/attribute_plan_modification.go +++ b/internal/fwserver/attribute_plan_modification.go @@ -98,7 +98,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo nm := a.GetAttributes().GetNestingMode() switch nm { case fwschema.NestingModeList: - configList, diags := coerceListValue(req.AttributePath, req.AttributeConfig) + configList, diags := coerceListValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -106,7 +106,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planList, diags := coerceListValue(req.AttributePath, req.AttributePlan) + planList, diags := coerceListValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -114,7 +114,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - stateList, diags := coerceListValue(req.AttributePath, req.AttributeState) + stateList, diags := coerceListValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) @@ -135,7 +135,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planObject, diags := coerceObjectValue(attrPath, planElem) + planObject, diags := coerceObjectValue(ctx, attrPath, planElem) resp.Diagnostics.Append(diags...) @@ -220,7 +220,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } case fwschema.NestingModeSet: - configSet, diags := coerceSetValue(req.AttributePath, req.AttributeConfig) + configSet, diags := coerceSetValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -228,7 +228,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planSet, diags := coerceSetValue(req.AttributePath, req.AttributePlan) + planSet, diags := coerceSetValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -236,7 +236,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - stateSet, diags := coerceSetValue(req.AttributePath, req.AttributeState) + stateSet, diags := coerceSetValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) @@ -257,7 +257,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planObject, diags := coerceObjectValue(attrPath, planElem) + planObject, diags := coerceObjectValue(ctx, attrPath, planElem) resp.Diagnostics.Append(diags...) @@ -342,7 +342,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } case fwschema.NestingModeMap: - configMap, diags := coerceMapValue(req.AttributePath, req.AttributeConfig) + configMap, diags := coerceMapValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -350,7 +350,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planMap, diags := coerceMapValue(req.AttributePath, req.AttributePlan) + planMap, diags := coerceMapValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -358,7 +358,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - stateMap, diags := coerceMapValue(req.AttributePath, req.AttributeState) + stateMap, diags := coerceMapValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) @@ -379,7 +379,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planObject, diags := coerceObjectValue(attrPath, planElem) + planObject, diags := coerceObjectValue(ctx, attrPath, planElem) resp.Diagnostics.Append(diags...) @@ -464,7 +464,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } case fwschema.NestingModeSingle: - configObject, diags := coerceObjectValue(req.AttributePath, req.AttributeConfig) + configObject, diags := coerceObjectValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -472,7 +472,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - planObject, diags := coerceObjectValue(req.AttributePath, req.AttributePlan) + planObject, diags := coerceObjectValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -480,7 +480,7 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req tfsdk.Mo return } - stateObject, diags := coerceObjectValue(req.AttributePath, req.AttributeState) + stateObject, diags := coerceObjectValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) diff --git a/internal/fwserver/attribute_plan_modification_test.go b/internal/fwserver/attribute_plan_modification_test.go index e5a280517..8ca0a0fae 100644 --- a/internal/fwserver/attribute_plan_modification_test.go +++ b/internal/fwserver/attribute_plan_modification_test.go @@ -6,16 +6,18 @@ import ( "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/privatestate" "github.com/hashicorp/terraform-plugin-framework/internal/testing/planmodifiers" + testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) func TestAttributeModifyPlan(t *testing.T) { @@ -199,6 +201,103 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-list-nested-custom": { + attribute: tfsdk.Attribute{ + Attributes: testtypes.ListNestedAttributesCustomType{ + NestedAttributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: testtypes.ListNestedAttributesCustomValue{ + List: types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: testtypes.ListNestedAttributesCustomValue{ + List: types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributeState: testtypes.ListNestedAttributesCustomValue{ + List: types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.ListValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("MODIFIED_TWO"), + }, + ), + }, + ), + Private: testEmptyProviderData, + }, + }, "attribute-set-nested-private": { attribute: tfsdk.Attribute{ Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ @@ -290,6 +389,103 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-custom-set-nested": { + attribute: tfsdk.Attribute{ + Attributes: testtypes.SetNestedAttributesCustomType{ + NestedAttributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: testtypes.SetNestedAttributesCustomValue{ + Set: types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: testtypes.SetNestedAttributesCustomValue{ + Set: types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributeState: testtypes.SetNestedAttributesCustomValue{ + Set: types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.SetValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + []attr.Value{ + types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("MODIFIED_TWO"), + }, + ), + }, + ), + Private: testEmptyProviderData, + }, + }, "attribute-set-nested-usestateforunknown": { attribute: tfsdk.Attribute{ Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ @@ -525,6 +721,103 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-custom-map-nested": { + attribute: tfsdk.Attribute{ + Attributes: testtypes.MapNestedAttributesCustomType{ + NestedAttributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: testtypes.MapNestedAttributesCustomValue{ + Map: types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: testtypes.MapNestedAttributesCustomValue{ + Map: types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + AttributeState: testtypes.MapNestedAttributesCustomValue{ + Map: types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("TESTATTRONE"), + }, + ), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.MapValueMust( + types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + map[string]attr.Value{ + "testkey": types.ObjectValueMust( + map[string]attr.Type{ + "nested_attr": types.StringType, + }, + map[string]attr.Value{ + "nested_attr": types.StringValue("MODIFIED_TWO"), + }, + ), + }, + ), + Private: testEmptyProviderData, + }, + }, "attribute-single-nested-private": { attribute: tfsdk.Attribute{ Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ @@ -580,6 +873,67 @@ func TestAttributeModifyPlan(t *testing.T) { Private: testProviderData, }, }, + "attribute-custom-single-nested": { + attribute: tfsdk.Attribute{ + Attributes: testtypes.SingleNestedAttributesCustomType{ + NestedAttributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "testing": { + Type: types.StringType, + Optional: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + planmodifiers.TestAttrPlanValueModifierOne{}, + planmodifiers.TestAttrPlanValueModifierTwo{}, + }, + }, + }), + }, + Required: true, + }, + req: tfsdk.ModifyAttributePlanRequest{ + AttributeConfig: testtypes.SingleNestedAttributesCustomValue{ + Object: types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("TESTATTRONE"), + }, + ), + }, + AttributePath: path.Root("test"), + AttributePlan: testtypes.SingleNestedAttributesCustomValue{ + Object: types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("TESTATTRONE"), + }, + ), + }, + AttributeState: testtypes.SingleNestedAttributesCustomValue{ + Object: types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("TESTATTRONE"), + }, + ), + }, + }, + expectedResp: ModifyAttributePlanResponse{ + AttributePlan: types.ObjectValueMust( + map[string]attr.Type{ + "testing": types.StringType, + }, + map[string]attr.Value{ + "testing": types.StringValue("MODIFIED_TWO"), + }, + ), + Private: testEmptyProviderData, + }, + }, "requires-replacement": { attribute: tfsdk.Attribute{ Type: types.StringType, diff --git a/internal/fwserver/attribute_validation.go b/internal/fwserver/attribute_validation.go index b781053b4..8639ea188 100644 --- a/internal/fwserver/attribute_validation.go +++ b/internal/fwserver/attribute_validation.go @@ -140,19 +140,26 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute nm := a.GetAttributes().GetNestingMode() switch nm { case fwschema.NestingModeList: - l, ok := req.AttributeConfig.(types.List) + listVal, ok := req.AttributeConfig.(types.ListValuable) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Attribute Validation Error Invalid Value Type", + "A type that implements types.ListValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + l, diags := listVal.ToListValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + for idx := range l.Elements() { for nestedName, nestedAttr := range a.GetAttributes().GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ @@ -170,19 +177,26 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute } } case fwschema.NestingModeSet: - s, ok := req.AttributeConfig.(types.Set) + setVal, ok := req.AttributeConfig.(types.SetValuable) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Attribute Validation Error Invalid Value Type", + "A type that implements types.SetValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + s, diags := setVal.ToSetValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + for _, value := range s.Elements() { for nestedName, nestedAttr := range a.GetAttributes().GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ @@ -200,19 +214,26 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute } } case fwschema.NestingModeMap: - m, ok := req.AttributeConfig.(types.Map) + mapVal, ok := req.AttributeConfig.(types.MapValuable) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Attribute Validation Error Invalid Value Type", + "A type that implements types.MapValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + m, diags := mapVal.ToMapValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + for key := range m.Elements() { for nestedName, nestedAttr := range a.GetAttributes().GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ @@ -230,19 +251,26 @@ func AttributeValidateNestedAttributes(ctx context.Context, a fwschema.Attribute } } case fwschema.NestingModeSingle: - o, ok := req.AttributeConfig.(types.Object) + objectVal, ok := req.AttributeConfig.(types.ObjectValuable) if !ok { err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Attribute Validation Error Invalid Value Type", + "A type that implements types.ObjectValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + o, diags := objectVal.ToObjectValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + if o.IsNull() || o.IsUnknown() { return } diff --git a/internal/fwserver/attribute_validation_test.go b/internal/fwserver/attribute_validation_test.go index 423080c48..2ad7f03d2 100644 --- a/internal/fwserver/attribute_validation_test.go +++ b/internal/fwserver/attribute_validation_test.go @@ -665,6 +665,65 @@ func TestAttributeValidate(t *testing.T) { }, resp: tfsdk.ValidateAttributeResponse{}, }, + "nested-custom-attr-list-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.ListNestedAttributesCustomType{ + NestedAttributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, "nested-attr-list-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -729,6 +788,72 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-list-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.ListNestedAttributesCustomType{ + NestedAttributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, "nested-attr-map-no-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -786,6 +911,65 @@ func TestAttributeValidate(t *testing.T) { }, resp: tfsdk.ValidateAttributeResponse{}, }, + "nested-custom-attr-map-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "testkey": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.MapNestedAttributesCustomType{ + NestedAttributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, "nested-attr-map-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -850,6 +1034,72 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-map-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "testkey": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.MapNestedAttributesCustomType{ + NestedAttributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, "nested-attr-set-no-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -907,6 +1157,65 @@ func TestAttributeValidate(t *testing.T) { }, resp: tfsdk.ValidateAttributeResponse{}, }, + "nested-custom-attr-set-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.SetNestedAttributesCustomType{ + NestedAttributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, "nested-attr-set-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -971,6 +1280,72 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-set-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.SetNestedAttributesCustomType{ + NestedAttributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, "nested-attr-single-no-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -1015,6 +1390,52 @@ func TestAttributeValidate(t *testing.T) { }, resp: tfsdk.ValidateAttributeResponse{}, }, + "nested-custom-attr-single-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.SingleNestedAttributesCustomType{ + NestedAttributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, "nested-attr-single-validation": { req: tfsdk.ValidateAttributeRequest{ AttributePath: path.Root("test"), @@ -1065,6 +1486,58 @@ func TestAttributeValidate(t *testing.T) { }, }, }, + "nested-custom-attr-single-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: path.Root("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: testtypes.SingleNestedAttributesCustomType{ + NestedAttributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + }, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, } for name, tc := range testCases { diff --git a/internal/fwserver/block_plan_modification.go b/internal/fwserver/block_plan_modification.go index 453ba7add..ae935266c 100644 --- a/internal/fwserver/block_plan_modification.go +++ b/internal/fwserver/block_plan_modification.go @@ -64,7 +64,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr nm := b.GetNestingMode() switch nm { case fwschema.BlockNestingModeList: - configList, diags := coerceListValue(req.AttributePath, req.AttributeConfig) + configList, diags := coerceListValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -72,7 +72,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planList, diags := coerceListValue(req.AttributePath, req.AttributePlan) + planList, diags := coerceListValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -80,7 +80,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - stateList, diags := coerceListValue(req.AttributePath, req.AttributeState) + stateList, diags := coerceListValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) @@ -101,7 +101,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planObject, diags := coerceObjectValue(attrPath, planElem) + planObject, diags := coerceObjectValue(ctx, attrPath, planElem) resp.Diagnostics.Append(diags...) @@ -236,7 +236,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } case fwschema.BlockNestingModeSet: - configSet, diags := coerceSetValue(req.AttributePath, req.AttributeConfig) + configSet, diags := coerceSetValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -244,7 +244,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planSet, diags := coerceSetValue(req.AttributePath, req.AttributePlan) + planSet, diags := coerceSetValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -252,7 +252,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - stateSet, diags := coerceSetValue(req.AttributePath, req.AttributeState) + stateSet, diags := coerceSetValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) @@ -273,7 +273,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planObject, diags := coerceObjectValue(attrPath, planElem) + planObject, diags := coerceObjectValue(ctx, attrPath, planElem) resp.Diagnostics.Append(diags...) @@ -408,7 +408,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } case fwschema.BlockNestingModeSingle: - configObject, diags := coerceObjectValue(req.AttributePath, req.AttributeConfig) + configObject, diags := coerceObjectValue(ctx, req.AttributePath, req.AttributeConfig) resp.Diagnostics.Append(diags...) @@ -416,7 +416,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - planObject, diags := coerceObjectValue(req.AttributePath, req.AttributePlan) + planObject, diags := coerceObjectValue(ctx, req.AttributePath, req.AttributePlan) resp.Diagnostics.Append(diags...) @@ -424,7 +424,7 @@ func BlockModifyPlan(ctx context.Context, b fwschema.Block, req tfsdk.ModifyAttr return } - stateObject, diags := coerceObjectValue(req.AttributePath, req.AttributeState) + stateObject, diags := coerceObjectValue(ctx, req.AttributePath, req.AttributeState) resp.Diagnostics.Append(diags...) diff --git a/internal/fwserver/block_plan_modification_test.go b/internal/fwserver/block_plan_modification_test.go index a34f242dc..20a67d6e4 100644 --- a/internal/fwserver/block_plan_modification_test.go +++ b/internal/fwserver/block_plan_modification_test.go @@ -2013,7 +2013,7 @@ func TestBlockModifyPlan(t *testing.T) { type testBlockPlanModifierNullList struct{} func (t testBlockPlanModifierNullList) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { - _, ok := req.AttributePlan.(types.List) + _, ok := req.AttributePlan.(types.ListValuable) if !ok { return } diff --git a/internal/fwserver/block_validation.go b/internal/fwserver/block_validation.go index 77d929a86..cb5add730 100644 --- a/internal/fwserver/block_validation.go +++ b/internal/fwserver/block_validation.go @@ -49,19 +49,26 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req tfsdk.ValidateAttr nm := b.GetNestingMode() switch nm { case fwschema.BlockNestingModeList: - l, ok := req.AttributeConfig.(types.List) + listVal, ok := req.AttributeConfig.(types.ListValuable) if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + err := fmt.Errorf("unknown block value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Block Validation Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Block Validation Error Invalid Value Type", + "A type that implements types.ListValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + l, diags := listVal.ToListValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + for idx := range l.Elements() { for name, attr := range b.GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ @@ -117,19 +124,26 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req tfsdk.ValidateAttr resp.Diagnostics.Append(blockMinItemsDiagnostic(req.AttributePath, b.GetMinItems(), len(l.Elements()))) } case fwschema.BlockNestingModeSet: - s, ok := req.AttributeConfig.(types.Set) + setVal, ok := req.AttributeConfig.(types.SetValuable) if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + err := fmt.Errorf("unknown block value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Block Validation Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Block Validation Error Invalid Value Type", + "A type that implements types.SetValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + s, diags := setVal.ToSetValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + for _, value := range s.Elements() { for name, attr := range b.GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ @@ -185,19 +199,26 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req tfsdk.ValidateAttr resp.Diagnostics.Append(blockMinItemsDiagnostic(req.AttributePath, b.GetMinItems(), len(s.Elements()))) } case fwschema.BlockNestingModeSingle: - s, ok := req.AttributeConfig.(types.Object) + objectVal, ok := req.AttributeConfig.(types.ObjectValuable) if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + err := fmt.Errorf("unknown block value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) resp.Diagnostics.AddAttributeError( req.AttributePath, - "Block Validation Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + "Block Validation Error Invalid Value Type", + "A type that implements types.ObjectValuable is expected here. Report this to the provider developer:\n\n"+err.Error(), ) return } + o, diags := objectVal.ToObjectValue(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + for name, attr := range b.GetAttributes() { nestedAttrReq := tfsdk.ValidateAttributeRequest{ AttributePath: req.AttributePath.AtName(name), @@ -228,7 +249,7 @@ func BlockValidate(ctx context.Context, b fwschema.Block, req tfsdk.ValidateAttr resp.Diagnostics = nestedAttrResp.Diagnostics } - if b.GetMinItems() == 1 && s.IsNull() { + if b.GetMinItems() == 1 && o.IsNull() { resp.Diagnostics.Append(blockMinItemsDiagnostic(req.AttributePath, b.GetMinItems(), 0)) } default: diff --git a/internal/testing/types/listnestedattributescustom.go b/internal/testing/types/listnestedattributescustom.go new file mode 100644 index 000000000..91d3178e8 --- /dev/null +++ b/internal/testing/types/listnestedattributescustom.go @@ -0,0 +1,51 @@ +package types + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ types.ListTypable = ListNestedAttributesCustomTypeType{} + _ types.ListValuable = &ListNestedAttributesCustomValue{} +) + +type ListNestedAttributesCustomType struct { + fwschema.NestedAttributes +} + +func (t ListNestedAttributesCustomType) Type() attr.Type { + return ListNestedAttributesCustomTypeType{ + t.NestedAttributes.Type().(types.ListType), + } +} + +type ListNestedAttributesCustomTypeType struct { + types.ListType +} + +func (tt ListNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.ListType.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + list, ok := val.(types.List) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.List", val) + } + + return ListNestedAttributesCustomValue{ + list, + }, nil +} + +type ListNestedAttributesCustomValue struct { + types.List +} diff --git a/internal/testing/types/mapnestedattributescustom.go b/internal/testing/types/mapnestedattributescustom.go new file mode 100644 index 000000000..facef9032 --- /dev/null +++ b/internal/testing/types/mapnestedattributescustom.go @@ -0,0 +1,51 @@ +package types + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ types.MapTypable = MapNestedAttributesCustomTypeType{} + _ types.MapValuable = &MapNestedAttributesCustomValue{} +) + +type MapNestedAttributesCustomType struct { + fwschema.NestedAttributes +} + +func (t MapNestedAttributesCustomType) Type() attr.Type { + return MapNestedAttributesCustomTypeType{ + t.NestedAttributes.Type().(types.MapType), + } +} + +type MapNestedAttributesCustomTypeType struct { + types.MapType +} + +func (tt MapNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.MapType.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + m, ok := val.(types.Map) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.Map", val) + } + + return MapNestedAttributesCustomValue{ + m, + }, nil +} + +type MapNestedAttributesCustomValue struct { + types.Map +} diff --git a/internal/testing/types/setnestedattributescustom.go b/internal/testing/types/setnestedattributescustom.go new file mode 100644 index 000000000..5751ffabd --- /dev/null +++ b/internal/testing/types/setnestedattributescustom.go @@ -0,0 +1,51 @@ +package types + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ types.SetTypable = SetNestedAttributesCustomTypeType{} + _ types.SetValuable = &SetNestedAttributesCustomValue{} +) + +type SetNestedAttributesCustomType struct { + fwschema.NestedAttributes +} + +func (t SetNestedAttributesCustomType) Type() attr.Type { + return SetNestedAttributesCustomTypeType{ + t.NestedAttributes.Type().(types.SetType), + } +} + +type SetNestedAttributesCustomTypeType struct { + types.SetType +} + +func (tt SetNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.SetType.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + s, ok := val.(types.Set) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.Set", val) + } + + return SetNestedAttributesCustomValue{ + s, + }, nil +} + +type SetNestedAttributesCustomValue struct { + types.Set +} diff --git a/internal/testing/types/singlenestedattributescustom.go b/internal/testing/types/singlenestedattributescustom.go new file mode 100644 index 000000000..ff3654c65 --- /dev/null +++ b/internal/testing/types/singlenestedattributescustom.go @@ -0,0 +1,51 @@ +package types + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ types.SetTypable = SetNestedAttributesCustomTypeType{} + _ types.SetValuable = &SetNestedAttributesCustomValue{} +) + +type SingleNestedAttributesCustomType struct { + fwschema.NestedAttributes +} + +func (t SingleNestedAttributesCustomType) Type() attr.Type { + return SingleNestedAttributesCustomTypeType{ + t.NestedAttributes.Type().(types.ObjectType), + } +} + +type SingleNestedAttributesCustomTypeType struct { + types.ObjectType +} + +func (tt SingleNestedAttributesCustomTypeType) ValueFromTerraform(ctx context.Context, value tftypes.Value) (attr.Value, error) { + val, err := tt.ObjectType.ValueFromTerraform(ctx, value) + if err != nil { + return nil, err + } + + s, ok := val.(types.Object) + if !ok { + return nil, fmt.Errorf("cannot assert %T as types.Object", val) + } + + return SingleNestedAttributesCustomValue{ + s, + }, nil +} + +type SingleNestedAttributesCustomValue struct { + types.Object +} diff --git a/types/bool.go b/types/bool.go index e97beafa9..2b4bd5236 100644 --- a/types/bool.go +++ b/types/bool.go @@ -5,13 +5,24 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( - _ attr.Value = Bool{} + _ BoolValuable = Bool{} ) +// BoolValuable extends attr.Value for boolean value types. +// Implement this interface to create a custom Bool value type. +type BoolValuable interface { + attr.Value + + // ToBoolValue should convert the value type to a Bool. + ToBoolValue(ctx context.Context) (Bool, diag.Diagnostics) +} + // BoolNull creates a Bool with a null value. Determine whether the value is // null via the Bool type IsNull method. // @@ -143,3 +154,8 @@ func (b Bool) String() string { func (b Bool) ValueBool() bool { return b.value } + +// ToBoolValue returns Bool. +func (b Bool) ToBoolValue(context.Context) (Bool, diag.Diagnostics) { + return b, nil +} diff --git a/types/float64.go b/types/float64.go index 38382e2ac..cb5993a7a 100644 --- a/types/float64.go +++ b/types/float64.go @@ -5,16 +5,26 @@ import ( "fmt" "math/big" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( - _ attr.Value = Float64{} + _ Float64Valuable = Float64{} ) +// Float64Valuable extends attr.Value for float64 value types. +// Implement this interface to create a custom Float64 value type. +type Float64Valuable interface { + attr.Value + + // ToFloat64Value should convert the value type to a Float64. + ToFloat64Value(ctx context.Context) (Float64, diag.Diagnostics) +} + // Float64Null creates a Float64 with a null value. Determine whether the value is // null via the Float64 type IsNull method. // @@ -204,3 +214,8 @@ func (f Float64) String() string { func (f Float64) ValueFloat64() float64 { return f.value } + +// ToFloat64Value returns Float64. +func (f Float64) ToFloat64Value(context.Context) (Float64, diag.Diagnostics) { + return f, nil +} diff --git a/types/int64.go b/types/int64.go index cda43fbe0..bae5a6e87 100644 --- a/types/int64.go +++ b/types/int64.go @@ -5,16 +5,26 @@ import ( "fmt" "math/big" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var ( - _ attr.Value = Int64{} + _ Int64Valuable = Int64{} ) +// Int64Valuable extends attr.Value for int64 value types. +// Implement this interface to create a custom Int64 value type. +type Int64Valuable interface { + attr.Value + + // ToInt64Value should convert the value type to an Int64. + ToInt64Value(ctx context.Context) (Int64, diag.Diagnostics) +} + // Int64Null creates a Int64 with a null value. Determine whether the value is // null via the Int64 type IsNull method. // @@ -217,3 +227,8 @@ func (i Int64) String() string { func (i Int64) ValueInt64() int64 { return i.value } + +// ToInt64Value returns Int64. +func (i Int64) ToInt64Value(context.Context) (Int64, diag.Diagnostics) { + return i, nil +} diff --git a/types/list.go b/types/list.go index 1e33fd5ca..31c40232e 100644 --- a/types/list.go +++ b/types/list.go @@ -15,10 +15,28 @@ import ( ) var ( - _ attr.Type = ListType{} - _ attr.Value = &List{} + _ ListTypable = ListType{} + _ ListValuable = &List{} ) +// ListTypable extends attr.Type for list types. +// Implement this interface to create a custom ListType type. +type ListTypable interface { + attr.Type + + // ValueFromList should convert the List to a ListValuable type. + ValueFromList(context.Context, List) (ListValuable, diag.Diagnostics) +} + +// ListValuable extends attr.Value for list value types. +// Implement this interface to create a custom List value type. +type ListValuable interface { + attr.Value + + // ToListValue should convert the value type to a List. + ToListValue(ctx context.Context) (List, diag.Diagnostics) +} + // ListType is an AttributeType representing a list of values. All values must // be of the same type, which the provider must specify as the ElemType // property. @@ -159,12 +177,17 @@ func (l ListType) Validate(ctx context.Context, in tftypes.Value, path path.Path } // ValueType returns the Value type. -func (t ListType) ValueType(_ context.Context) attr.Value { +func (l ListType) ValueType(_ context.Context) attr.Value { return List{ - elementType: t.ElemType, + elementType: l.ElemType, } } +// ValueFromList returns a ListValuable type given a List. +func (l ListType) ValueFromList(_ context.Context, list List) (ListValuable, diag.Diagnostics) { + return list, nil +} + // ListNull creates a List with a null value. Determine whether the value is // null via the List type IsNull method. func ListNull(elementType attr.Type) List { @@ -249,7 +272,7 @@ func ListValueFrom(ctx context.Context, elementType attr.Type, elements any) (Li // type Elements or ElementsAs methods. // // This creation function is only recommended to create List values which will -// not potentially effect practitioners, such as testing, or exhaustively +// not potentially affect practitioners, such as testing, or exhaustively // tested provider logic. func ListValueMust(elementType attr.Type, elements []attr.Value) List { list, diags := ListValue(elementType, elements) @@ -427,3 +450,8 @@ func (l List) String() string { return res.String() } + +// ToListValue returns the List. +func (l List) ToListValue(context.Context) (List, diag.Diagnostics) { + return l, nil +} diff --git a/types/map.go b/types/map.go index a9c1443c0..990407462 100644 --- a/types/map.go +++ b/types/map.go @@ -16,10 +16,28 @@ import ( ) var ( - _ attr.Type = MapType{} - _ attr.Value = &Map{} + _ MapTypable = MapType{} + _ MapValuable = &Map{} ) +// MapTypable extends attr.Type for map types. +// Implement this interface to create a custom MapType type. +type MapTypable interface { + attr.Type + + // ValueFromMap should convert the Map to a MapValuable type. + ValueFromMap(context.Context, Map) (MapValuable, diag.Diagnostics) +} + +// MapValuable extends attr.Value for map value types. +// Implement this interface to create a custom Map value type. +type MapValuable interface { + attr.Value + + // ToMapValue should convert the value type to a Map. + ToMapValue(ctx context.Context) (Map, diag.Diagnostics) +} + // MapType is an AttributeType representing a map of values. All values must // be of the same type, which the provider must specify as the ElemType // property. Keys will always be strings. @@ -163,12 +181,17 @@ func (m MapType) Validate(ctx context.Context, in tftypes.Value, path path.Path) } // ValueType returns the Value type. -func (t MapType) ValueType(_ context.Context) attr.Value { +func (m MapType) ValueType(_ context.Context) attr.Value { return Map{ - elementType: t.ElemType, + elementType: m.ElemType, } } +// ValueFromMap returns a MapValuable type given a Map. +func (m MapType) ValueFromMap(_ context.Context, ma Map) (MapValuable, diag.Diagnostics) { + return ma, nil +} + // MapNull creates a Map with a null value. Determine whether the value is // null via the Map type IsNull method. func MapNull(elementType attr.Type) Map { @@ -329,7 +352,7 @@ func (m Map) Type(ctx context.Context) attr.Type { return MapType{ElemType: m.ElementType(ctx)} } -// ToTerraformValue returns the data contained in the List as a tftypes.Value. +// ToTerraformValue returns the data contained in the Map as a tftypes.Value. func (m Map) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { mapType := tftypes.Map{ElementType: m.ElementType(ctx).TerraformType(ctx)} @@ -441,3 +464,8 @@ func (m Map) String() string { return res.String() } + +// ToMapValue returns the Map. +func (m Map) ToMapValue(context.Context) (Map, diag.Diagnostics) { + return m, nil +} diff --git a/types/number.go b/types/number.go index bac347d15..bfaba3a6d 100644 --- a/types/number.go +++ b/types/number.go @@ -5,14 +5,25 @@ import ( "fmt" "math/big" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" ) var ( - _ attr.Value = Number{} + _ NumberValuable = Number{} ) +// NumberValuable extends attr.Value for number value types. +// Implement this interface to create a custom Number value type. +type NumberValuable interface { + attr.Value + + // ToNumberValue should convert the value type to a Number. + ToNumberValue(ctx context.Context) (Number, diag.Diagnostics) +} + // NumberNull creates a Number with a null value. Determine whether the value is // null via the Number type IsNull method. // @@ -153,3 +164,8 @@ func (n Number) String() string { func (n Number) ValueBigFloat() *big.Float { return n.value } + +// ToNumberValue returns Number. +func (n Number) ToNumberValue(context.Context) (Number, diag.Diagnostics) { + return n, nil +} diff --git a/types/object.go b/types/object.go index f834be36b..71cd7155b 100644 --- a/types/object.go +++ b/types/object.go @@ -15,10 +15,28 @@ import ( ) var ( - _ attr.Type = ObjectType{} - _ attr.Value = &Object{} + _ ObjectTypable = ObjectType{} + _ ObjectValuable = &Object{} ) +// ObjectTypable extends attr.Type for object types. +// Implement this interface to create a custom ObjectType type. +type ObjectTypable interface { + attr.Type + + // ValueFromObject should convert the Object to an ObjectValuable type. + ValueFromObject(context.Context, Object) (ObjectValuable, diag.Diagnostics) +} + +// ObjectValuable extends attr.Value for object value types. +// Implement this interface to create a custom Object value type. +type ObjectValuable interface { + attr.Value + + // ToObjectValue should convert the value type to an Object. + ToObjectValue(ctx context.Context) (Object, diag.Diagnostics) +} + // ObjectType is an AttributeType representing an object. type ObjectType struct { AttrTypes map[string]attr.Type @@ -141,12 +159,17 @@ func (o ObjectType) String() string { } // ValueType returns the Value type. -func (t ObjectType) ValueType(_ context.Context) attr.Value { +func (o ObjectType) ValueType(_ context.Context) attr.Value { return Object{ - attributeTypes: t.AttrTypes, + attributeTypes: o.AttrTypes, } } +// ValueFromObject returns an ObjectValuable type given an Object. +func (o ObjectType) ValueFromObject(_ context.Context, obj Object) (ObjectValuable, diag.Diagnostics) { + return obj, nil +} + // ObjectNull creates a Object with a null value. Determine whether the value is // null via the Object type IsNull method. func ObjectNull(attributeTypes map[string]attr.Type) Object { @@ -480,3 +503,8 @@ func (o Object) String() string { return res.String() } + +// ToObjectValue returns the Object. +func (o Object) ToObjectValue(context.Context) (Object, diag.Diagnostics) { + return o, nil +} diff --git a/types/primitive.go b/types/primitive.go index bdadd153f..7f917f396 100644 --- a/types/primitive.go +++ b/types/primitive.go @@ -4,11 +4,12 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr/xattr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) type primitive uint8 @@ -31,13 +32,83 @@ const ( ) var ( - _ attr.Type = StringType - _ attr.Type = NumberType - _ attr.Type = BoolType - _ xattr.TypeWithValidate = Int64Type - _ xattr.TypeWithValidate = Float64Type + _ StringTypable = StringType + _ NumberTypable = NumberType + _ BoolTypable = BoolType + _ Int64Typable = Int64Type + _ Float64Typable = Float64Type ) +// StringTypable extends attr.Type for string types. +// Implement this interface to create a custom StringType type. +type StringTypable interface { + attr.Type + + // ValueFromString should convert the String to a StringValuable type. + ValueFromString(context.Context, String) (StringValuable, diag.Diagnostics) +} + +// NumberTypable extends attr.Type for number types. +// Implement this interface to create a custom NumberType type. +type NumberTypable interface { + attr.Type + + // ValueFromNumber should convert the Number to a NumberValuable type. + ValueFromNumber(context.Context, Number) (NumberValuable, diag.Diagnostics) +} + +// BoolTypable extends attr.Type for bool types. +// Implement this interface to create a custom BoolType type. +type BoolTypable interface { + attr.Type + + // ValueFromBool should convert the Bool to a BoolValuable type. + ValueFromBool(context.Context, Bool) (BoolValuable, diag.Diagnostics) +} + +// Int64Typable extends attr.Type for int64 types. +// Implement this interface to create a custom Int64Type type. +type Int64Typable interface { + xattr.TypeWithValidate + + // ValueFromInt64 should convert the Int64 to a Int64Valuable type. + ValueFromInt64(context.Context, Int64) (Int64Valuable, diag.Diagnostics) +} + +// Float64Typable extends attr.Type for float64 types. +// Implement this interface to create a custom Float64Type type. +type Float64Typable interface { + xattr.TypeWithValidate + + // ValueFromFloat64 should convert the Float64 to a Float64Valuable type. + ValueFromFloat64(context.Context, Float64) (Float64Valuable, diag.Diagnostics) +} + +// ValueFromString returns a StringValuable type given a String. +func (p primitive) ValueFromString(_ context.Context, s String) (StringValuable, diag.Diagnostics) { + return s, nil +} + +// ValueFromNumber returns a NumberValuable type given a Number. +func (p primitive) ValueFromNumber(_ context.Context, n Number) (NumberValuable, diag.Diagnostics) { + return n, nil +} + +// ValueFromBool returns a BoolValuable type given a Bool. +func (p primitive) ValueFromBool(_ context.Context, b Bool) (BoolValuable, diag.Diagnostics) { + return b, nil +} + +// ValueFromInt64 returns an Int64Valuable type given an Int64. +func (p primitive) ValueFromInt64(_ context.Context, i Int64) (Int64Valuable, diag.Diagnostics) { + return i, nil +} + +// ValueFromFloat64 returns a Float64Valuable type given a Float64. +func (p primitive) ValueFromFloat64(_ context.Context, f Float64) (Float64Valuable, diag.Diagnostics) { + return f, nil +} + func (p primitive) String() string { switch p { case StringType: diff --git a/types/set.go b/types/set.go index 18c386d71..c135675c7 100644 --- a/types/set.go +++ b/types/set.go @@ -15,11 +15,29 @@ import ( ) var ( - _ attr.Type = SetType{} + _ SetTypable = SetType{} _ xattr.TypeWithValidate = SetType{} - _ attr.Value = &Set{} + _ SetValuable = &Set{} ) +// SetTypable extends attr.Type for set types. +// Implement this interface to create a custom SetType type. +type SetTypable interface { + attr.Type + + // ValueFromSet should convert the Set to a SetValuable type. + ValueFromSet(context.Context, Set) (SetValuable, diag.Diagnostics) +} + +// SetValuable extends attr.Value for set value types. +// Implement this interface to create a custom Set value type. +type SetValuable interface { + attr.Value + + // ToSetValue should convert the value type to a Set. + ToSetValue(ctx context.Context) (Set, diag.Diagnostics) +} + // SetType is an AttributeType representing a set of values. All values must // be of the same type, which the provider must specify as the ElemType // property. @@ -191,12 +209,17 @@ func (st SetType) Validate(ctx context.Context, in tftypes.Value, path path.Path } // ValueType returns the Value type. -func (t SetType) ValueType(_ context.Context) attr.Value { +func (st SetType) ValueType(_ context.Context) attr.Value { return Set{ - elementType: t.ElemType, + elementType: st.ElemType, } } +// ValueFromSet returns a SetValuable type given a Set. +func (st SetType) ValueFromSet(_ context.Context, set Set) (SetValuable, diag.Diagnostics) { + return set, nil +} + // SetNull creates a Set with a null value. Determine whether the value is // null via the Set type IsNull method. func SetNull(elementType attr.Type) Set { @@ -467,3 +490,8 @@ func (s Set) String() string { return res.String() } + +// ToSetValue returns the Set. +func (s Set) ToSetValue(context.Context) (Set, diag.Diagnostics) { + return s, nil +} diff --git a/types/string.go b/types/string.go index 9239cb403..e99679a92 100644 --- a/types/string.go +++ b/types/string.go @@ -4,14 +4,25 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" ) var ( - _ attr.Value = String{} + _ StringValuable = String{} ) +// StringValuable extends attr.Value for string value types. +// Implement this interface to create a custom String value type. +type StringValuable interface { + attr.Value + + // ToStringValue should convert the value type to a String. + ToStringValue(ctx context.Context) (String, diag.Diagnostics) +} + // StringNull creates a String with a null value. Determine whether the value is // null via the String type IsNull method. // @@ -145,3 +156,8 @@ func (s String) String() string { func (s String) ValueString() string { return s.value } + +// ToStringValue returns String. +func (s String) ToStringValue(context.Context) (String, diag.Diagnostics) { + return s, nil +}