From 99e04d26f1e62856d8f436d89bced6bb97526d2a Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 19 Oct 2023 14:01:40 +0100 Subject: [PATCH] Generate custom type and value types and to/from methods for primitives (#59) * Refactoring to move generation of to/from functions to attributes and blocks * Moving schema.gotmpl * Renaming interface method * Removing unneeded template variable * Renaming method * Renaming methods * Inline function * Renaming * Adding tests for to/from method generated code * Adding initial implementation of custom type and value types for data source bool attribute along with To/From method generation * Updating data source attributes to use generated type and value type in schema and models if associated external type is defined * Populating data source associated external type * Setting up generation of custom type and value types and to/from functions for data source float64, int64, number, and string attributes * Setting up generation of custom type and value types and to/from functions for provider and resource bool, float64, int64, number, and string attributes * Adding test coverage for associated external type * Temporarily using sha for codegen-spec * Adding changelog (#70) * Adding tests for custom string type and value, and to/from methods (#70) * Using latest codegen-spec SHA from main (#70) --- .../ENHANCEMENTS-20231018-094607.yaml | 6 + go.mod | 2 +- go.sum | 4 +- internal/datasource_convert/bool_attribute.go | 6 +- .../datasource_convert/float64_attribute.go | 6 +- .../datasource_convert/int64_attribute.go | 6 +- .../datasource_convert/number_attribute.go | 6 +- .../datasource_convert/string_attribute.go | 6 +- .../datasource_generate/bool_attribute.go | 73 ++- .../bool_attribute_test.go | 166 +++++++ internal/datasource_generate/embed.go | 21 +- .../datasource_generate/float64_attribute.go | 72 ++- .../float64_attribute_test.go | 63 +++ .../datasource_generate/int64_attribute.go | 72 ++- .../int64_attribute_test.go | 63 +++ .../datasource_generate/number_attribute.go | 72 ++- .../number_attribute_test.go | 63 +++ .../datasource_generate/string_attribute.go | 72 ++- .../string_attribute_test.go | 63 +++ .../templates/attribute.gotmpl | 26 + .../templates/bool_attribute.gotmpl | 3 + .../templates/float64_attribute.gotmpl | 3 + .../templates/int64_attribute.gotmpl | 3 + .../templates/number_attribute.gotmpl | 3 + .../templates/string_attribute.gotmpl | 3 + internal/provider_convert/bool_attribute.go | 7 +- .../provider_convert/float64_attribute.go | 7 +- internal/provider_convert/int64_attribute.go | 7 +- internal/provider_convert/number_attribute.go | 7 +- internal/provider_convert/string_attribute.go | 7 +- internal/provider_generate/bool_attribute.go | 73 ++- .../provider_generate/bool_attribute_test.go | 204 +++++++- internal/provider_generate/embed.go | 21 +- .../provider_generate/float64_attribute.go | 72 ++- .../float64_attribute_test.go | 85 +++- internal/provider_generate/int64_attribute.go | 72 ++- .../provider_generate/int64_attribute_test.go | 85 +++- .../provider_generate/number_attribute.go | 101 ++-- .../number_attribute_test.go | 85 +++- .../provider_generate/string_attribute.go | 72 ++- .../string_attribute_test.go | 85 +++- .../templates/attribute.gotmpl | 22 + .../templates/bool_attribute.gotmpl | 3 + .../templates/float64_attribute.gotmpl | 3 + .../templates/int64_attribute.gotmpl | 3 + .../templates/number_attribute.gotmpl | 3 + .../templates/string_attribute.gotmpl | 3 + internal/resource_convert/bool_attribute.go | 11 +- .../resource_convert/float64_attribute.go | 11 +- internal/resource_convert/int64_attribute.go | 11 +- internal/resource_convert/number_attribute.go | 11 +- internal/resource_convert/string_attribute.go | 11 +- internal/resource_generate/bool_attribute.go | 72 ++- .../resource_generate/bool_attribute_test.go | 166 +++++++ internal/resource_generate/embed.go | 21 +- .../resource_generate/float64_attribute.go | 71 ++- .../float64_attribute_test.go | 63 +++ internal/resource_generate/int64_attribute.go | 71 ++- .../resource_generate/int64_attribute_test.go | 63 +++ .../resource_generate/number_attribute.go | 72 ++- .../number_attribute_test.go | 63 +++ .../resource_generate/string_attribute.go | 71 ++- .../string_attribute_test.go | 63 +++ .../templates/attribute.gotmpl | 26 + .../templates/bool_attribute.gotmpl | 3 + .../templates/float64_attribute.gotmpl | 3 + .../templates/int64_attribute.gotmpl | 3 + .../templates/number_attribute.gotmpl | 3 + .../templates/string_attribute.gotmpl | 3 + internal/schema/custom_bool.go | 345 +++++++++++++ internal/schema/custom_bool_test.go | 460 ++++++++++++++++++ internal/schema/custom_float64.go | 345 +++++++++++++ internal/schema/custom_float64_test.go | 460 ++++++++++++++++++ internal/schema/custom_int64.go | 345 +++++++++++++ internal/schema/custom_int64_test.go | 460 ++++++++++++++++++ internal/schema/custom_number.go | 345 +++++++++++++ internal/schema/custom_number_test.go | 460 ++++++++++++++++++ internal/schema/custom_string.go | 345 +++++++++++++ internal/schema/custom_string_test.go | 460 ++++++++++++++++++ internal/schema/embed.go | 231 +++++++++ internal/schema/import.go | 24 + internal/schema/templates/bool_from.gotmpl | 14 + internal/schema/templates/bool_to.gotmpl | 20 + .../schema/templates/bool_type_equal.gotmpl | 9 + .../schema/templates/bool_type_string.gotmpl | 4 + .../schema/templates/bool_type_typable.gotmpl | 1 + .../schema/templates/bool_type_type.gotmpl | 3 + .../bool_type_value_from_bool.gotmpl | 6 + .../bool_type_value_from_terraform.gotmpl | 22 + .../templates/bool_type_value_type.gotmpl | 4 + .../schema/templates/bool_value_equal.gotmpl | 10 + .../schema/templates/bool_value_type.gotmpl | 5 + .../templates/bool_value_valuable.gotmpl | 1 + .../schema/templates/bool_value_value.gotmpl | 3 + internal/schema/templates/float64_from.gotmpl | 14 + internal/schema/templates/float64_to.gotmpl | 20 + .../templates/float64_type_equal.gotmpl | 9 + .../templates/float64_type_string.gotmpl | 4 + .../templates/float64_type_typable.gotmpl | 1 + .../schema/templates/float64_type_type.gotmpl | 3 + .../float64_type_value_from_float64.gotmpl | 6 + .../float64_type_value_from_terraform.gotmpl | 22 + .../templates/float64_type_value_type.gotmpl | 4 + .../templates/float64_value_equal.gotmpl | 10 + .../templates/float64_value_type.gotmpl | 5 + .../templates/float64_value_valuable.gotmpl | 1 + .../templates/float64_value_value.gotmpl | 3 + internal/schema/templates/int64_from.gotmpl | 14 + internal/schema/templates/int64_to.gotmpl | 20 + .../schema/templates/int64_type_equal.gotmpl | 9 + .../schema/templates/int64_type_string.gotmpl | 4 + .../templates/int64_type_typable.gotmpl | 1 + .../schema/templates/int64_type_type.gotmpl | 3 + .../int64_type_value_from_int64.gotmpl | 6 + .../int64_type_value_from_terraform.gotmpl | 22 + .../templates/int64_type_value_type.gotmpl | 4 + .../schema/templates/int64_value_equal.gotmpl | 10 + .../schema/templates/int64_value_type.gotmpl | 5 + .../templates/int64_value_valuable.gotmpl | 1 + .../schema/templates/int64_value_value.gotmpl | 3 + internal/schema/templates/number_from.gotmpl | 14 + internal/schema/templates/number_to.gotmpl | 20 + .../schema/templates/number_type_equal.gotmpl | 9 + .../templates/number_type_string.gotmpl | 4 + .../templates/number_type_typable.gotmpl | 1 + .../schema/templates/number_type_type.gotmpl | 3 + .../number_type_value_from_number.gotmpl | 6 + .../number_type_value_from_terraform.gotmpl | 22 + .../templates/number_type_value_type.gotmpl | 4 + .../templates/number_value_equal.gotmpl | 10 + .../schema/templates/number_value_type.gotmpl | 5 + .../templates/number_value_valuable.gotmpl | 1 + .../templates/number_value_value.gotmpl | 3 + internal/schema/templates/string_from.gotmpl | 14 + internal/schema/templates/string_to.gotmpl | 20 + .../schema/templates/string_type_equal.gotmpl | 9 + .../templates/string_type_string.gotmpl | 4 + .../templates/string_type_typable.gotmpl | 1 + .../schema/templates/string_type_type.gotmpl | 3 + .../string_type_value_from_string.gotmpl | 6 + .../string_type_value_from_terraform.gotmpl | 22 + .../templates/string_type_value_type.gotmpl | 4 + .../templates/string_value_equal.gotmpl | 10 + .../schema/templates/string_value_type.gotmpl | 5 + .../templates/string_value_valuable.gotmpl | 1 + .../templates/string_value_value.gotmpl | 3 + internal/schema/to_from_bool.go | 99 ++++ internal/schema/to_from_bool_test.go | 133 +++++ internal/schema/to_from_float64.go | 99 ++++ internal/schema/to_from_float64_test.go | 133 +++++ internal/schema/to_from_int64.go | 99 ++++ internal/schema/to_from_int64_test.go | 133 +++++ internal/schema/to_from_number.go | 99 ++++ internal/schema/to_from_number_test.go | 133 +++++ internal/schema/to_from_string.go | 99 ++++ internal/schema/to_from_string_test.go | 133 +++++ 156 files changed, 8560 insertions(+), 192 deletions(-) create mode 100644 .changes/unreleased/ENHANCEMENTS-20231018-094607.yaml create mode 100644 internal/datasource_generate/templates/attribute.gotmpl create mode 100644 internal/provider_generate/templates/attribute.gotmpl create mode 100644 internal/resource_generate/templates/attribute.gotmpl create mode 100644 internal/schema/custom_bool.go create mode 100644 internal/schema/custom_bool_test.go create mode 100644 internal/schema/custom_float64.go create mode 100644 internal/schema/custom_float64_test.go create mode 100644 internal/schema/custom_int64.go create mode 100644 internal/schema/custom_int64_test.go create mode 100644 internal/schema/custom_number.go create mode 100644 internal/schema/custom_number_test.go create mode 100644 internal/schema/custom_string.go create mode 100644 internal/schema/custom_string_test.go create mode 100644 internal/schema/templates/bool_from.gotmpl create mode 100644 internal/schema/templates/bool_to.gotmpl create mode 100644 internal/schema/templates/bool_type_equal.gotmpl create mode 100644 internal/schema/templates/bool_type_string.gotmpl create mode 100644 internal/schema/templates/bool_type_typable.gotmpl create mode 100644 internal/schema/templates/bool_type_type.gotmpl create mode 100644 internal/schema/templates/bool_type_value_from_bool.gotmpl create mode 100644 internal/schema/templates/bool_type_value_from_terraform.gotmpl create mode 100644 internal/schema/templates/bool_type_value_type.gotmpl create mode 100644 internal/schema/templates/bool_value_equal.gotmpl create mode 100644 internal/schema/templates/bool_value_type.gotmpl create mode 100644 internal/schema/templates/bool_value_valuable.gotmpl create mode 100644 internal/schema/templates/bool_value_value.gotmpl create mode 100644 internal/schema/templates/float64_from.gotmpl create mode 100644 internal/schema/templates/float64_to.gotmpl create mode 100644 internal/schema/templates/float64_type_equal.gotmpl create mode 100644 internal/schema/templates/float64_type_string.gotmpl create mode 100644 internal/schema/templates/float64_type_typable.gotmpl create mode 100644 internal/schema/templates/float64_type_type.gotmpl create mode 100644 internal/schema/templates/float64_type_value_from_float64.gotmpl create mode 100644 internal/schema/templates/float64_type_value_from_terraform.gotmpl create mode 100644 internal/schema/templates/float64_type_value_type.gotmpl create mode 100644 internal/schema/templates/float64_value_equal.gotmpl create mode 100644 internal/schema/templates/float64_value_type.gotmpl create mode 100644 internal/schema/templates/float64_value_valuable.gotmpl create mode 100644 internal/schema/templates/float64_value_value.gotmpl create mode 100644 internal/schema/templates/int64_from.gotmpl create mode 100644 internal/schema/templates/int64_to.gotmpl create mode 100644 internal/schema/templates/int64_type_equal.gotmpl create mode 100644 internal/schema/templates/int64_type_string.gotmpl create mode 100644 internal/schema/templates/int64_type_typable.gotmpl create mode 100644 internal/schema/templates/int64_type_type.gotmpl create mode 100644 internal/schema/templates/int64_type_value_from_int64.gotmpl create mode 100644 internal/schema/templates/int64_type_value_from_terraform.gotmpl create mode 100644 internal/schema/templates/int64_type_value_type.gotmpl create mode 100644 internal/schema/templates/int64_value_equal.gotmpl create mode 100644 internal/schema/templates/int64_value_type.gotmpl create mode 100644 internal/schema/templates/int64_value_valuable.gotmpl create mode 100644 internal/schema/templates/int64_value_value.gotmpl create mode 100644 internal/schema/templates/number_from.gotmpl create mode 100644 internal/schema/templates/number_to.gotmpl create mode 100644 internal/schema/templates/number_type_equal.gotmpl create mode 100644 internal/schema/templates/number_type_string.gotmpl create mode 100644 internal/schema/templates/number_type_typable.gotmpl create mode 100644 internal/schema/templates/number_type_type.gotmpl create mode 100644 internal/schema/templates/number_type_value_from_number.gotmpl create mode 100644 internal/schema/templates/number_type_value_from_terraform.gotmpl create mode 100644 internal/schema/templates/number_type_value_type.gotmpl create mode 100644 internal/schema/templates/number_value_equal.gotmpl create mode 100644 internal/schema/templates/number_value_type.gotmpl create mode 100644 internal/schema/templates/number_value_valuable.gotmpl create mode 100644 internal/schema/templates/number_value_value.gotmpl create mode 100644 internal/schema/templates/string_from.gotmpl create mode 100644 internal/schema/templates/string_to.gotmpl create mode 100644 internal/schema/templates/string_type_equal.gotmpl create mode 100644 internal/schema/templates/string_type_string.gotmpl create mode 100644 internal/schema/templates/string_type_typable.gotmpl create mode 100644 internal/schema/templates/string_type_type.gotmpl create mode 100644 internal/schema/templates/string_type_value_from_string.gotmpl create mode 100644 internal/schema/templates/string_type_value_from_terraform.gotmpl create mode 100644 internal/schema/templates/string_type_value_type.gotmpl create mode 100644 internal/schema/templates/string_value_equal.gotmpl create mode 100644 internal/schema/templates/string_value_type.gotmpl create mode 100644 internal/schema/templates/string_value_valuable.gotmpl create mode 100644 internal/schema/templates/string_value_value.gotmpl create mode 100644 internal/schema/to_from_bool.go create mode 100644 internal/schema/to_from_bool_test.go create mode 100644 internal/schema/to_from_float64.go create mode 100644 internal/schema/to_from_float64_test.go create mode 100644 internal/schema/to_from_int64.go create mode 100644 internal/schema/to_from_int64_test.go create mode 100644 internal/schema/to_from_number.go create mode 100644 internal/schema/to_from_number_test.go create mode 100644 internal/schema/to_from_string.go create mode 100644 internal/schema/to_from_string_test.go diff --git a/.changes/unreleased/ENHANCEMENTS-20231018-094607.yaml b/.changes/unreleased/ENHANCEMENTS-20231018-094607.yaml new file mode 100644 index 00000000..acda3bc7 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20231018-094607.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: Adds code generation for Bool, Float64, Int64, Number, and String attributes + that have an associated external type +time: 2023-10-18T09:46:07.467187+01:00 +custom: + Issue: "59" diff --git a/go.mod b/go.mod index 90955135..c2d70b7b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-codegen-spec v0.1.0 + github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231019064449-867ccf6fb279 github.com/hashicorp/terraform-plugin-framework v1.4.0 github.com/mattn/go-colorable v0.1.12 github.com/mitchellh/cli v1.1.5 diff --git a/go.sum b/go.sum index 9334ca5a..6494d33a 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+ github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/terraform-plugin-codegen-spec v0.1.0 h1:flL5dprli2h54RxewQi6po02am0zXDRq6nsV6c4WQ/I= -github.com/hashicorp/terraform-plugin-codegen-spec v0.1.0/go.mod h1:PQn6bDD8UWoAVJoHXqFk2i/RmLbeQBjbiP38i+E+YIw= +github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231019064449-867ccf6fb279 h1:9D8ydY5s8Fw76pqCFtwlm9X7wVJdFLMK2FAqyQoOZT0= +github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231019064449-867ccf6fb279/go.mod h1:PQn6bDD8UWoAVJoHXqFk2i/RmLbeQBjbiP38i+E+YIw= github.com/hashicorp/terraform-plugin-framework v1.4.0 h1:WKbtCRtNrjsh10eA7NZvC/Qyr7zp77j+D21aDO5th9c= github.com/hashicorp/terraform-plugin-framework v1.4.0/go.mod h1:XC0hPcQbBvlbxwmjxuV/8sn8SbZRg4XwGMs22f+kqV0= github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU= diff --git a/internal/datasource_convert/bool_attribute.go b/internal/datasource_convert/bool_attribute.go index 7e9ae35d..55d82b4a 100644 --- a/internal/datasource_convert/bool_attribute.go +++ b/internal/datasource_convert/bool_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/datasource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertBoolAttribute(a *datasource.BoolAttribute) (datasource_generate.GeneratorBoolAttribute, error) { @@ -28,7 +29,8 @@ func convertBoolAttribute(a *datasource.BoolAttribute) (datasource_generate.Gene DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/float64_attribute.go b/internal/datasource_convert/float64_attribute.go index ba3be791..27a7b96d 100644 --- a/internal/datasource_convert/float64_attribute.go +++ b/internal/datasource_convert/float64_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/datasource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertFloat64Attribute(a *datasource.Float64Attribute) (datasource_generate.GeneratorFloat64Attribute, error) { @@ -28,7 +29,8 @@ func convertFloat64Attribute(a *datasource.Float64Attribute) (datasource_generat DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/int64_attribute.go b/internal/datasource_convert/int64_attribute.go index f3a105a1..8ee639a8 100644 --- a/internal/datasource_convert/int64_attribute.go +++ b/internal/datasource_convert/int64_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/datasource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertInt64Attribute(a *datasource.Int64Attribute) (datasource_generate.GeneratorInt64Attribute, error) { @@ -28,7 +29,8 @@ func convertInt64Attribute(a *datasource.Int64Attribute) (datasource_generate.Ge DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/number_attribute.go b/internal/datasource_convert/number_attribute.go index db86cac7..53f86af5 100644 --- a/internal/datasource_convert/number_attribute.go +++ b/internal/datasource_convert/number_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/datasource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertNumberAttribute(a *datasource.NumberAttribute) (datasource_generate.GeneratorNumberAttribute, error) { @@ -28,7 +29,8 @@ func convertNumberAttribute(a *datasource.NumberAttribute) (datasource_generate. DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/string_attribute.go b/internal/datasource_convert/string_attribute.go index c2fa8595..7796cfce 100644 --- a/internal/datasource_convert/string_attribute.go +++ b/internal/datasource_convert/string_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/datasource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertStringAttribute(a *datasource.StringAttribute) (datasource_generate.GeneratorStringAttribute, error) { @@ -28,7 +29,8 @@ func convertStringAttribute(a *datasource.StringAttribute) (datasource_generate. DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_generate/bool_attribute.go b/internal/datasource_generate/bool_attribute.go index 69a432ae..601ad816 100644 --- a/internal/datasource_generate/bool_attribute.go +++ b/internal/datasource_generate/bool_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorBoolAttribute struct { schema.BoolAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorBoolAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorBoolAttribute) Equal(ga generatorschema.GeneratorAttribute) boo func (g GeneratorBoolAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorBoolAttribute GeneratorBoolAttribute } @@ -69,12 +79,20 @@ func (g GeneratorBoolAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorBoolAttribute: g, } - t, err := template.New("bool_attribute").Parse(boolAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("bool_attribute").Parse(boolAttributeTemplate) + if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +113,58 @@ func (g GeneratorBoolAttribute) ModelField(name generatorschema.FrameworkIdentif ValueType: model.BoolValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorBoolAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + boolType := generatorschema.NewCustomBoolType(name) + + b, err := boolType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + boolValue := generatorschema.NewCustomBoolValue(name) + + b, err = boolValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorBoolAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromBool(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/bool_attribute_test.go b/internal/datasource_generate/bool_attribute_test.go index d61db957..fb7f8609 100644 --- a/internal/datasource_generate/bool_attribute_test.go +++ b/internal/datasource_generate/bool_attribute_test.go @@ -141,6 +141,110 @@ func TestGeneratorBoolAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/hashicorp/terraform-plugin-framework/types", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + }, + }, + "associated-external-type-with-import": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.BoolAttribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/hashicorp/terraform-plugin-framework/types", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + { + Path: "github.com/api", + }, + }, + }, + "associated-external-type-with-custom-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Import: &code.Import{ + Path: "github.com/my_account/my_project/attribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/my_account/my_project/attribute", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + { + Path: "github.com/api", + }, + }, + }, } for name, testCase := range testCases { @@ -178,6 +282,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: ` +"bool_attribute": schema.BoolAttribute{ +CustomType: BoolAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"bool_attribute": schema.BoolAttribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ @@ -331,6 +466,37 @@ func TestGeneratorBoolAttribute_ModelField(t *testing.T) { TfsdkName: "bool_attribute", }, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "BoolAttribute", + ValueType: "BoolAttributeValue", + TfsdkName: "bool_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "BoolAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "bool_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/embed.go b/internal/datasource_generate/embed.go index c58a3bc7..c4ee8e00 100644 --- a/internal/datasource_generate/embed.go +++ b/internal/datasource_generate/embed.go @@ -10,13 +10,13 @@ import ( ) //go:embed templates/bool_attribute.gotmpl -var boolAttributeGoTemplate string +var boolAttributeTemplate string //go:embed templates/float64_attribute.gotmpl -var float64AttributeGoTemplate string +var float64AttributeTemplate string //go:embed templates/int64_attribute.gotmpl -var int64AttributeGoTemplate string +var int64AttributeTemplate string //go:embed templates/list_attribute.gotmpl var listAttributeGoTemplate string @@ -31,7 +31,7 @@ var mapAttributeGoTemplate string var mapNestedAttributeGoTemplate string //go:embed templates/number_attribute.gotmpl -var numberAttributeGoTemplate string +var numberAttributeTemplate string //go:embed templates/object_attribute.gotmpl var objectAttributeGoTemplate string @@ -46,7 +46,7 @@ var setNestedAttributeGoTemplate string var singleNestedAttributeGoTemplate string //go:embed templates/string_attribute.gotmpl -var stringAttributeGoTemplate string +var stringAttributeTemplate string //go:embed templates/list_nested_block.gotmpl var listNestedBlockGoTemplate string @@ -78,3 +78,14 @@ func addCommonBlockTemplate(t *template.Template) (*template.Template, error) { return t.New("common_block").Funcs(commonTemplateFuncs).Parse(commonBlockGoTemplate) } + +//go:embed templates/attribute.gotmpl +var attributeTemplate string + +func addAttributeTemplate(t *template.Template) (*template.Template, error) { + templateFuncs := template.FuncMap{ + "quote": strconv.Quote, + } + + return t.New("attribute").Funcs(templateFuncs).Parse(attributeTemplate) +} diff --git a/internal/datasource_generate/float64_attribute.go b/internal/datasource_generate/float64_attribute.go index c649636a..18da604e 100644 --- a/internal/datasource_generate/float64_attribute.go +++ b/internal/datasource_generate/float64_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorFloat64Attribute struct { schema.Float64Attribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorFloat64Attribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorFloat64Attribute) Equal(ga generatorschema.GeneratorAttribute) func (g GeneratorFloat64Attribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorFloat64Attribute GeneratorFloat64Attribute } @@ -69,12 +79,19 @@ func (g GeneratorFloat64Attribute) Schema(name generatorschema.FrameworkIdentifi GeneratorFloat64Attribute: g, } - t, err := template.New("float64_attribute").Parse(float64AttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("float64_attribute").Parse(float64AttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorFloat64Attribute) ModelField(name generatorschema.FrameworkIden ValueType: model.Float64ValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorFloat64Attribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + float64Type := generatorschema.NewCustomFloat64Type(name) + + b, err := float64Type.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + float64Value := generatorschema.NewCustomFloat64Value(name) + + b, err = float64Value.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorFloat64Attribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromFloat64(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/float64_attribute_test.go b/internal/datasource_generate/float64_attribute_test.go index 5d7fe12f..27189830 100644 --- a/internal/datasource_generate/float64_attribute_test.go +++ b/internal/datasource_generate/float64_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorFloat64Attribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + }, + expected: ` +"float64_attribute": schema.Float64Attribute{ +CustomType: Float64AttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"float64_attribute": schema.Float64Attribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorFloat64Attribute{ Float64Attribute: schema.Float64Attribute{ @@ -178,6 +210,37 @@ func TestGeneratorFloat64Attribute_ModelField(t *testing.T) { TfsdkName: "float64_attribute", }, }, + "associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + }, + expected: model.Field{ + Name: "Float64Attribute", + ValueType: "Float64AttributeValue", + TfsdkName: "float64_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "Float64Attribute", + ValueType: "my_custom_value_type", + TfsdkName: "float64_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/int64_attribute.go b/internal/datasource_generate/int64_attribute.go index 247f139e..3531239d 100644 --- a/internal/datasource_generate/int64_attribute.go +++ b/internal/datasource_generate/int64_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorInt64Attribute struct { schema.Int64Attribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorInt64Attribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorInt64Attribute) Equal(ga generatorschema.GeneratorAttribute) bo func (g GeneratorInt64Attribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorInt64Attribute GeneratorInt64Attribute } @@ -69,12 +79,19 @@ func (g GeneratorInt64Attribute) Schema(name generatorschema.FrameworkIdentifier GeneratorInt64Attribute: g, } - t, err := template.New("int64_attribute").Parse(int64AttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("int64_attribute").Parse(int64AttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorInt64Attribute) ModelField(name generatorschema.FrameworkIdenti ValueType: model.Int64ValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorInt64Attribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + int64Type := generatorschema.NewCustomInt64Type(name) + + b, err := int64Type.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + int64Value := generatorschema.NewCustomInt64Value(name) + + b, err = int64Value.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorInt64Attribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromInt64(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/int64_attribute_test.go b/internal/datasource_generate/int64_attribute_test.go index fc62a99a..27bb1c0c 100644 --- a/internal/datasource_generate/int64_attribute_test.go +++ b/internal/datasource_generate/int64_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorInt64Attribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + }, + expected: ` +"int64_attribute": schema.Int64Attribute{ +CustomType: Int64AttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"int64_attribute": schema.Int64Attribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorInt64Attribute{ Int64Attribute: schema.Int64Attribute{ @@ -178,6 +210,37 @@ func TestGeneratorInt64Attribute_ModelField(t *testing.T) { TfsdkName: "int64_attribute", }, }, + "associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + }, + expected: model.Field{ + Name: "Int64Attribute", + ValueType: "Int64AttributeValue", + TfsdkName: "int64_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "Int64Attribute", + ValueType: "my_custom_value_type", + TfsdkName: "int64_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/number_attribute.go b/internal/datasource_generate/number_attribute.go index 701f37b6..1d6f91e7 100644 --- a/internal/datasource_generate/number_attribute.go +++ b/internal/datasource_generate/number_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorNumberAttribute struct { schema.NumberAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorNumberAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorNumberAttribute) Equal(ga generatorschema.GeneratorAttribute) b func (g GeneratorNumberAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorNumberAttribute GeneratorNumberAttribute } @@ -69,12 +79,19 @@ func (g GeneratorNumberAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorNumberAttribute: g, } - t, err := template.New("number_attribute").Parse(numberAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("number_attribute").Parse(numberAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorNumberAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.NumberValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorNumberAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + numberType := generatorschema.NewCustomNumberType(name) + + b, err := numberType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + numberValue := generatorschema.NewCustomNumberValue(name) + + b, err = numberValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorNumberAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromNumber(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/number_attribute_test.go b/internal/datasource_generate/number_attribute_test.go index 77863520..d247db77 100644 --- a/internal/datasource_generate/number_attribute_test.go +++ b/internal/datasource_generate/number_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorNumberAttribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + }, + expected: ` +"number_attribute": schema.NumberAttribute{ +CustomType: NumberAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"number_attribute": schema.NumberAttribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorNumberAttribute{ NumberAttribute: schema.NumberAttribute{ @@ -178,6 +210,37 @@ func TestGeneratorNumberAttribute_ModelField(t *testing.T) { TfsdkName: "number_attribute", }, }, + "associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + }, + expected: model.Field{ + Name: "NumberAttribute", + ValueType: "NumberAttributeValue", + TfsdkName: "number_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "NumberAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "number_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/string_attribute.go b/internal/datasource_generate/string_attribute.go index 715f9522..247e622a 100644 --- a/internal/datasource_generate/string_attribute.go +++ b/internal/datasource_generate/string_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorStringAttribute struct { schema.StringAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorStringAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorStringAttribute) Equal(ga generatorschema.GeneratorAttribute) b func (g GeneratorStringAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorStringAttribute GeneratorStringAttribute } @@ -69,12 +79,19 @@ func (g GeneratorStringAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorStringAttribute: g, } - t, err := template.New("string_attribute").Parse(stringAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("string_attribute").Parse(stringAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorStringAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.StringValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorStringAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + stringType := generatorschema.NewCustomStringType(name) + + b, err := stringType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + stringValue := generatorschema.NewCustomStringValue(name) + + b, err = stringValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorStringAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromString(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/string_attribute_test.go b/internal/datasource_generate/string_attribute_test.go index dd9239a0..39b6f173 100644 --- a/internal/datasource_generate/string_attribute_test.go +++ b/internal/datasource_generate/string_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorStringAttribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + }, + expected: ` +"string_attribute": schema.StringAttribute{ +CustomType: StringAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"string_attribute": schema.StringAttribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorStringAttribute{ StringAttribute: schema.StringAttribute{ @@ -178,6 +210,37 @@ func TestGeneratorStringAttribute_ModelField(t *testing.T) { TfsdkName: "string_attribute", }, }, + "associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + }, + expected: model.Field{ + Name: "StringAttribute", + ValueType: "StringAttributeValue", + TfsdkName: "string_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "StringAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "string_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/templates/attribute.gotmpl b/internal/datasource_generate/templates/attribute.gotmpl new file mode 100644 index 00000000..abab8c3d --- /dev/null +++ b/internal/datasource_generate/templates/attribute.gotmpl @@ -0,0 +1,26 @@ +{{define "common_attribute"}} + +{{- if .Required}} +Required: {{.Required}}, +{{- end}} + +{{- if .Optional}} +Optional: {{.Optional}}, +{{- end}} + +{{- if .Computed}} +Computed: {{.Computed}}, +{{- end}} + +{{- if .Sensitive }} +Sensitive: {{.Sensitive}}, +{{- end}} + +{{- if .Description }} +Description: {{ .Description | quote }}, +MarkdownDescription: {{.Description | quote }}, +{{- end}} + +{{- if .DeprecationMessage }} +DeprecationMessage: {{ .DeprecationMessage | quote }},{{- end}} +{{- end}} \ No newline at end of file diff --git a/internal/datasource_generate/templates/bool_attribute.gotmpl b/internal/datasource_generate/templates/bool_attribute.gotmpl index 98d9d2b6..8ac85743 100644 --- a/internal/datasource_generate/templates/bool_attribute.gotmpl +++ b/internal/datasource_generate/templates/bool_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.BoolAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorBoolAttribute }} {{- if gt (len .GeneratorBoolAttribute.Validators) 0 }} Validators: []validator.Bool{ diff --git a/internal/datasource_generate/templates/float64_attribute.gotmpl b/internal/datasource_generate/templates/float64_attribute.gotmpl index d6530c7f..752c64e6 100644 --- a/internal/datasource_generate/templates/float64_attribute.gotmpl +++ b/internal/datasource_generate/templates/float64_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.Float64Attribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorFloat64Attribute }} {{- if gt (len .GeneratorFloat64Attribute.Validators) 0 }} Validators: []validator.Float64{ diff --git a/internal/datasource_generate/templates/int64_attribute.gotmpl b/internal/datasource_generate/templates/int64_attribute.gotmpl index d0ce7c04..2f65cbb2 100644 --- a/internal/datasource_generate/templates/int64_attribute.gotmpl +++ b/internal/datasource_generate/templates/int64_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.Int64Attribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorInt64Attribute }} {{- if gt (len .GeneratorInt64Attribute.Validators) 0 }} Validators: []validator.Int64{ diff --git a/internal/datasource_generate/templates/number_attribute.gotmpl b/internal/datasource_generate/templates/number_attribute.gotmpl index 98425de0..e089ecd0 100644 --- a/internal/datasource_generate/templates/number_attribute.gotmpl +++ b/internal/datasource_generate/templates/number_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.NumberAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorNumberAttribute }} {{- if gt (len .GeneratorNumberAttribute.Validators) 0 }} Validators: []validator.Number{ diff --git a/internal/datasource_generate/templates/string_attribute.gotmpl b/internal/datasource_generate/templates/string_attribute.gotmpl index fab62020..ebb12b19 100644 --- a/internal/datasource_generate/templates/string_attribute.gotmpl +++ b/internal/datasource_generate/templates/string_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.StringAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorStringAttribute }} {{- if gt (len .GeneratorStringAttribute.Validators) 0 }} Validators: []validator.String{ diff --git a/internal/provider_convert/bool_attribute.go b/internal/provider_convert/bool_attribute.go index d8d9a832..266603ba 100644 --- a/internal/provider_convert/bool_attribute.go +++ b/internal/provider_convert/bool_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/provider_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertBoolAttribute(a *provider.BoolAttribute) (provider_generate.GeneratorBoolAttribute, error) { @@ -26,7 +27,9 @@ func convertBoolAttribute(a *provider.BoolAttribute) (provider_generate.Generato MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/float64_attribute.go b/internal/provider_convert/float64_attribute.go index 375e3f1b..d3aa8408 100644 --- a/internal/provider_convert/float64_attribute.go +++ b/internal/provider_convert/float64_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/provider_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertFloat64Attribute(a *provider.Float64Attribute) (provider_generate.GeneratorFloat64Attribute, error) { @@ -26,7 +27,9 @@ func convertFloat64Attribute(a *provider.Float64Attribute) (provider_generate.Ge MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/int64_attribute.go b/internal/provider_convert/int64_attribute.go index 9e11c98c..fc8756b9 100644 --- a/internal/provider_convert/int64_attribute.go +++ b/internal/provider_convert/int64_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/provider_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertInt64Attribute(a *provider.Int64Attribute) (provider_generate.GeneratorInt64Attribute, error) { @@ -26,7 +27,9 @@ func convertInt64Attribute(a *provider.Int64Attribute) (provider_generate.Genera MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/number_attribute.go b/internal/provider_convert/number_attribute.go index c7d3fbf8..b8f9519a 100644 --- a/internal/provider_convert/number_attribute.go +++ b/internal/provider_convert/number_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/provider_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertNumberAttribute(a *provider.NumberAttribute) (provider_generate.GeneratorNumberAttribute, error) { @@ -26,7 +27,9 @@ func convertNumberAttribute(a *provider.NumberAttribute) (provider_generate.Gene MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/string_attribute.go b/internal/provider_convert/string_attribute.go index fd931460..397ef4b4 100644 --- a/internal/provider_convert/string_attribute.go +++ b/internal/provider_convert/string_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/provider_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertStringAttribute(a *provider.StringAttribute) (provider_generate.GeneratorStringAttribute, error) { @@ -26,7 +27,9 @@ func convertStringAttribute(a *provider.StringAttribute) (provider_generate.Gene MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_generate/bool_attribute.go b/internal/provider_generate/bool_attribute.go index b1a50e1d..47dc5fd5 100644 --- a/internal/provider_generate/bool_attribute.go +++ b/internal/provider_generate/bool_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorBoolAttribute struct { schema.BoolAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorBoolAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorBoolAttribute) Equal(ga generatorschema.GeneratorAttribute) boo func (g GeneratorBoolAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorBoolAttribute GeneratorBoolAttribute } @@ -69,12 +79,20 @@ func (g GeneratorBoolAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorBoolAttribute: g, } - t, err := template.New("bool_attribute").Parse(boolAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("bool_attribute").Parse(boolAttributeTemplate) + if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +113,58 @@ func (g GeneratorBoolAttribute) ModelField(name generatorschema.FrameworkIdentif ValueType: model.BoolValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorBoolAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + boolType := generatorschema.NewCustomBoolType(name) + + b, err := boolType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + boolValue := generatorschema.NewCustomBoolValue(name) + + b, err = boolValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorBoolAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromBool(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/bool_attribute_test.go b/internal/provider_generate/bool_attribute_test.go index b52c9c4e..4cfa9fae 100644 --- a/internal/provider_generate/bool_attribute_test.go +++ b/internal/provider_generate/bool_attribute_test.go @@ -141,6 +141,110 @@ func TestGeneratorBoolAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/hashicorp/terraform-plugin-framework/types", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + }, + }, + "associated-external-type-with-import": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.BoolAttribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/hashicorp/terraform-plugin-framework/types", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + { + Path: "github.com/api", + }, + }, + }, + "associated-external-type-with-custom-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Import: &code.Import{ + Path: "github.com/my_account/my_project/attribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/my_account/my_project/attribute", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + { + Path: "github.com/api", + }, + }, + }, } for name, testCase := range testCases { @@ -162,53 +266,84 @@ func TestGeneratorBoolAttribute_Schema(t *testing.T) { t.Parallel() testCases := map[string]struct { - boolAttribute GeneratorBoolAttribute - expectedAttribute string - expectedError error + input GeneratorBoolAttribute + expected string + expectedError error }{ "custom-type": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"bool_attribute": schema.BoolAttribute{ +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: ` +"bool_attribute": schema.BoolAttribute{ +CustomType: BoolAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, CustomType: &specschema.CustomType{ Type: "my_custom_type", }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ CustomType: my_custom_type, },`, }, "required": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ Required: true, }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ Required: true, },`, }, "optional": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ Optional: true, }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ Optional: true, },`, }, "sensitive": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ Sensitive: true, }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ Sensitive: true, },`, @@ -216,12 +351,12 @@ Sensitive: true, // TODO: Do we need separate description and markdown description? "description": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ Description: "description", }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ Description: "description", MarkdownDescription: "description", @@ -229,19 +364,19 @@ MarkdownDescription: "description", }, "deprecation-message": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ DeprecationMessage: "deprecated", }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ DeprecationMessage: "deprecated", },`, }, "validators": { - boolAttribute: GeneratorBoolAttribute{ + input: GeneratorBoolAttribute{ Validators: specschema.BoolValidators{ { Custom: &specschema.CustomValidator{ @@ -255,7 +390,7 @@ DeprecationMessage: "deprecated", }, }, }, - expectedAttribute: ` + expected: ` "bool_attribute": schema.BoolAttribute{ Validators: []validator.Bool{ my_validator.Validate(), @@ -271,13 +406,13 @@ my_other_validator.Validate(), t.Run(name, func(t *testing.T) { t.Parallel() - got, err := testCase.boolAttribute.Schema("bool_attribute") + got, err := testCase.input.Schema("bool_attribute") if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected error: %s", diff) } - if diff := cmp.Diff(got, testCase.expectedAttribute); diff != "" { + if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) } }) @@ -311,6 +446,37 @@ func TestGeneratorBoolAttribute_ModelField(t *testing.T) { TfsdkName: "bool_attribute", }, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "BoolAttribute", + ValueType: "BoolAttributeValue", + TfsdkName: "bool_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "BoolAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "bool_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/embed.go b/internal/provider_generate/embed.go index 9f7117ff..87d684fe 100644 --- a/internal/provider_generate/embed.go +++ b/internal/provider_generate/embed.go @@ -10,13 +10,13 @@ import ( ) //go:embed templates/bool_attribute.gotmpl -var boolAttributeGoTemplate string +var boolAttributeTemplate string //go:embed templates/float64_attribute.gotmpl -var float64AttributeGoTemplate string +var float64AttributeTemplate string //go:embed templates/int64_attribute.gotmpl -var int64AttributeGoTemplate string +var int64AttributeTemplate string //go:embed templates/list_attribute.gotmpl var listAttributeGoTemplate string @@ -31,7 +31,7 @@ var mapAttributeGoTemplate string var mapNestedAttributeGoTemplate string //go:embed templates/number_attribute.gotmpl -var numberAttributeGoTemplate string +var numberAttributeTemplate string //go:embed templates/object_attribute.gotmpl var objectAttributeGoTemplate string @@ -46,7 +46,7 @@ var setNestedAttributeGoTemplate string var singleNestedAttributeGoTemplate string //go:embed templates/string_attribute.gotmpl -var stringAttributeGoTemplate string +var stringAttributeTemplate string //go:embed templates/list_nested_block.gotmpl var listNestedBlockGoTemplate string @@ -78,3 +78,14 @@ func addCommonBlockTemplate(t *template.Template) (*template.Template, error) { return t.New("common_block").Funcs(commonTemplateFuncs).Parse(commonBlockGoTemplate) } + +//go:embed templates/attribute.gotmpl +var attributeTemplate string + +func addAttributeTemplate(t *template.Template) (*template.Template, error) { + templateFuncs := template.FuncMap{ + "quote": strconv.Quote, + } + + return t.New("attribute").Funcs(templateFuncs).Parse(attributeTemplate) +} diff --git a/internal/provider_generate/float64_attribute.go b/internal/provider_generate/float64_attribute.go index 266c25e5..128a1340 100644 --- a/internal/provider_generate/float64_attribute.go +++ b/internal/provider_generate/float64_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorFloat64Attribute struct { schema.Float64Attribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorFloat64Attribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorFloat64Attribute) Equal(ga generatorschema.GeneratorAttribute) func (g GeneratorFloat64Attribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorFloat64Attribute GeneratorFloat64Attribute } @@ -69,12 +79,19 @@ func (g GeneratorFloat64Attribute) Schema(name generatorschema.FrameworkIdentifi GeneratorFloat64Attribute: g, } - t, err := template.New("float64_attribute").Parse(float64AttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("float64_attribute").Parse(float64AttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorFloat64Attribute) ModelField(name generatorschema.FrameworkIden ValueType: model.Float64ValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorFloat64Attribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + float64Type := generatorschema.NewCustomFloat64Type(name) + + b, err := float64Type.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + float64Value := generatorschema.NewCustomFloat64Value(name) + + b, err = float64Value.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorFloat64Attribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromFloat64(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/float64_attribute_test.go b/internal/provider_generate/float64_attribute_test.go index 22102dc0..a29fd059 100644 --- a/internal/provider_generate/float64_attribute_test.go +++ b/internal/provider_generate/float64_attribute_test.go @@ -11,15 +11,16 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorFloat64Attribute_Schema(t *testing.T) { t.Parallel() testCases := map[string]struct { - input GeneratorFloat64Attribute - expectedAttribute string - expectedError error + input GeneratorFloat64Attribute + expected string + expectedError error }{ "custom-type": { input: GeneratorFloat64Attribute{ @@ -27,7 +28,38 @@ func TestGeneratorFloat64Attribute_Schema(t *testing.T) { Type: "my_custom_type", }, }, - expectedAttribute: ` + expected: ` +"float64_attribute": schema.Float64Attribute{ +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + }, + expected: ` +"float64_attribute": schema.Float64Attribute{ +CustomType: Float64AttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` "float64_attribute": schema.Float64Attribute{ CustomType: my_custom_type, },`, @@ -39,7 +71,7 @@ CustomType: my_custom_type, Required: true, }, }, - expectedAttribute: ` + expected: ` "float64_attribute": schema.Float64Attribute{ Required: true, },`, @@ -51,7 +83,7 @@ Required: true, Optional: true, }, }, - expectedAttribute: ` + expected: ` "float64_attribute": schema.Float64Attribute{ Optional: true, },`, @@ -63,7 +95,7 @@ Optional: true, Sensitive: true, }, }, - expectedAttribute: ` + expected: ` "float64_attribute": schema.Float64Attribute{ Sensitive: true, },`, @@ -76,7 +108,7 @@ Sensitive: true, Description: "description", }, }, - expectedAttribute: ` + expected: ` "float64_attribute": schema.Float64Attribute{ Description: "description", MarkdownDescription: "description", @@ -89,7 +121,7 @@ MarkdownDescription: "description", DeprecationMessage: "deprecated", }, }, - expectedAttribute: ` + expected: ` "float64_attribute": schema.Float64Attribute{ DeprecationMessage: "deprecated", },`, @@ -110,7 +142,7 @@ DeprecationMessage: "deprecated", }, }, }, - expectedAttribute: ` + expected: ` "float64_attribute": schema.Float64Attribute{ Validators: []validator.Float64{ my_validator.Validate(), @@ -132,7 +164,7 @@ my_other_validator.Validate(), t.Errorf("unexpected error: %s", diff) } - if diff := cmp.Diff(got, testCase.expectedAttribute); diff != "" { + if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) } }) @@ -166,6 +198,37 @@ func TestGeneratorFloat64Attribute_ModelField(t *testing.T) { TfsdkName: "float64_attribute", }, }, + "associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + }, + expected: model.Field{ + Name: "Float64Attribute", + ValueType: "Float64AttributeValue", + TfsdkName: "float64_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "Float64Attribute", + ValueType: "my_custom_value_type", + TfsdkName: "float64_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/int64_attribute.go b/internal/provider_generate/int64_attribute.go index bdd413c6..e9845c6f 100644 --- a/internal/provider_generate/int64_attribute.go +++ b/internal/provider_generate/int64_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorInt64Attribute struct { schema.Int64Attribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorInt64Attribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorInt64Attribute) Equal(ga generatorschema.GeneratorAttribute) bo func (g GeneratorInt64Attribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorInt64Attribute GeneratorInt64Attribute } @@ -69,12 +79,19 @@ func (g GeneratorInt64Attribute) Schema(name generatorschema.FrameworkIdentifier GeneratorInt64Attribute: g, } - t, err := template.New("int64_attribute").Parse(int64AttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("int64_attribute").Parse(int64AttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorInt64Attribute) ModelField(name generatorschema.FrameworkIdenti ValueType: model.Int64ValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorInt64Attribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + int64Type := generatorschema.NewCustomInt64Type(name) + + b, err := int64Type.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + int64Value := generatorschema.NewCustomInt64Value(name) + + b, err = int64Value.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorInt64Attribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromInt64(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/int64_attribute_test.go b/internal/provider_generate/int64_attribute_test.go index bdaff45e..43c474c4 100644 --- a/internal/provider_generate/int64_attribute_test.go +++ b/internal/provider_generate/int64_attribute_test.go @@ -11,15 +11,16 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorInt64Attribute_Schema(t *testing.T) { t.Parallel() testCases := map[string]struct { - input GeneratorInt64Attribute - expectedAttribute string - expectedError error + input GeneratorInt64Attribute + expected string + expectedError error }{ "custom-type": { input: GeneratorInt64Attribute{ @@ -27,7 +28,38 @@ func TestGeneratorInt64Attribute_Schema(t *testing.T) { Type: "my_custom_type", }, }, - expectedAttribute: ` + expected: ` +"int64_attribute": schema.Int64Attribute{ +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + }, + expected: ` +"int64_attribute": schema.Int64Attribute{ +CustomType: Int64AttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` "int64_attribute": schema.Int64Attribute{ CustomType: my_custom_type, },`, @@ -39,7 +71,7 @@ CustomType: my_custom_type, Required: true, }, }, - expectedAttribute: ` + expected: ` "int64_attribute": schema.Int64Attribute{ Required: true, },`, @@ -51,7 +83,7 @@ Required: true, Optional: true, }, }, - expectedAttribute: ` + expected: ` "int64_attribute": schema.Int64Attribute{ Optional: true, },`, @@ -63,7 +95,7 @@ Optional: true, Sensitive: true, }, }, - expectedAttribute: ` + expected: ` "int64_attribute": schema.Int64Attribute{ Sensitive: true, },`, @@ -76,7 +108,7 @@ Sensitive: true, Description: "description", }, }, - expectedAttribute: ` + expected: ` "int64_attribute": schema.Int64Attribute{ Description: "description", MarkdownDescription: "description", @@ -89,7 +121,7 @@ MarkdownDescription: "description", DeprecationMessage: "deprecated", }, }, - expectedAttribute: ` + expected: ` "int64_attribute": schema.Int64Attribute{ DeprecationMessage: "deprecated", },`, @@ -110,7 +142,7 @@ DeprecationMessage: "deprecated", }, }, }, - expectedAttribute: ` + expected: ` "int64_attribute": schema.Int64Attribute{ Validators: []validator.Int64{ my_validator.Validate(), @@ -132,7 +164,7 @@ my_other_validator.Validate(), t.Errorf("unexpected error: %s", diff) } - if diff := cmp.Diff(got, testCase.expectedAttribute); diff != "" { + if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) } }) @@ -166,6 +198,37 @@ func TestGeneratorInt64Attribute_ModelField(t *testing.T) { TfsdkName: "int64_attribute", }, }, + "associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + }, + expected: model.Field{ + Name: "Int64Attribute", + ValueType: "Int64AttributeValue", + TfsdkName: "int64_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "Int64Attribute", + ValueType: "my_custom_value_type", + TfsdkName: "int64_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/number_attribute.go b/internal/provider_generate/number_attribute.go index 042e6dc3..0f744abb 100644 --- a/internal/provider_generate/number_attribute.go +++ b/internal/provider_generate/number_attribute.go @@ -4,10 +4,11 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" - "github.com/hashicorp/terraform-plugin-codegen-spec/code" specschema "github.com/hashicorp/terraform-plugin-codegen-spec/schema" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -18,6 +19,7 @@ import ( type GeneratorNumberAttribute struct { schema.NumberAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -31,36 +33,20 @@ func (g GeneratorNumberAttribute) GeneratorSchemaType() generatorschema.Type { func (g GeneratorNumberAttribute) Imports() *generatorschema.Imports { imports := generatorschema.NewImports() - if g.CustomType != nil { - if g.CustomType.HasImport() { - imports.Add(*g.CustomType.Import) - } - } else { - imports.Add(code.Import{ - Path: generatorschema.TypesImport, - }) - } + customTypeImports := generatorschema.CustomTypeImports(g.CustomType) + imports.Append(customTypeImports) for _, v := range g.Validators { - if v.Custom == nil { - continue - } - - if !v.Custom.HasImport() { - continue - } - - for _, i := range v.Custom.Imports { - if len(i.Path) > 0 { - imports.Add(code.Import{ - Path: generatorschema.ValidatorImport, - }) + customValidatorImports := generatorschema.CustomValidatorImports(v.Custom) + imports.Append(customValidatorImports) + } - imports.Add(i) - } - } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) } + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -84,6 +70,7 @@ func (g GeneratorNumberAttribute) Equal(ga generatorschema.GeneratorAttribute) b func (g GeneratorNumberAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorNumberAttribute GeneratorNumberAttribute } @@ -92,12 +79,19 @@ func (g GeneratorNumberAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorNumberAttribute: g, } - t, err := template.New("number_attribute").Parse(numberAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("number_attribute").Parse(numberAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -118,9 +112,58 @@ func (g GeneratorNumberAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.NumberValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorNumberAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + numberType := generatorschema.NewCustomNumberType(name) + + b, err := numberType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + numberValue := generatorschema.NewCustomNumberValue(name) + + b, err = numberValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorNumberAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromNumber(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/number_attribute_test.go b/internal/provider_generate/number_attribute_test.go index 5113b15e..1be108d7 100644 --- a/internal/provider_generate/number_attribute_test.go +++ b/internal/provider_generate/number_attribute_test.go @@ -11,15 +11,16 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorNumberAttribute_Schema(t *testing.T) { t.Parallel() testCases := map[string]struct { - input GeneratorNumberAttribute - expectedAttribute string - expectedError error + input GeneratorNumberAttribute + expected string + expectedError error }{ "custom-type": { input: GeneratorNumberAttribute{ @@ -27,7 +28,38 @@ func TestGeneratorNumberAttribute_Schema(t *testing.T) { Type: "my_custom_type", }, }, - expectedAttribute: ` + expected: ` +"number_attribute": schema.NumberAttribute{ +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + }, + expected: ` +"number_attribute": schema.NumberAttribute{ +CustomType: NumberAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` "number_attribute": schema.NumberAttribute{ CustomType: my_custom_type, },`, @@ -39,7 +71,7 @@ CustomType: my_custom_type, Required: true, }, }, - expectedAttribute: ` + expected: ` "number_attribute": schema.NumberAttribute{ Required: true, },`, @@ -51,7 +83,7 @@ Required: true, Optional: true, }, }, - expectedAttribute: ` + expected: ` "number_attribute": schema.NumberAttribute{ Optional: true, },`, @@ -63,7 +95,7 @@ Optional: true, Sensitive: true, }, }, - expectedAttribute: ` + expected: ` "number_attribute": schema.NumberAttribute{ Sensitive: true, },`, @@ -76,7 +108,7 @@ Sensitive: true, Description: "description", }, }, - expectedAttribute: ` + expected: ` "number_attribute": schema.NumberAttribute{ Description: "description", MarkdownDescription: "description", @@ -89,7 +121,7 @@ MarkdownDescription: "description", DeprecationMessage: "deprecated", }, }, - expectedAttribute: ` + expected: ` "number_attribute": schema.NumberAttribute{ DeprecationMessage: "deprecated", },`, @@ -110,7 +142,7 @@ DeprecationMessage: "deprecated", }, }, }, - expectedAttribute: ` + expected: ` "number_attribute": schema.NumberAttribute{ Validators: []validator.Number{ my_validator.Validate(), @@ -132,7 +164,7 @@ my_other_validator.Validate(), t.Errorf("unexpected error: %s", diff) } - if diff := cmp.Diff(got, testCase.expectedAttribute); diff != "" { + if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) } }) @@ -166,6 +198,37 @@ func TestGeneratorNumberAttribute_ModelField(t *testing.T) { TfsdkName: "number_attribute", }, }, + "associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + }, + expected: model.Field{ + Name: "NumberAttribute", + ValueType: "NumberAttributeValue", + TfsdkName: "number_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "NumberAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "number_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/string_attribute.go b/internal/provider_generate/string_attribute.go index 74d683c4..37bd15ca 100644 --- a/internal/provider_generate/string_attribute.go +++ b/internal/provider_generate/string_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorStringAttribute struct { schema.StringAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -38,6 +41,12 @@ func (g GeneratorStringAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -61,6 +70,7 @@ func (g GeneratorStringAttribute) Equal(ga generatorschema.GeneratorAttribute) b func (g GeneratorStringAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string GeneratorStringAttribute GeneratorStringAttribute } @@ -69,12 +79,19 @@ func (g GeneratorStringAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorStringAttribute: g, } - t, err := template.New("string_attribute").Parse(stringAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("string_attribute").Parse(stringAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -95,9 +112,58 @@ func (g GeneratorStringAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.StringValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorStringAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + stringType := generatorschema.NewCustomStringType(name) + + b, err := stringType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + stringValue := generatorschema.NewCustomStringValue(name) + + b, err = stringValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorStringAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromString(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/string_attribute_test.go b/internal/provider_generate/string_attribute_test.go index aadb6251..f866a535 100644 --- a/internal/provider_generate/string_attribute_test.go +++ b/internal/provider_generate/string_attribute_test.go @@ -11,15 +11,16 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorStringAttribute_Schema(t *testing.T) { t.Parallel() testCases := map[string]struct { - input GeneratorStringAttribute - expectedAttribute string - expectedError error + input GeneratorStringAttribute + expected string + expectedError error }{ "custom-type": { input: GeneratorStringAttribute{ @@ -27,7 +28,38 @@ func TestGeneratorStringAttribute_Schema(t *testing.T) { Type: "my_custom_type", }, }, - expectedAttribute: ` + expected: ` +"string_attribute": schema.StringAttribute{ +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + }, + expected: ` +"string_attribute": schema.StringAttribute{ +CustomType: StringAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` "string_attribute": schema.StringAttribute{ CustomType: my_custom_type, },`, @@ -39,7 +71,7 @@ CustomType: my_custom_type, Required: true, }, }, - expectedAttribute: ` + expected: ` "string_attribute": schema.StringAttribute{ Required: true, },`, @@ -51,7 +83,7 @@ Required: true, Optional: true, }, }, - expectedAttribute: ` + expected: ` "string_attribute": schema.StringAttribute{ Optional: true, },`, @@ -63,7 +95,7 @@ Optional: true, Sensitive: true, }, }, - expectedAttribute: ` + expected: ` "string_attribute": schema.StringAttribute{ Sensitive: true, },`, @@ -76,7 +108,7 @@ Sensitive: true, Description: "description", }, }, - expectedAttribute: ` + expected: ` "string_attribute": schema.StringAttribute{ Description: "description", MarkdownDescription: "description", @@ -89,7 +121,7 @@ MarkdownDescription: "description", DeprecationMessage: "deprecated", }, }, - expectedAttribute: ` + expected: ` "string_attribute": schema.StringAttribute{ DeprecationMessage: "deprecated", },`, @@ -110,7 +142,7 @@ DeprecationMessage: "deprecated", }, }, }, - expectedAttribute: ` + expected: ` "string_attribute": schema.StringAttribute{ Validators: []validator.String{ my_validator.Validate(), @@ -132,7 +164,7 @@ my_other_validator.Validate(), t.Errorf("unexpected error: %s", diff) } - if diff := cmp.Diff(got, testCase.expectedAttribute); diff != "" { + if diff := cmp.Diff(got, testCase.expected); diff != "" { t.Errorf("unexpected difference: %s", diff) } }) @@ -166,6 +198,37 @@ func TestGeneratorStringAttribute_ModelField(t *testing.T) { TfsdkName: "string_attribute", }, }, + "associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + }, + expected: model.Field{ + Name: "StringAttribute", + ValueType: "StringAttributeValue", + TfsdkName: "string_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "StringAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "string_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/templates/attribute.gotmpl b/internal/provider_generate/templates/attribute.gotmpl new file mode 100644 index 00000000..c7b6ba8f --- /dev/null +++ b/internal/provider_generate/templates/attribute.gotmpl @@ -0,0 +1,22 @@ +{{define "common_attribute"}} + +{{- if .Required}} +Required: {{.Required}}, +{{- end}} + +{{- if .Optional}} +Optional: {{.Optional}}, +{{- end}} + +{{- if .Sensitive }} +Sensitive: {{.Sensitive}}, +{{- end}} + +{{- if .Description }} +Description: {{ .Description | quote }}, +MarkdownDescription: {{.Description | quote }}, +{{- end}} + +{{- if .DeprecationMessage }} +DeprecationMessage: {{ .DeprecationMessage | quote }},{{- end}} +{{- end}} \ No newline at end of file diff --git a/internal/provider_generate/templates/bool_attribute.gotmpl b/internal/provider_generate/templates/bool_attribute.gotmpl index 98d9d2b6..8ac85743 100644 --- a/internal/provider_generate/templates/bool_attribute.gotmpl +++ b/internal/provider_generate/templates/bool_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.BoolAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorBoolAttribute }} {{- if gt (len .GeneratorBoolAttribute.Validators) 0 }} Validators: []validator.Bool{ diff --git a/internal/provider_generate/templates/float64_attribute.gotmpl b/internal/provider_generate/templates/float64_attribute.gotmpl index d6530c7f..752c64e6 100644 --- a/internal/provider_generate/templates/float64_attribute.gotmpl +++ b/internal/provider_generate/templates/float64_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.Float64Attribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorFloat64Attribute }} {{- if gt (len .GeneratorFloat64Attribute.Validators) 0 }} Validators: []validator.Float64{ diff --git a/internal/provider_generate/templates/int64_attribute.gotmpl b/internal/provider_generate/templates/int64_attribute.gotmpl index d0ce7c04..2f65cbb2 100644 --- a/internal/provider_generate/templates/int64_attribute.gotmpl +++ b/internal/provider_generate/templates/int64_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.Int64Attribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorInt64Attribute }} {{- if gt (len .GeneratorInt64Attribute.Validators) 0 }} Validators: []validator.Int64{ diff --git a/internal/provider_generate/templates/number_attribute.gotmpl b/internal/provider_generate/templates/number_attribute.gotmpl index 98425de0..e089ecd0 100644 --- a/internal/provider_generate/templates/number_attribute.gotmpl +++ b/internal/provider_generate/templates/number_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.NumberAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorNumberAttribute }} {{- if gt (len .GeneratorNumberAttribute.Validators) 0 }} Validators: []validator.Number{ diff --git a/internal/provider_generate/templates/string_attribute.gotmpl b/internal/provider_generate/templates/string_attribute.gotmpl index fab62020..ebb12b19 100644 --- a/internal/provider_generate/templates/string_attribute.gotmpl +++ b/internal/provider_generate/templates/string_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.StringAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorStringAttribute }} {{- if gt (len .GeneratorStringAttribute.Validators) 0 }} Validators: []validator.String{ diff --git a/internal/resource_convert/bool_attribute.go b/internal/resource_convert/bool_attribute.go index d0358952..5578ab09 100644 --- a/internal/resource_convert/bool_attribute.go +++ b/internal/resource_convert/bool_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/resource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertBoolAttribute(a *resource.BoolAttribute) (resource_generate.GeneratorBoolAttribute, error) { @@ -27,9 +28,11 @@ func convertBoolAttribute(a *resource.BoolAttribute) (resource_generate.Generato MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/float64_attribute.go b/internal/resource_convert/float64_attribute.go index b3d25efe..595d6a41 100644 --- a/internal/resource_convert/float64_attribute.go +++ b/internal/resource_convert/float64_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/resource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertFloat64Attribute(a *resource.Float64Attribute) (resource_generate.GeneratorFloat64Attribute, error) { @@ -27,9 +28,11 @@ func convertFloat64Attribute(a *resource.Float64Attribute) (resource_generate.Ge MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/int64_attribute.go b/internal/resource_convert/int64_attribute.go index 86e406bb..97fec33a 100644 --- a/internal/resource_convert/int64_attribute.go +++ b/internal/resource_convert/int64_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/resource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertInt64Attribute(a *resource.Int64Attribute) (resource_generate.GeneratorInt64Attribute, error) { @@ -27,9 +28,11 @@ func convertInt64Attribute(a *resource.Int64Attribute) (resource_generate.Genera MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/number_attribute.go b/internal/resource_convert/number_attribute.go index 0b88e5c2..acb84a0b 100644 --- a/internal/resource_convert/number_attribute.go +++ b/internal/resource_convert/number_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/resource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertNumberAttribute(a *resource.NumberAttribute) (resource_generate.GeneratorNumberAttribute, error) { @@ -27,9 +28,11 @@ func convertNumberAttribute(a *resource.NumberAttribute) (resource_generate.Gene MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/string_attribute.go b/internal/resource_convert/string_attribute.go index 9c2741bc..4cb027bd 100644 --- a/internal/resource_convert/string_attribute.go +++ b/internal/resource_convert/string_attribute.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/resource_generate" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func convertStringAttribute(a *resource.StringAttribute) (resource_generate.GeneratorStringAttribute, error) { @@ -27,9 +28,11 @@ func convertStringAttribute(a *resource.StringAttribute) (resource_generate.Gene MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_generate/bool_attribute.go b/internal/resource_generate/bool_attribute.go index be89d290..12ef3495 100644 --- a/internal/resource_generate/bool_attribute.go +++ b/internal/resource_generate/bool_attribute.go @@ -4,6 +4,7 @@ package resource_generate import ( + "bytes" "fmt" "strings" "text/template" @@ -19,6 +20,7 @@ import ( type GeneratorBoolAttribute struct { schema.BoolAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -58,6 +60,12 @@ func (g GeneratorBoolAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -105,6 +113,7 @@ func boolDefault(d *specschema.BoolDefault) string { func (g GeneratorBoolAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string GeneratorBoolAttribute GeneratorBoolAttribute } @@ -115,12 +124,20 @@ func (g GeneratorBoolAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorBoolAttribute: g, } - t, err := template.New("bool_attribute").Parse(boolAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("bool_attribute").Parse(boolAttributeTemplate) + if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -141,9 +158,58 @@ func (g GeneratorBoolAttribute) ModelField(name generatorschema.FrameworkIdentif ValueType: model.BoolValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorBoolAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + boolType := generatorschema.NewCustomBoolType(name) + + b, err := boolType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + boolValue := generatorschema.NewCustomBoolValue(name) + + b, err = boolValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorBoolAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromBool(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/bool_attribute_test.go b/internal/resource_generate/bool_attribute_test.go index 5d14ae6d..8134b23e 100644 --- a/internal/resource_generate/bool_attribute_test.go +++ b/internal/resource_generate/bool_attribute_test.go @@ -309,6 +309,110 @@ func TestGeneratorBoolAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/hashicorp/terraform-plugin-framework/types", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + }, + }, + "associated-external-type-with-import": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.BoolAttribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/hashicorp/terraform-plugin-framework/types", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + { + Path: "github.com/api", + }, + }, + }, + "associated-external-type-with-custom-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Import: &code.Import{ + Path: "github.com/my_account/my_project/attribute", + }, + }, + }, + expected: []code.Import{ + { + Path: "github.com/my_account/my_project/attribute", + }, + { + Path: "fmt", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/diag", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/attr", + }, + { + Path: "github.com/hashicorp/terraform-plugin-go/tftypes", + }, + { + Path: "github.com/hashicorp/terraform-plugin-framework/types/basetypes", + }, + { + Path: "github.com/api", + }, + }, + }, } for name, testCase := range testCases { @@ -346,6 +450,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: ` +"bool_attribute": schema.BoolAttribute{ +CustomType: BoolAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"bool_attribute": schema.BoolAttribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorBoolAttribute{ BoolAttribute: schema.BoolAttribute{ @@ -541,6 +676,37 @@ func TestGeneratorBoolAttribute_ModelField(t *testing.T) { TfsdkName: "bool_attribute", }, }, + "associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "BoolAttribute", + ValueType: "BoolAttributeValue", + TfsdkName: "bool_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorBoolAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "BoolAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "bool_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/embed.go b/internal/resource_generate/embed.go index 29510ec9..e9c19e7e 100644 --- a/internal/resource_generate/embed.go +++ b/internal/resource_generate/embed.go @@ -10,13 +10,13 @@ import ( ) //go:embed templates/bool_attribute.gotmpl -var boolAttributeGoTemplate string +var boolAttributeTemplate string //go:embed templates/float64_attribute.gotmpl -var float64AttributeGoTemplate string +var float64AttributeTemplate string //go:embed templates/int64_attribute.gotmpl -var int64AttributeGoTemplate string +var int64AttributeTemplate string //go:embed templates/list_attribute.gotmpl var listAttributeGoTemplate string @@ -31,7 +31,7 @@ var mapAttributeGoTemplate string var mapNestedAttributeGoTemplate string //go:embed templates/number_attribute.gotmpl -var numberAttributeGoTemplate string +var numberAttributeTemplate string //go:embed templates/object_attribute.gotmpl var objectAttributeGoTemplate string @@ -46,7 +46,7 @@ var setNestedAttributeGoTemplate string var singleNestedAttributeGoTemplate string //go:embed templates/string_attribute.gotmpl -var stringAttributeGoTemplate string +var stringAttributeTemplate string //go:embed templates/list_nested_block.gotmpl var listNestedBlockGoTemplate string @@ -78,3 +78,14 @@ func addCommonBlockTemplate(t *template.Template) (*template.Template, error) { return t.New("common_block").Funcs(commonTemplateFuncs).Parse(commonBlockGoTemplate) } + +//go:embed templates/attribute.gotmpl +var attributeTemplate string + +func addAttributeTemplate(t *template.Template) (*template.Template, error) { + templateFuncs := template.FuncMap{ + "quote": strconv.Quote, + } + + return t.New("attribute").Funcs(templateFuncs).Parse(attributeTemplate) +} diff --git a/internal/resource_generate/float64_attribute.go b/internal/resource_generate/float64_attribute.go index 0a8e97a0..228fc36b 100644 --- a/internal/resource_generate/float64_attribute.go +++ b/internal/resource_generate/float64_attribute.go @@ -4,6 +4,7 @@ package resource_generate import ( + "bytes" "fmt" "strconv" "strings" @@ -20,6 +21,7 @@ import ( type GeneratorFloat64Attribute struct { schema.Float64Attribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -59,6 +61,12 @@ func (g GeneratorFloat64Attribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -106,6 +114,7 @@ func float64Default(d *specschema.Float64Default) string { func (g GeneratorFloat64Attribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string GeneratorFloat64Attribute GeneratorFloat64Attribute } @@ -116,12 +125,19 @@ func (g GeneratorFloat64Attribute) Schema(name generatorschema.FrameworkIdentifi GeneratorFloat64Attribute: g, } - t, err := template.New("float64_attribute").Parse(float64AttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("float64_attribute").Parse(float64AttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -142,9 +158,58 @@ func (g GeneratorFloat64Attribute) ModelField(name generatorschema.FrameworkIden ValueType: model.Float64ValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorFloat64Attribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + float64Type := generatorschema.NewCustomFloat64Type(name) + + b, err := float64Type.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + float64Value := generatorschema.NewCustomFloat64Value(name) + + b, err = float64Value.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorFloat64Attribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromFloat64(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/float64_attribute_test.go b/internal/resource_generate/float64_attribute_test.go index 68086f61..91653650 100644 --- a/internal/resource_generate/float64_attribute_test.go +++ b/internal/resource_generate/float64_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorFloat64Attribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + }, + expected: ` +"float64_attribute": schema.Float64Attribute{ +CustomType: Float64AttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"float64_attribute": schema.Float64Attribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorFloat64Attribute{ Float64Attribute: schema.Float64Attribute{ @@ -228,6 +260,37 @@ func TestGeneratorFloat64Attribute_ModelField(t *testing.T) { TfsdkName: "float64_attribute", }, }, + "associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + }, + expected: model.Field{ + Name: "Float64Attribute", + ValueType: "Float64AttributeValue", + TfsdkName: "float64_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorFloat64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Float64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "Float64Attribute", + ValueType: "my_custom_value_type", + TfsdkName: "float64_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/int64_attribute.go b/internal/resource_generate/int64_attribute.go index 97d5d93d..6e7099d7 100644 --- a/internal/resource_generate/int64_attribute.go +++ b/internal/resource_generate/int64_attribute.go @@ -4,6 +4,7 @@ package resource_generate import ( + "bytes" "fmt" "strings" "text/template" @@ -19,6 +20,7 @@ import ( type GeneratorInt64Attribute struct { schema.Int64Attribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -58,6 +60,12 @@ func (g GeneratorInt64Attribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -105,6 +113,7 @@ func int64Default(d *specschema.Int64Default) string { func (g GeneratorInt64Attribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string GeneratorInt64Attribute GeneratorInt64Attribute } @@ -115,12 +124,19 @@ func (g GeneratorInt64Attribute) Schema(name generatorschema.FrameworkIdentifier GeneratorInt64Attribute: g, } - t, err := template.New("int64_attribute").Parse(int64AttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("int64_attribute").Parse(int64AttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -141,9 +157,58 @@ func (g GeneratorInt64Attribute) ModelField(name generatorschema.FrameworkIdenti ValueType: model.Int64ValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorInt64Attribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + int64Type := generatorschema.NewCustomInt64Type(name) + + b, err := int64Type.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + int64Value := generatorschema.NewCustomInt64Value(name) + + b, err = int64Value.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorInt64Attribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromInt64(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/int64_attribute_test.go b/internal/resource_generate/int64_attribute_test.go index 0fc2347d..08aeb9da 100644 --- a/internal/resource_generate/int64_attribute_test.go +++ b/internal/resource_generate/int64_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorInt64Attribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + }, + expected: ` +"int64_attribute": schema.Int64Attribute{ +CustomType: Int64AttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"int64_attribute": schema.Int64Attribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorInt64Attribute{ Int64Attribute: schema.Int64Attribute{ @@ -228,6 +260,37 @@ func TestGeneratorInt64Attribute_ModelField(t *testing.T) { TfsdkName: "int64_attribute", }, }, + "associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + }, + expected: model.Field{ + Name: "Int64Attribute", + ValueType: "Int64AttributeValue", + TfsdkName: "int64_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorInt64Attribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.Int64Attribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "Int64Attribute", + ValueType: "my_custom_value_type", + TfsdkName: "int64_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/number_attribute.go b/internal/resource_generate/number_attribute.go index 438d5db2..69e6e040 100644 --- a/internal/resource_generate/number_attribute.go +++ b/internal/resource_generate/number_attribute.go @@ -4,6 +4,8 @@ package resource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorNumberAttribute struct { schema.NumberAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -50,6 +53,12 @@ func (g GeneratorNumberAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -94,6 +103,7 @@ func (g GeneratorNumberAttribute) Schema(name generatorschema.FrameworkIdentifie type attribute struct { Name string Default string + CustomType string GeneratorNumberAttribute GeneratorNumberAttribute } @@ -103,12 +113,19 @@ func (g GeneratorNumberAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorNumberAttribute: g, } - t, err := template.New("number_attribute").Parse(numberAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("number_attribute").Parse(numberAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -129,9 +146,58 @@ func (g GeneratorNumberAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.NumberValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorNumberAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + numberType := generatorschema.NewCustomNumberType(name) + + b, err := numberType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + numberValue := generatorschema.NewCustomNumberValue(name) + + b, err = numberValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorNumberAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromNumber(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/number_attribute_test.go b/internal/resource_generate/number_attribute_test.go index ca9db436..64afea61 100644 --- a/internal/resource_generate/number_attribute_test.go +++ b/internal/resource_generate/number_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorNumberAttribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + }, + expected: ` +"number_attribute": schema.NumberAttribute{ +CustomType: NumberAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"number_attribute": schema.NumberAttribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorNumberAttribute{ NumberAttribute: schema.NumberAttribute{ @@ -216,6 +248,37 @@ func TestGeneratorNumberAttribute_ModelField(t *testing.T) { TfsdkName: "number_attribute", }, }, + "associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + }, + expected: model.Field{ + Name: "NumberAttribute", + ValueType: "NumberAttributeValue", + TfsdkName: "number_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorNumberAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.NumberAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "NumberAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "number_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/string_attribute.go b/internal/resource_generate/string_attribute.go index 38c58bcc..77c25aae 100644 --- a/internal/resource_generate/string_attribute.go +++ b/internal/resource_generate/string_attribute.go @@ -4,6 +4,7 @@ package resource_generate import ( + "bytes" "fmt" "strings" "text/template" @@ -19,6 +20,7 @@ import ( type GeneratorStringAttribute struct { schema.StringAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. CustomType *specschema.CustomType @@ -58,6 +60,12 @@ func (g GeneratorStringAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -105,6 +113,7 @@ func stringDefault(d *specschema.StringDefault) string { func (g GeneratorStringAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string GeneratorStringAttribute GeneratorStringAttribute } @@ -115,12 +124,19 @@ func (g GeneratorStringAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorStringAttribute: g, } - t, err := template.New("string_attribute").Parse(stringAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{}", name.ToPascalCase()) + } + + t, err := template.New("string_attribute").Parse(stringAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -141,9 +157,58 @@ func (g GeneratorStringAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.StringValueType, } - if g.CustomType != nil { + switch { + case g.CustomType != nil: field.ValueType = g.CustomType.ValueType + case g.AssociatedExternalType != nil: + field.ValueType = fmt.Sprintf("%sValue", name.ToPascalCase()) } return field, nil } + +func (g GeneratorStringAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + stringType := generatorschema.NewCustomStringType(name) + + b, err := stringType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + stringValue := generatorschema.NewCustomStringValue(name) + + b, err = stringValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorStringAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + toFrom := generatorschema.NewToFromString(name, g.AssociatedExternalType) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/string_attribute_test.go b/internal/resource_generate/string_attribute_test.go index 85f1fe35..08466734 100644 --- a/internal/resource_generate/string_attribute_test.go +++ b/internal/resource_generate/string_attribute_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/hashicorp/terraform-plugin-codegen-framework/internal/schema" ) func TestGeneratorStringAttribute_Schema(t *testing.T) { @@ -33,6 +34,37 @@ CustomType: my_custom_type, },`, }, + "associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + }, + expected: ` +"string_attribute": schema.StringAttribute{ +CustomType: StringAttributeType{}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"string_attribute": schema.StringAttribute{ +CustomType: my_custom_type, +},`, + }, + "required": { input: GeneratorStringAttribute{ StringAttribute: schema.StringAttribute{ @@ -228,6 +260,37 @@ func TestGeneratorStringAttribute_ModelField(t *testing.T) { TfsdkName: "string_attribute", }, }, + "associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + }, + expected: model.Field{ + Name: "StringAttribute", + ValueType: "StringAttributeValue", + TfsdkName: "string_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorStringAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.StringAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "StringAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "string_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/templates/attribute.gotmpl b/internal/resource_generate/templates/attribute.gotmpl new file mode 100644 index 00000000..0726b444 --- /dev/null +++ b/internal/resource_generate/templates/attribute.gotmpl @@ -0,0 +1,26 @@ +{{define "common_attribute"}} + +{{- if .Required}} +Required: {{.Required}}, +{{- end}} + +{{- if .Optional}} +Optional: {{.Optional}}, +{{- end}} + +{{- if .Computed}} +Computed: {{.Computed}}, +{{- end}} + +{{- if .Sensitive }} +Sensitive: {{.Sensitive}}, +{{- end}} + +{{- if .Description }} +Description: {{ .Description | quote }}, +MarkdownDescription: {{.Description | quote }}, +{{- end}} + +{{- if .DeprecationMessage }} +DeprecationMessage: {{ .DeprecationMessage | quote }},{{- end}} +{{- end}} diff --git a/internal/resource_generate/templates/bool_attribute.gotmpl b/internal/resource_generate/templates/bool_attribute.gotmpl index 743e7dc5..3290e87f 100644 --- a/internal/resource_generate/templates/bool_attribute.gotmpl +++ b/internal/resource_generate/templates/bool_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.BoolAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorBoolAttribute }} {{- if gt (len .GeneratorBoolAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Bool{ diff --git a/internal/resource_generate/templates/float64_attribute.gotmpl b/internal/resource_generate/templates/float64_attribute.gotmpl index 8fadacc4..cb54d308 100644 --- a/internal/resource_generate/templates/float64_attribute.gotmpl +++ b/internal/resource_generate/templates/float64_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.Float64Attribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorFloat64Attribute }} {{- if gt (len .GeneratorFloat64Attribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Float64{ diff --git a/internal/resource_generate/templates/int64_attribute.gotmpl b/internal/resource_generate/templates/int64_attribute.gotmpl index bb5d1c2c..90941cba 100644 --- a/internal/resource_generate/templates/int64_attribute.gotmpl +++ b/internal/resource_generate/templates/int64_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.Int64Attribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorInt64Attribute }} {{- if gt (len .GeneratorInt64Attribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Int64{ diff --git a/internal/resource_generate/templates/number_attribute.gotmpl b/internal/resource_generate/templates/number_attribute.gotmpl index 430395a7..4a0b2d54 100644 --- a/internal/resource_generate/templates/number_attribute.gotmpl +++ b/internal/resource_generate/templates/number_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.NumberAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorNumberAttribute }} {{- if gt (len .GeneratorNumberAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Number{ diff --git a/internal/resource_generate/templates/string_attribute.gotmpl b/internal/resource_generate/templates/string_attribute.gotmpl index 14af19f0..cb8c2b00 100644 --- a/internal/resource_generate/templates/string_attribute.gotmpl +++ b/internal/resource_generate/templates/string_attribute.gotmpl @@ -1,5 +1,8 @@ "{{.Name}}": schema.StringAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- end}} {{- template "common_attribute" .GeneratorStringAttribute }} {{- if gt (len .GeneratorStringAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.String{ diff --git a/internal/schema/custom_bool.go b/internal/schema/custom_bool.go new file mode 100644 index 00000000..44321037 --- /dev/null +++ b/internal/schema/custom_bool.go @@ -0,0 +1,345 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomBoolType struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomBoolType(name string) CustomBoolType { + t := map[string]string{ + "equal": BoolTypeEqualTemplate, + "string": BoolTypeStringTemplate, + "type": BoolTypeTypeTemplate, + "typable": BoolTypeTypableTemplate, + "valueFromBool": BoolTypeValueFromBoolTemplate, + "valueFromTerraform": BoolTypeValueFromTerraformTemplate, + "valueType": BoolTypeValueTypeTemplate, + } + + return CustomBoolType{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomBoolType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromBool, + c.renderValueFromTerraform, + c.renderValueType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderString() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["string"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderTypable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["typable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderValueFromBool() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromBool"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderValueFromTerraform() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromTerraform"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolType) renderValueType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueType"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type CustomBoolValue struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomBoolValue(name string) CustomBoolValue { + t := map[string]string{ + "equal": BoolValueEqualTemplate, + "type": BoolValueTypeTemplate, + "valuable": BoolValueValuableTemplate, + "value": BoolValueValueTemplate, + } + + return CustomBoolValue{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomBoolValue) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderValuable, + c.renderValue, + c.renderEqual, + c.renderType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomBoolValue) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolValue) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolValue) renderValuable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valuable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomBoolValue) renderValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["value"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/custom_bool_test.go b/internal/schema/custom_bool_test.go new file mode 100644 index 00000000..f962555f --- /dev/null +++ b/internal/schema/custom_bool_test.go @@ -0,0 +1,460 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomBoolType_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`func (t ExampleType) Equal(o attr.Type) bool { +other, ok := o.(ExampleType) + +if !ok { +return false +} + +return t.BoolType.Equal(other.BoolType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolType_renderString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) String() string { +return "ExampleType" +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderString() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.BoolTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderTypable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolType_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleType struct { +basetypes.BoolType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolType_renderValueFromBool(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.BoolValue", + }, + expected: []byte(` +func (t ExampleType) ValueFromBool(ctx context.Context, in basetypes.BoolValue) (basetypes.BoolValuable, diag.Diagnostics) { +return ExampleValue{ +BoolValue: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderValueFromBool() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolType_renderValueFromTerraform(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.BoolType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.BoolValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromBool(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting BoolValue to BoolValuable: %v", diags) +} + +return boolValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderValueFromTerraform() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolType_renderValueType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueType(ctx context.Context) attr.Value { +return ExampleValue{} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolType := NewCustomBoolType(testCase.name) + + got, err := customBoolType.renderValueType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolValue_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.BoolValue", + }, + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.BoolValue.Equal(other.BoolValue) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolValue := NewCustomBoolValue(testCase.name) + + got, err := customBoolValue.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolValue_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolValue := NewCustomBoolValue(testCase.name) + + got, err := customBoolValue.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.BoolValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolValue := NewCustomBoolValue(testCase.name) + + got, err := customBoolValue.renderValuable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomBoolValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleValue struct { +basetypes.BoolValue +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customBoolValue := NewCustomBoolValue(testCase.name) + + got, err := customBoolValue.renderValue() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/custom_float64.go b/internal/schema/custom_float64.go new file mode 100644 index 00000000..703ac6fa --- /dev/null +++ b/internal/schema/custom_float64.go @@ -0,0 +1,345 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomFloat64Type struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomFloat64Type(name string) CustomFloat64Type { + t := map[string]string{ + "equal": Float64TypeEqualTemplate, + "string": Float64TypeStringTemplate, + "type": Float64TypeTypeTemplate, + "typable": Float64TypeTypableTemplate, + "valueFromFloat64": Float64TypeValueFromFloat64Template, + "valueFromTerraform": Float64TypeValueFromTerraformTemplate, + "valueType": Float64TypeValueTypeTemplate, + } + + return CustomFloat64Type{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomFloat64Type) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromFloat64, + c.renderValueFromTerraform, + c.renderValueType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderString() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["string"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderTypable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["typable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderValueFromFloat64() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromFloat64"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderValueFromTerraform() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromTerraform"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Type) renderValueType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueType"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type CustomFloat64Value struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomFloat64Value(name string) CustomFloat64Value { + t := map[string]string{ + "equal": Float64ValueEqualTemplate, + "type": Float64ValueTypeTemplate, + "valuable": Float64ValueValuableTemplate, + "value": Float64ValueValueTemplate, + } + + return CustomFloat64Value{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomFloat64Value) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderValuable, + c.renderValue, + c.renderEqual, + c.renderType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Value) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Value) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Value) renderValuable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valuable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomFloat64Value) renderValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["value"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/custom_float64_test.go b/internal/schema/custom_float64_test.go new file mode 100644 index 00000000..38749b94 --- /dev/null +++ b/internal/schema/custom_float64_test.go @@ -0,0 +1,460 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomFloat64Type_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`func (t ExampleType) Equal(o attr.Type) bool { +other, ok := o.(ExampleType) + +if !ok { +return false +} + +return t.Float64Type.Equal(other.Float64Type) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Type_renderString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) String() string { +return "ExampleType" +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderString() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Type_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.Float64Typable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderTypable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Type_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleType struct { +basetypes.Float64Type +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Type_renderValueFromFloat64(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.Float64Value", + }, + expected: []byte(` +func (t ExampleType) ValueFromFloat64(ctx context.Context, in basetypes.Float64Value) (basetypes.Float64Valuable, diag.Diagnostics) { +return ExampleValue{ +Float64Value: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderValueFromFloat64() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Type_renderValueFromTerraform(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.Float64Type.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.Float64Value) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromFloat64(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting Float64Value to Float64Valuable: %v", diags) +} + +return boolValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderValueFromTerraform() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Type_renderValueType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueType(ctx context.Context) attr.Value { +return ExampleValue{} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Type := NewCustomFloat64Type(testCase.name) + + got, err := customFloat64Type.renderValueType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Value_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.Float64Value", + }, + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.Float64Value.Equal(other.Float64Value) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Value := NewCustomFloat64Value(testCase.name) + + got, err := customFloat64Value.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Value_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Value := NewCustomFloat64Value(testCase.name) + + got, err := customFloat64Value.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Value_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.Float64Valuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Value := NewCustomFloat64Value(testCase.name) + + got, err := customFloat64Value.renderValuable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomFloat64Value_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleValue struct { +basetypes.Float64Value +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customFloat64Value := NewCustomFloat64Value(testCase.name) + + got, err := customFloat64Value.renderValue() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/custom_int64.go b/internal/schema/custom_int64.go new file mode 100644 index 00000000..a7ce57be --- /dev/null +++ b/internal/schema/custom_int64.go @@ -0,0 +1,345 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomInt64Type struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomInt64Type(name string) CustomInt64Type { + t := map[string]string{ + "equal": Int64TypeEqualTemplate, + "string": Int64TypeStringTemplate, + "type": Int64TypeTypeTemplate, + "typable": Int64TypeTypableTemplate, + "valueFromInt64": Int64TypeValueFromInt64Template, + "valueFromTerraform": Int64TypeValueFromTerraformTemplate, + "valueType": Int64TypeValueTypeTemplate, + } + + return CustomInt64Type{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomInt64Type) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromInt64, + c.renderValueFromTerraform, + c.renderValueType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderString() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["string"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderTypable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["typable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderValueFromInt64() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromInt64"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderValueFromTerraform() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromTerraform"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Type) renderValueType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueType"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type CustomInt64Value struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomInt64Value(name string) CustomInt64Value { + t := map[string]string{ + "equal": Int64ValueEqualTemplate, + "type": Int64ValueTypeTemplate, + "valuable": Int64ValueValuableTemplate, + "value": Int64ValueValueTemplate, + } + + return CustomInt64Value{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomInt64Value) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderValuable, + c.renderValue, + c.renderEqual, + c.renderType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Value) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Value) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Value) renderValuable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valuable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomInt64Value) renderValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["value"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/custom_int64_test.go b/internal/schema/custom_int64_test.go new file mode 100644 index 00000000..ad5047da --- /dev/null +++ b/internal/schema/custom_int64_test.go @@ -0,0 +1,460 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomInt64Type_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`func (t ExampleType) Equal(o attr.Type) bool { +other, ok := o.(ExampleType) + +if !ok { +return false +} + +return t.Int64Type.Equal(other.Int64Type) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Type_renderString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) String() string { +return "ExampleType" +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderString() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Type_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.Int64Typable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderTypable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Type_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleType struct { +basetypes.Int64Type +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Type_renderValueFromInt64(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.Int64Value", + }, + expected: []byte(` +func (t ExampleType) ValueFromInt64(ctx context.Context, in basetypes.Int64Value) (basetypes.Int64Valuable, diag.Diagnostics) { +return ExampleValue{ +Int64Value: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderValueFromInt64() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Type_renderValueFromTerraform(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.Int64Type.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.Int64Value) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromInt64(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting Int64Value to Int64Valuable: %v", diags) +} + +return boolValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderValueFromTerraform() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Type_renderValueType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueType(ctx context.Context) attr.Value { +return ExampleValue{} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Type := NewCustomInt64Type(testCase.name) + + got, err := customInt64Type.renderValueType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Value_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.Int64Value", + }, + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.Int64Value.Equal(other.Int64Value) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Value := NewCustomInt64Value(testCase.name) + + got, err := customInt64Value.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Value_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Value := NewCustomInt64Value(testCase.name) + + got, err := customInt64Value.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Value_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.Int64Valuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Value := NewCustomInt64Value(testCase.name) + + got, err := customInt64Value.renderValuable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomInt64Value_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleValue struct { +basetypes.Int64Value +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customInt64Value := NewCustomInt64Value(testCase.name) + + got, err := customInt64Value.renderValue() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/custom_number.go b/internal/schema/custom_number.go new file mode 100644 index 00000000..b695469b --- /dev/null +++ b/internal/schema/custom_number.go @@ -0,0 +1,345 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomNumberType struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomNumberType(name string) CustomNumberType { + t := map[string]string{ + "equal": NumberTypeEqualTemplate, + "string": NumberTypeStringTemplate, + "type": NumberTypeTypeTemplate, + "typable": NumberTypeTypableTemplate, + "valueFromNumber": NumberTypeValueFromNumberTemplate, + "valueFromTerraform": NumberTypeValueFromTerraformTemplate, + "valueType": NumberTypeValueTypeTemplate, + } + + return CustomNumberType{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomNumberType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromNumber, + c.renderValueFromTerraform, + c.renderValueType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderString() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["string"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderTypable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["typable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderValueFromNumber() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromNumber"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderValueFromTerraform() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromTerraform"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberType) renderValueType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueType"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type CustomNumberValue struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomNumberValue(name string) CustomNumberValue { + t := map[string]string{ + "equal": NumberValueEqualTemplate, + "type": NumberValueTypeTemplate, + "valuable": NumberValueValuableTemplate, + "value": NumberValueValueTemplate, + } + + return CustomNumberValue{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomNumberValue) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderValuable, + c.renderValue, + c.renderEqual, + c.renderType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomNumberValue) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberValue) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberValue) renderValuable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valuable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNumberValue) renderValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["value"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/custom_number_test.go b/internal/schema/custom_number_test.go new file mode 100644 index 00000000..fcca5d5f --- /dev/null +++ b/internal/schema/custom_number_test.go @@ -0,0 +1,460 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomNumberType_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`func (t ExampleType) Equal(o attr.Type) bool { +other, ok := o.(ExampleType) + +if !ok { +return false +} + +return t.NumberType.Equal(other.NumberType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberType_renderString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) String() string { +return "ExampleType" +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderString() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.NumberTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderTypable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberType_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleType struct { +basetypes.NumberType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberType_renderValueFromNumber(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.NumberValue", + }, + expected: []byte(` +func (t ExampleType) ValueFromNumber(ctx context.Context, in basetypes.NumberValue) (basetypes.NumberValuable, diag.Diagnostics) { +return ExampleValue{ +NumberValue: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderValueFromNumber() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberType_renderValueFromTerraform(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.NumberType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.NumberValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromNumber(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting NumberValue to NumberValuable: %v", diags) +} + +return boolValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderValueFromTerraform() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberType_renderValueType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueType(ctx context.Context) attr.Value { +return ExampleValue{} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberType := NewCustomNumberType(testCase.name) + + got, err := customNumberType.renderValueType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberValue_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.NumberValue", + }, + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.NumberValue.Equal(other.NumberValue) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberValue := NewCustomNumberValue(testCase.name) + + got, err := customNumberValue.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberValue_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberValue := NewCustomNumberValue(testCase.name) + + got, err := customNumberValue.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.NumberValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberValue := NewCustomNumberValue(testCase.name) + + got, err := customNumberValue.renderValuable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomNumberValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleValue struct { +basetypes.NumberValue +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customNumberValue := NewCustomNumberValue(testCase.name) + + got, err := customNumberValue.renderValue() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/custom_string.go b/internal/schema/custom_string.go new file mode 100644 index 00000000..c028cc51 --- /dev/null +++ b/internal/schema/custom_string.go @@ -0,0 +1,345 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomStringType struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomStringType(name string) CustomStringType { + t := map[string]string{ + "equal": StringTypeEqualTemplate, + "string": StringTypeStringTemplate, + "type": StringTypeTypeTemplate, + "typable": StringTypeTypableTemplate, + "valueFromString": StringTypeValueFromStringTemplate, + "valueFromTerraform": StringTypeValueFromTerraformTemplate, + "valueType": StringTypeValueTypeTemplate, + } + + return CustomStringType{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomStringType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromString, + c.renderValueFromTerraform, + c.renderValueType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderString() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["string"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderTypable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["typable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderValueFromString() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromString"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderValueFromTerraform() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromTerraform"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringType) renderValueType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueType"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type CustomStringValue struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomStringValue(name string) CustomStringValue { + t := map[string]string{ + "equal": StringValueEqualTemplate, + "type": StringValueTypeTemplate, + "valuable": StringValueValuableTemplate, + "value": StringValueValueTemplate, + } + + return CustomStringValue{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomStringValue) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderValuable, + c.renderValue, + c.renderEqual, + c.renderType, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (c CustomStringValue) renderEqual() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["equal"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringValue) renderType() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["type"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringValue) renderValuable() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valuable"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomStringValue) renderValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["value"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + }{ + Name: c.Name.ToPascalCase(), + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/custom_string_test.go b/internal/schema/custom_string_test.go new file mode 100644 index 00000000..b17b2e5c --- /dev/null +++ b/internal/schema/custom_string_test.go @@ -0,0 +1,460 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomStringType_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`func (t ExampleType) Equal(o attr.Type) bool { +other, ok := o.(ExampleType) + +if !ok { +return false +} + +return t.StringType.Equal(other.StringType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringType_renderString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) String() string { +return "ExampleType" +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderString() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.StringTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderTypable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringType_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleType struct { +basetypes.StringType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringType_renderValueFromString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.StringValue", + }, + expected: []byte(` +func (t ExampleType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { +return ExampleValue{ +StringValue: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderValueFromString() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringType_renderValueFromTerraform(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.StringType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.StringValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromString(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags) +} + +return boolValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderValueFromTerraform() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringType_renderValueType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (t ExampleType) ValueType(ctx context.Context) attr.Value { +return ExampleValue{} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringType := NewCustomStringType(testCase.name) + + got, err := customStringType.renderValueType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringValue_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrValues map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrValues: map[string]string{ + "bool_attribute": "basetypes.StringValue", + }, + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.StringValue.Equal(other.StringValue) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringValue := NewCustomStringValue(testCase.name) + + got, err := customStringValue.renderEqual() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringValue_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringValue := NewCustomStringValue(testCase.name) + + got, err := customStringValue.renderType() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.StringValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringValue := NewCustomStringValue(testCase.name) + + got, err := customStringValue.renderValuable() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestCustomStringValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleValue struct { +basetypes.StringValue +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customStringValue := NewCustomStringValue(testCase.name) + + got, err := customStringValue.renderValue() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/embed.go b/internal/schema/embed.go index da7a5fe8..a96bcf6c 100644 --- a/internal/schema/embed.go +++ b/internal/schema/embed.go @@ -7,12 +7,196 @@ import ( _ "embed" ) +// Bool From/To + +//go:embed templates/bool_from.gotmpl +var BoolFromTemplate string + +//go:embed templates/bool_to.gotmpl +var BoolToTemplate string + +// Bool Type + +//go:embed templates/bool_type_equal.gotmpl +var BoolTypeEqualTemplate string + +//go:embed templates/bool_type_string.gotmpl +var BoolTypeStringTemplate string + +//go:embed templates/bool_type_type.gotmpl +var BoolTypeTypeTemplate string + +//go:embed templates/bool_type_typable.gotmpl +var BoolTypeTypableTemplate string + +//go:embed templates/bool_type_value_from_bool.gotmpl +var BoolTypeValueFromBoolTemplate string + +//go:embed templates/bool_type_value_from_terraform.gotmpl +var BoolTypeValueFromTerraformTemplate string + +//go:embed templates/bool_type_value_type.gotmpl +var BoolTypeValueTypeTemplate string + +// Bool Value + +//go:embed templates/bool_value_equal.gotmpl +var BoolValueEqualTemplate string + +//go:embed templates/bool_value_type.gotmpl +var BoolValueTypeTemplate string + +//go:embed templates/bool_value_value.gotmpl +var BoolValueValueTemplate string + +//go:embed templates/bool_value_valuable.gotmpl +var BoolValueValuableTemplate string + +// Float64 From/To + +//go:embed templates/float64_from.gotmpl +var Float64FromTemplate string + +//go:embed templates/float64_to.gotmpl +var Float64ToTemplate string + +// Float64 Type + +//go:embed templates/float64_type_equal.gotmpl +var Float64TypeEqualTemplate string + +//go:embed templates/float64_type_string.gotmpl +var Float64TypeStringTemplate string + +//go:embed templates/float64_type_type.gotmpl +var Float64TypeTypeTemplate string + +//go:embed templates/float64_type_typable.gotmpl +var Float64TypeTypableTemplate string + +//go:embed templates/float64_type_value_from_float64.gotmpl +var Float64TypeValueFromFloat64Template string + +//go:embed templates/float64_type_value_from_terraform.gotmpl +var Float64TypeValueFromTerraformTemplate string + +//go:embed templates/float64_type_value_type.gotmpl +var Float64TypeValueTypeTemplate string + +// Float64 Value + +//go:embed templates/float64_value_equal.gotmpl +var Float64ValueEqualTemplate string + +//go:embed templates/float64_value_type.gotmpl +var Float64ValueTypeTemplate string + +//go:embed templates/float64_value_value.gotmpl +var Float64ValueValueTemplate string + +//go:embed templates/float64_value_valuable.gotmpl +var Float64ValueValuableTemplate string + +// Int64 From/To + +//go:embed templates/int64_from.gotmpl +var Int64FromTemplate string + +//go:embed templates/int64_to.gotmpl +var Int64ToTemplate string + +// Int64 Type + +//go:embed templates/int64_type_equal.gotmpl +var Int64TypeEqualTemplate string + +//go:embed templates/int64_type_string.gotmpl +var Int64TypeStringTemplate string + +//go:embed templates/int64_type_type.gotmpl +var Int64TypeTypeTemplate string + +//go:embed templates/int64_type_typable.gotmpl +var Int64TypeTypableTemplate string + +//go:embed templates/int64_type_value_from_int64.gotmpl +var Int64TypeValueFromInt64Template string + +//go:embed templates/int64_type_value_from_terraform.gotmpl +var Int64TypeValueFromTerraformTemplate string + +//go:embed templates/int64_type_value_type.gotmpl +var Int64TypeValueTypeTemplate string + +// Int64 Value + +//go:embed templates/int64_value_equal.gotmpl +var Int64ValueEqualTemplate string + +//go:embed templates/int64_value_type.gotmpl +var Int64ValueTypeTemplate string + +//go:embed templates/int64_value_value.gotmpl +var Int64ValueValueTemplate string + +//go:embed templates/int64_value_valuable.gotmpl +var Int64ValueValuableTemplate string + +// Number From/To + +//go:embed templates/number_from.gotmpl +var NumberFromTemplate string + +//go:embed templates/number_to.gotmpl +var NumberToTemplate string + +// Number Type + +//go:embed templates/number_type_equal.gotmpl +var NumberTypeEqualTemplate string + +//go:embed templates/number_type_string.gotmpl +var NumberTypeStringTemplate string + +//go:embed templates/number_type_type.gotmpl +var NumberTypeTypeTemplate string + +//go:embed templates/number_type_typable.gotmpl +var NumberTypeTypableTemplate string + +//go:embed templates/number_type_value_from_number.gotmpl +var NumberTypeValueFromNumberTemplate string + +//go:embed templates/number_type_value_from_terraform.gotmpl +var NumberTypeValueFromTerraformTemplate string + +//go:embed templates/number_type_value_type.gotmpl +var NumberTypeValueTypeTemplate string + +// Number Value + +//go:embed templates/number_value_equal.gotmpl +var NumberValueEqualTemplate string + +//go:embed templates/number_value_type.gotmpl +var NumberValueTypeTemplate string + +//go:embed templates/number_value_value.gotmpl +var NumberValueValueTemplate string + +//go:embed templates/number_value_valuable.gotmpl +var NumberValueValuableTemplate string + +// Object From/To + //go:embed templates/object_from.gotmpl var ObjectFromTemplate string //go:embed templates/object_to.gotmpl var ObjectToTemplate string +// Object Type + //go:embed templates/object_type_equal.gotmpl var ObjectTypeEqualTemplate string @@ -46,6 +230,8 @@ var ObjectTypeValueTypeTemplate string //go:embed templates/object_type_value_unknown.gotmpl var ObjectTypeValueUnknownTemplate string +// Object Value + //go:embed templates/object_value_attribute_types.gotmpl var ObjectValueAttributeTypesTemplate string @@ -78,3 +264,48 @@ var ObjectValueValueTemplate string //go:embed templates/schema.gotmpl var SchemaGoTemplate string + +// String From/To + +//go:embed templates/string_from.gotmpl +var StringFromTemplate string + +//go:embed templates/string_to.gotmpl +var StringToTemplate string + +// String Type + +//go:embed templates/string_type_equal.gotmpl +var StringTypeEqualTemplate string + +//go:embed templates/string_type_string.gotmpl +var StringTypeStringTemplate string + +//go:embed templates/string_type_type.gotmpl +var StringTypeTypeTemplate string + +//go:embed templates/string_type_typable.gotmpl +var StringTypeTypableTemplate string + +//go:embed templates/string_type_value_from_string.gotmpl +var StringTypeValueFromStringTemplate string + +//go:embed templates/string_type_value_from_terraform.gotmpl +var StringTypeValueFromTerraformTemplate string + +//go:embed templates/string_type_value_type.gotmpl +var StringTypeValueTypeTemplate string + +// String Value + +//go:embed templates/string_value_equal.gotmpl +var StringValueEqualTemplate string + +//go:embed templates/string_value_type.gotmpl +var StringValueTypeTemplate string + +//go:embed templates/string_value_value.gotmpl +var StringValueValueTemplate string + +//go:embed templates/string_value_valuable.gotmpl +var StringValueValuableTemplate string diff --git a/internal/schema/import.go b/internal/schema/import.go index 6d07609b..6a1ea6a4 100644 --- a/internal/schema/import.go +++ b/internal/schema/import.go @@ -311,3 +311,27 @@ func CustomValidatorImports(cv *specschema.CustomValidator) *Imports { return imports } + +func AssociatedExternalTypeImports() *Imports { + imports := NewImports() + + imports.Add([]code.Import{ + { + Path: FmtImport, + }, + { + Path: DiagImport, + }, + { + Path: AttrImport, + }, + { + Path: TfTypesImport, + }, + { + Path: BaseTypesImport, + }, + }...) + + return imports +} diff --git a/internal/schema/templates/bool_from.gotmpl b/internal/schema/templates/bool_from.gotmpl new file mode 100644 index 00000000..38eec569 --- /dev/null +++ b/internal/schema/templates/bool_from.gotmpl @@ -0,0 +1,14 @@ + +func (v {{.Name}}Value) From{{.AssocExtType.ToPascalCase}}(ctx context.Context, apiObject {{.AssocExtType.Type}}) ({{.Name}}Value, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return {{.Name}}Value{ +types.BoolNull(), +}, diags +} + +return {{.Name}}Value{ +types.BoolPointerValue(*apiObject), +}, diags +} diff --git a/internal/schema/templates/bool_to.gotmpl b/internal/schema/templates/bool_to.gotmpl new file mode 100644 index 00000000..97c3fe5a --- /dev/null +++ b/internal/schema/templates/bool_to.gotmpl @@ -0,0 +1,20 @@ +func (v {{.Name}}Value) To{{.AssocExtType.ToPascalCase}}(ctx context.Context) ({{.AssocExtType.Type}}, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"{{.Name}}Value Value Is Unknown", +`"{{.Name}}Value" is unknown.`, +)) + +return nil, diags +} + +a := {{.AssocExtType.TypeReference}}(v.ValueBoolPointer()) + +return &a, diags +} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_equal.gotmpl b/internal/schema/templates/bool_type_equal.gotmpl new file mode 100644 index 00000000..409be80f --- /dev/null +++ b/internal/schema/templates/bool_type_equal.gotmpl @@ -0,0 +1,9 @@ +func (t {{.Name}}Type) Equal(o attr.Type) bool { +other, ok := o.({{.Name}}Type) + +if !ok { +return false +} + +return t.BoolType.Equal(other.BoolType) +} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_string.gotmpl b/internal/schema/templates/bool_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/bool_type_string.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) String() string { +return "{{.Name}}Type" +} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_typable.gotmpl b/internal/schema/templates/bool_type_typable.gotmpl new file mode 100644 index 00000000..3dd7951a --- /dev/null +++ b/internal/schema/templates/bool_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.BoolTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_type.gotmpl b/internal/schema/templates/bool_type_type.gotmpl new file mode 100644 index 00000000..08d365b4 --- /dev/null +++ b/internal/schema/templates/bool_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.BoolType +} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_value_from_bool.gotmpl b/internal/schema/templates/bool_type_value_from_bool.gotmpl new file mode 100644 index 00000000..73d3f24a --- /dev/null +++ b/internal/schema/templates/bool_type_value_from_bool.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromBool(ctx context.Context, in basetypes.BoolValue) (basetypes.BoolValuable, diag.Diagnostics) { +return {{.Name}}Value{ +BoolValue: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_value_from_terraform.gotmpl b/internal/schema/templates/bool_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..1a816779 --- /dev/null +++ b/internal/schema/templates/bool_type_value_from_terraform.gotmpl @@ -0,0 +1,22 @@ + +func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.BoolType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.BoolValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromBool(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting BoolValue to BoolValuable: %v", diags) +} + +return boolValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/bool_type_value_type.gotmpl b/internal/schema/templates/bool_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/bool_type_value_type.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) ValueType(ctx context.Context) attr.Value { +return {{.Name}}Value{} +} \ No newline at end of file diff --git a/internal/schema/templates/bool_value_equal.gotmpl b/internal/schema/templates/bool_value_equal.gotmpl new file mode 100644 index 00000000..6c5c01e2 --- /dev/null +++ b/internal/schema/templates/bool_value_equal.gotmpl @@ -0,0 +1,10 @@ + +func (v {{.Name}}Value) Equal(o attr.Value) bool { +other, ok := o.({{.Name}}Value) + +if !ok { +return false +} + +return v.BoolValue.Equal(other.BoolValue) +} \ No newline at end of file diff --git a/internal/schema/templates/bool_value_type.gotmpl b/internal/schema/templates/bool_value_type.gotmpl new file mode 100644 index 00000000..751cd5c0 --- /dev/null +++ b/internal/schema/templates/bool_value_type.gotmpl @@ -0,0 +1,5 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +} +} \ No newline at end of file diff --git a/internal/schema/templates/bool_value_valuable.gotmpl b/internal/schema/templates/bool_value_valuable.gotmpl new file mode 100644 index 00000000..469f4614 --- /dev/null +++ b/internal/schema/templates/bool_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.BoolValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/bool_value_value.gotmpl b/internal/schema/templates/bool_value_value.gotmpl new file mode 100644 index 00000000..e4744962 --- /dev/null +++ b/internal/schema/templates/bool_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.BoolValue +} \ No newline at end of file diff --git a/internal/schema/templates/float64_from.gotmpl b/internal/schema/templates/float64_from.gotmpl new file mode 100644 index 00000000..df820abc --- /dev/null +++ b/internal/schema/templates/float64_from.gotmpl @@ -0,0 +1,14 @@ + +func (v {{.Name}}Value) From{{.AssocExtType.ToPascalCase}}(ctx context.Context, apiObject {{.AssocExtType.Type}}) ({{.Name}}Value, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return {{.Name}}Value{ +types.Float64Null(), +}, diags +} + +return {{.Name}}Value{ +types.Float64PointerValue(*apiObject), +}, diags +} diff --git a/internal/schema/templates/float64_to.gotmpl b/internal/schema/templates/float64_to.gotmpl new file mode 100644 index 00000000..874259ea --- /dev/null +++ b/internal/schema/templates/float64_to.gotmpl @@ -0,0 +1,20 @@ +func (v {{.Name}}Value) To{{.AssocExtType.ToPascalCase}}(ctx context.Context) ({{.AssocExtType.Type}}, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"{{.Name}}Value Value Is Unknown", +`"{{.Name}}Value" is unknown.`, +)) + +return nil, diags +} + +a := {{.AssocExtType.TypeReference}}(v.ValueFloat64Pointer()) + +return &a, diags +} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_equal.gotmpl b/internal/schema/templates/float64_type_equal.gotmpl new file mode 100644 index 00000000..963a06bd --- /dev/null +++ b/internal/schema/templates/float64_type_equal.gotmpl @@ -0,0 +1,9 @@ +func (t {{.Name}}Type) Equal(o attr.Type) bool { +other, ok := o.({{.Name}}Type) + +if !ok { +return false +} + +return t.Float64Type.Equal(other.Float64Type) +} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_string.gotmpl b/internal/schema/templates/float64_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/float64_type_string.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) String() string { +return "{{.Name}}Type" +} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_typable.gotmpl b/internal/schema/templates/float64_type_typable.gotmpl new file mode 100644 index 00000000..58c70d7c --- /dev/null +++ b/internal/schema/templates/float64_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.Float64Typable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_type.gotmpl b/internal/schema/templates/float64_type_type.gotmpl new file mode 100644 index 00000000..37410119 --- /dev/null +++ b/internal/schema/templates/float64_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.Float64Type +} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_value_from_float64.gotmpl b/internal/schema/templates/float64_type_value_from_float64.gotmpl new file mode 100644 index 00000000..8825dbc4 --- /dev/null +++ b/internal/schema/templates/float64_type_value_from_float64.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromFloat64(ctx context.Context, in basetypes.Float64Value) (basetypes.Float64Valuable, diag.Diagnostics) { +return {{.Name}}Value{ +Float64Value: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_value_from_terraform.gotmpl b/internal/schema/templates/float64_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..f1e6afc0 --- /dev/null +++ b/internal/schema/templates/float64_type_value_from_terraform.gotmpl @@ -0,0 +1,22 @@ + +func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.Float64Type.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.Float64Value) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromFloat64(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting Float64Value to Float64Valuable: %v", diags) +} + +return boolValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/float64_type_value_type.gotmpl b/internal/schema/templates/float64_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/float64_type_value_type.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) ValueType(ctx context.Context) attr.Value { +return {{.Name}}Value{} +} \ No newline at end of file diff --git a/internal/schema/templates/float64_value_equal.gotmpl b/internal/schema/templates/float64_value_equal.gotmpl new file mode 100644 index 00000000..a6c75d60 --- /dev/null +++ b/internal/schema/templates/float64_value_equal.gotmpl @@ -0,0 +1,10 @@ + +func (v {{.Name}}Value) Equal(o attr.Value) bool { +other, ok := o.({{.Name}}Value) + +if !ok { +return false +} + +return v.Float64Value.Equal(other.Float64Value) +} \ No newline at end of file diff --git a/internal/schema/templates/float64_value_type.gotmpl b/internal/schema/templates/float64_value_type.gotmpl new file mode 100644 index 00000000..751cd5c0 --- /dev/null +++ b/internal/schema/templates/float64_value_type.gotmpl @@ -0,0 +1,5 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +} +} \ No newline at end of file diff --git a/internal/schema/templates/float64_value_valuable.gotmpl b/internal/schema/templates/float64_value_valuable.gotmpl new file mode 100644 index 00000000..016b8f65 --- /dev/null +++ b/internal/schema/templates/float64_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.Float64Valuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/float64_value_value.gotmpl b/internal/schema/templates/float64_value_value.gotmpl new file mode 100644 index 00000000..fdaacfb7 --- /dev/null +++ b/internal/schema/templates/float64_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.Float64Value +} \ No newline at end of file diff --git a/internal/schema/templates/int64_from.gotmpl b/internal/schema/templates/int64_from.gotmpl new file mode 100644 index 00000000..5ece67b5 --- /dev/null +++ b/internal/schema/templates/int64_from.gotmpl @@ -0,0 +1,14 @@ + +func (v {{.Name}}Value) From{{.AssocExtType.ToPascalCase}}(ctx context.Context, apiObject {{.AssocExtType.Type}}) ({{.Name}}Value, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return {{.Name}}Value{ +types.Int64Null(), +}, diags +} + +return {{.Name}}Value{ +types.Int64PointerValue(*apiObject), +}, diags +} diff --git a/internal/schema/templates/int64_to.gotmpl b/internal/schema/templates/int64_to.gotmpl new file mode 100644 index 00000000..29f60285 --- /dev/null +++ b/internal/schema/templates/int64_to.gotmpl @@ -0,0 +1,20 @@ +func (v {{.Name}}Value) To{{.AssocExtType.ToPascalCase}}(ctx context.Context) ({{.AssocExtType.Type}}, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"{{.Name}}Value Value Is Unknown", +`"{{.Name}}Value" is unknown.`, +)) + +return nil, diags +} + +a := {{.AssocExtType.TypeReference}}(v.ValueInt64Pointer()) + +return &a, diags +} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_equal.gotmpl b/internal/schema/templates/int64_type_equal.gotmpl new file mode 100644 index 00000000..2f39bd1f --- /dev/null +++ b/internal/schema/templates/int64_type_equal.gotmpl @@ -0,0 +1,9 @@ +func (t {{.Name}}Type) Equal(o attr.Type) bool { +other, ok := o.({{.Name}}Type) + +if !ok { +return false +} + +return t.Int64Type.Equal(other.Int64Type) +} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_string.gotmpl b/internal/schema/templates/int64_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/int64_type_string.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) String() string { +return "{{.Name}}Type" +} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_typable.gotmpl b/internal/schema/templates/int64_type_typable.gotmpl new file mode 100644 index 00000000..4326600e --- /dev/null +++ b/internal/schema/templates/int64_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.Int64Typable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_type.gotmpl b/internal/schema/templates/int64_type_type.gotmpl new file mode 100644 index 00000000..ce3dd42e --- /dev/null +++ b/internal/schema/templates/int64_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.Int64Type +} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_value_from_int64.gotmpl b/internal/schema/templates/int64_type_value_from_int64.gotmpl new file mode 100644 index 00000000..ed5158c6 --- /dev/null +++ b/internal/schema/templates/int64_type_value_from_int64.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromInt64(ctx context.Context, in basetypes.Int64Value) (basetypes.Int64Valuable, diag.Diagnostics) { +return {{.Name}}Value{ +Int64Value: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_value_from_terraform.gotmpl b/internal/schema/templates/int64_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..0b497fd9 --- /dev/null +++ b/internal/schema/templates/int64_type_value_from_terraform.gotmpl @@ -0,0 +1,22 @@ + +func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.Int64Type.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.Int64Value) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromInt64(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting Int64Value to Int64Valuable: %v", diags) +} + +return boolValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/int64_type_value_type.gotmpl b/internal/schema/templates/int64_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/int64_type_value_type.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) ValueType(ctx context.Context) attr.Value { +return {{.Name}}Value{} +} \ No newline at end of file diff --git a/internal/schema/templates/int64_value_equal.gotmpl b/internal/schema/templates/int64_value_equal.gotmpl new file mode 100644 index 00000000..5903da86 --- /dev/null +++ b/internal/schema/templates/int64_value_equal.gotmpl @@ -0,0 +1,10 @@ + +func (v {{.Name}}Value) Equal(o attr.Value) bool { +other, ok := o.({{.Name}}Value) + +if !ok { +return false +} + +return v.Int64Value.Equal(other.Int64Value) +} \ No newline at end of file diff --git a/internal/schema/templates/int64_value_type.gotmpl b/internal/schema/templates/int64_value_type.gotmpl new file mode 100644 index 00000000..751cd5c0 --- /dev/null +++ b/internal/schema/templates/int64_value_type.gotmpl @@ -0,0 +1,5 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +} +} \ No newline at end of file diff --git a/internal/schema/templates/int64_value_valuable.gotmpl b/internal/schema/templates/int64_value_valuable.gotmpl new file mode 100644 index 00000000..a2245552 --- /dev/null +++ b/internal/schema/templates/int64_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.Int64Valuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/int64_value_value.gotmpl b/internal/schema/templates/int64_value_value.gotmpl new file mode 100644 index 00000000..2258d505 --- /dev/null +++ b/internal/schema/templates/int64_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.Int64Value +} \ No newline at end of file diff --git a/internal/schema/templates/number_from.gotmpl b/internal/schema/templates/number_from.gotmpl new file mode 100644 index 00000000..33439da7 --- /dev/null +++ b/internal/schema/templates/number_from.gotmpl @@ -0,0 +1,14 @@ + +func (v {{.Name}}Value) From{{.AssocExtType.ToPascalCase}}(ctx context.Context, apiObject {{.AssocExtType.Type}}) ({{.Name}}Value, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return {{.Name}}Value{ +types.NumberNull(), +}, diags +} + +return {{.Name}}Value{ +types.NumberValue(*apiObject), +}, diags +} diff --git a/internal/schema/templates/number_to.gotmpl b/internal/schema/templates/number_to.gotmpl new file mode 100644 index 00000000..62c41cf1 --- /dev/null +++ b/internal/schema/templates/number_to.gotmpl @@ -0,0 +1,20 @@ +func (v {{.Name}}Value) To{{.AssocExtType.ToPascalCase}}(ctx context.Context) ({{.AssocExtType.Type}}, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"{{.Name}}Value Value Is Unknown", +`"{{.Name}}Value" is unknown.`, +)) + +return nil, diags +} + +a := {{.AssocExtType.TypeReference}}(v.ValueBigFloat()) + +return &a, diags +} \ No newline at end of file diff --git a/internal/schema/templates/number_type_equal.gotmpl b/internal/schema/templates/number_type_equal.gotmpl new file mode 100644 index 00000000..be8f84fa --- /dev/null +++ b/internal/schema/templates/number_type_equal.gotmpl @@ -0,0 +1,9 @@ +func (t {{.Name}}Type) Equal(o attr.Type) bool { +other, ok := o.({{.Name}}Type) + +if !ok { +return false +} + +return t.NumberType.Equal(other.NumberType) +} \ No newline at end of file diff --git a/internal/schema/templates/number_type_string.gotmpl b/internal/schema/templates/number_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/number_type_string.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) String() string { +return "{{.Name}}Type" +} \ No newline at end of file diff --git a/internal/schema/templates/number_type_typable.gotmpl b/internal/schema/templates/number_type_typable.gotmpl new file mode 100644 index 00000000..9c8525ec --- /dev/null +++ b/internal/schema/templates/number_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.NumberTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/number_type_type.gotmpl b/internal/schema/templates/number_type_type.gotmpl new file mode 100644 index 00000000..3f9d3dc7 --- /dev/null +++ b/internal/schema/templates/number_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.NumberType +} \ No newline at end of file diff --git a/internal/schema/templates/number_type_value_from_number.gotmpl b/internal/schema/templates/number_type_value_from_number.gotmpl new file mode 100644 index 00000000..a2c7b7d4 --- /dev/null +++ b/internal/schema/templates/number_type_value_from_number.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromNumber(ctx context.Context, in basetypes.NumberValue) (basetypes.NumberValuable, diag.Diagnostics) { +return {{.Name}}Value{ +NumberValue: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/number_type_value_from_terraform.gotmpl b/internal/schema/templates/number_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..d0f9d7bb --- /dev/null +++ b/internal/schema/templates/number_type_value_from_terraform.gotmpl @@ -0,0 +1,22 @@ + +func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.NumberType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.NumberValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromNumber(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting NumberValue to NumberValuable: %v", diags) +} + +return boolValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/number_type_value_type.gotmpl b/internal/schema/templates/number_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/number_type_value_type.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) ValueType(ctx context.Context) attr.Value { +return {{.Name}}Value{} +} \ No newline at end of file diff --git a/internal/schema/templates/number_value_equal.gotmpl b/internal/schema/templates/number_value_equal.gotmpl new file mode 100644 index 00000000..75110605 --- /dev/null +++ b/internal/schema/templates/number_value_equal.gotmpl @@ -0,0 +1,10 @@ + +func (v {{.Name}}Value) Equal(o attr.Value) bool { +other, ok := o.({{.Name}}Value) + +if !ok { +return false +} + +return v.NumberValue.Equal(other.NumberValue) +} \ No newline at end of file diff --git a/internal/schema/templates/number_value_type.gotmpl b/internal/schema/templates/number_value_type.gotmpl new file mode 100644 index 00000000..751cd5c0 --- /dev/null +++ b/internal/schema/templates/number_value_type.gotmpl @@ -0,0 +1,5 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +} +} \ No newline at end of file diff --git a/internal/schema/templates/number_value_valuable.gotmpl b/internal/schema/templates/number_value_valuable.gotmpl new file mode 100644 index 00000000..bf1163c6 --- /dev/null +++ b/internal/schema/templates/number_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.NumberValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/number_value_value.gotmpl b/internal/schema/templates/number_value_value.gotmpl new file mode 100644 index 00000000..1513cdf1 --- /dev/null +++ b/internal/schema/templates/number_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.NumberValue +} \ No newline at end of file diff --git a/internal/schema/templates/string_from.gotmpl b/internal/schema/templates/string_from.gotmpl new file mode 100644 index 00000000..8b4d91ed --- /dev/null +++ b/internal/schema/templates/string_from.gotmpl @@ -0,0 +1,14 @@ + +func (v {{.Name}}Value) From{{.AssocExtType.ToPascalCase}}(ctx context.Context, apiObject {{.AssocExtType.Type}}) ({{.Name}}Value, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return {{.Name}}Value{ +types.StringNull(), +}, diags +} + +return {{.Name}}Value{ +types.StringPointerValue(*apiObject), +}, diags +} diff --git a/internal/schema/templates/string_to.gotmpl b/internal/schema/templates/string_to.gotmpl new file mode 100644 index 00000000..324f8e97 --- /dev/null +++ b/internal/schema/templates/string_to.gotmpl @@ -0,0 +1,20 @@ +func (v {{.Name}}Value) To{{.AssocExtType.ToPascalCase}}(ctx context.Context) ({{.AssocExtType.Type}}, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"{{.Name}}Value Value Is Unknown", +`"{{.Name}}Value" is unknown.`, +)) + +return nil, diags +} + +a := {{.AssocExtType.TypeReference}}(v.ValueStringPointer()) + +return &a, diags +} \ No newline at end of file diff --git a/internal/schema/templates/string_type_equal.gotmpl b/internal/schema/templates/string_type_equal.gotmpl new file mode 100644 index 00000000..ac101aef --- /dev/null +++ b/internal/schema/templates/string_type_equal.gotmpl @@ -0,0 +1,9 @@ +func (t {{.Name}}Type) Equal(o attr.Type) bool { +other, ok := o.({{.Name}}Type) + +if !ok { +return false +} + +return t.StringType.Equal(other.StringType) +} \ No newline at end of file diff --git a/internal/schema/templates/string_type_string.gotmpl b/internal/schema/templates/string_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/string_type_string.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) String() string { +return "{{.Name}}Type" +} \ No newline at end of file diff --git a/internal/schema/templates/string_type_typable.gotmpl b/internal/schema/templates/string_type_typable.gotmpl new file mode 100644 index 00000000..5d8d5aae --- /dev/null +++ b/internal/schema/templates/string_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.StringTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/string_type_type.gotmpl b/internal/schema/templates/string_type_type.gotmpl new file mode 100644 index 00000000..18e62918 --- /dev/null +++ b/internal/schema/templates/string_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.StringType +} \ No newline at end of file diff --git a/internal/schema/templates/string_type_value_from_string.gotmpl b/internal/schema/templates/string_type_value_from_string.gotmpl new file mode 100644 index 00000000..d8abf943 --- /dev/null +++ b/internal/schema/templates/string_type_value_from_string.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { +return {{.Name}}Value{ +StringValue: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/string_type_value_from_terraform.gotmpl b/internal/schema/templates/string_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..cac59c10 --- /dev/null +++ b/internal/schema/templates/string_type_value_from_terraform.gotmpl @@ -0,0 +1,22 @@ + +func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +attrValue, err := t.StringType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +boolValue, ok := attrValue.(basetypes.StringValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +boolValuable, diags := t.ValueFromString(ctx, boolValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags) +} + +return boolValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/string_type_value_type.gotmpl b/internal/schema/templates/string_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/string_type_value_type.gotmpl @@ -0,0 +1,4 @@ + +func (t {{.Name}}Type) ValueType(ctx context.Context) attr.Value { +return {{.Name}}Value{} +} \ No newline at end of file diff --git a/internal/schema/templates/string_value_equal.gotmpl b/internal/schema/templates/string_value_equal.gotmpl new file mode 100644 index 00000000..572a2838 --- /dev/null +++ b/internal/schema/templates/string_value_equal.gotmpl @@ -0,0 +1,10 @@ + +func (v {{.Name}}Value) Equal(o attr.Value) bool { +other, ok := o.({{.Name}}Value) + +if !ok { +return false +} + +return v.StringValue.Equal(other.StringValue) +} \ No newline at end of file diff --git a/internal/schema/templates/string_value_type.gotmpl b/internal/schema/templates/string_value_type.gotmpl new file mode 100644 index 00000000..751cd5c0 --- /dev/null +++ b/internal/schema/templates/string_value_type.gotmpl @@ -0,0 +1,5 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +} +} \ No newline at end of file diff --git a/internal/schema/templates/string_value_valuable.gotmpl b/internal/schema/templates/string_value_valuable.gotmpl new file mode 100644 index 00000000..aa7d1377 --- /dev/null +++ b/internal/schema/templates/string_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.StringValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/string_value_value.gotmpl b/internal/schema/templates/string_value_value.gotmpl new file mode 100644 index 00000000..d1bcf39d --- /dev/null +++ b/internal/schema/templates/string_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.StringValue +} \ No newline at end of file diff --git a/internal/schema/to_from_bool.go b/internal/schema/to_from_bool.go new file mode 100644 index 00000000..6570df26 --- /dev/null +++ b/internal/schema/to_from_bool.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromBool struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + templates map[string]string +} + +func NewToFromBool(name string, assocExtType *AssocExtType) ToFromBool { + t := map[string]string{ + "from": BoolFromTemplate, + "to": BoolToTemplate, + } + + return ToFromBool{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + templates: t, + } +} + +func (o ToFromBool) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + o.renderTo, + o.renderFrom, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (o ToFromBool) renderTo() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["to"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o ToFromBool) renderFrom() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["from"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_bool_test.go b/internal/schema/to_from_bool_test.go new file mode 100644 index 00000000..0f527879 --- /dev/null +++ b/internal/schema/to_from_bool_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-spec/code" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" +) + +func TestToFromBool_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return ExampleValue{ +types.BoolNull(), +}, diags +} + +return ExampleValue{ +types.BoolPointerValue(*apiObject), +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromBool := NewToFromBool(testCase.name, testCase.assocExtType) + + got, err := toFromBool.renderFrom() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestToFromBool_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue Value Is Unknown", +` + "`" + `"ExampleValue" is unknown.` + "`" + `, +)) + +return nil, diags +} + +a := apisdk.Type(v.ValueBoolPointer()) + +return &a, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromBool := NewToFromBool(testCase.name, testCase.assocExtType) + + got, err := toFromBool.renderTo() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/to_from_float64.go b/internal/schema/to_from_float64.go new file mode 100644 index 00000000..44f2d1ac --- /dev/null +++ b/internal/schema/to_from_float64.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromFloat64 struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + templates map[string]string +} + +func NewToFromFloat64(name string, assocExtType *AssocExtType) ToFromFloat64 { + t := map[string]string{ + "from": Float64FromTemplate, + "to": Float64ToTemplate, + } + + return ToFromFloat64{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + templates: t, + } +} + +func (o ToFromFloat64) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + o.renderTo, + o.renderFrom, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (o ToFromFloat64) renderTo() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["to"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o ToFromFloat64) renderFrom() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["from"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_float64_test.go b/internal/schema/to_from_float64_test.go new file mode 100644 index 00000000..f149b0f2 --- /dev/null +++ b/internal/schema/to_from_float64_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-spec/code" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" +) + +func TestToFromFloat64_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return ExampleValue{ +types.Float64Null(), +}, diags +} + +return ExampleValue{ +types.Float64PointerValue(*apiObject), +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromFloat64 := NewToFromFloat64(testCase.name, testCase.assocExtType) + + got, err := toFromFloat64.renderFrom() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestToFromFloat64_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue Value Is Unknown", +` + "`" + `"ExampleValue" is unknown.` + "`" + `, +)) + +return nil, diags +} + +a := apisdk.Type(v.ValueFloat64Pointer()) + +return &a, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromFloat64 := NewToFromFloat64(testCase.name, testCase.assocExtType) + + got, err := toFromFloat64.renderTo() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/to_from_int64.go b/internal/schema/to_from_int64.go new file mode 100644 index 00000000..82beddb5 --- /dev/null +++ b/internal/schema/to_from_int64.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromInt64 struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + templates map[string]string +} + +func NewToFromInt64(name string, assocExtType *AssocExtType) ToFromInt64 { + t := map[string]string{ + "from": Int64FromTemplate, + "to": Int64ToTemplate, + } + + return ToFromInt64{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + templates: t, + } +} + +func (o ToFromInt64) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + o.renderTo, + o.renderFrom, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (o ToFromInt64) renderTo() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["to"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o ToFromInt64) renderFrom() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["from"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_int64_test.go b/internal/schema/to_from_int64_test.go new file mode 100644 index 00000000..02af2fa1 --- /dev/null +++ b/internal/schema/to_from_int64_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-spec/code" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" +) + +func TestToFromInt64_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return ExampleValue{ +types.Int64Null(), +}, diags +} + +return ExampleValue{ +types.Int64PointerValue(*apiObject), +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromInt64 := NewToFromInt64(testCase.name, testCase.assocExtType) + + got, err := toFromInt64.renderFrom() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestToFromInt64_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue Value Is Unknown", +` + "`" + `"ExampleValue" is unknown.` + "`" + `, +)) + +return nil, diags +} + +a := apisdk.Type(v.ValueInt64Pointer()) + +return &a, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromInt64 := NewToFromInt64(testCase.name, testCase.assocExtType) + + got, err := toFromInt64.renderTo() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/to_from_number.go b/internal/schema/to_from_number.go new file mode 100644 index 00000000..3a78cb90 --- /dev/null +++ b/internal/schema/to_from_number.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromNumber struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + templates map[string]string +} + +func NewToFromNumber(name string, assocExtType *AssocExtType) ToFromNumber { + t := map[string]string{ + "from": NumberFromTemplate, + "to": NumberToTemplate, + } + + return ToFromNumber{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + templates: t, + } +} + +func (o ToFromNumber) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + o.renderTo, + o.renderFrom, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (o ToFromNumber) renderTo() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["to"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o ToFromNumber) renderFrom() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["from"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_number_test.go b/internal/schema/to_from_number_test.go new file mode 100644 index 00000000..4eaa4ffd --- /dev/null +++ b/internal/schema/to_from_number_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-spec/code" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" +) + +func TestToFromNumber_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return ExampleValue{ +types.NumberNull(), +}, diags +} + +return ExampleValue{ +types.NumberValue(*apiObject), +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromNumber := NewToFromNumber(testCase.name, testCase.assocExtType) + + got, err := toFromNumber.renderFrom() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestToFromNumber_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue Value Is Unknown", +` + "`" + `"ExampleValue" is unknown.` + "`" + `, +)) + +return nil, diags +} + +a := apisdk.Type(v.ValueBigFloat()) + +return &a, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromNumber := NewToFromNumber(testCase.name, testCase.assocExtType) + + got, err := toFromNumber.renderTo() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/schema/to_from_string.go b/internal/schema/to_from_string.go new file mode 100644 index 00000000..64c10125 --- /dev/null +++ b/internal/schema/to_from_string.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromString struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + templates map[string]string +} + +func NewToFromString(name string, assocExtType *AssocExtType) ToFromString { + t := map[string]string{ + "from": StringFromTemplate, + "to": StringToTemplate, + } + + return ToFromString{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + templates: t, + } +} + +func (o ToFromString) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + o.renderTo, + o.renderFrom, + } + + for _, f := range renderFuncs { + b, err := f() + + if err != nil { + return nil, err + } + + buf.Write([]byte("\n")) + + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func (o ToFromString) renderTo() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["to"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o ToFromString) renderFrom() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(o.templates["from"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AssocExtType *AssocExtType + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_string_test.go b/internal/schema/to_from_string_test.go new file mode 100644 index 00000000..3366aa30 --- /dev/null +++ b/internal/schema/to_from_string_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-spec/code" + "github.com/hashicorp/terraform-plugin-codegen-spec/schema" +) + +func TestToFromString_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return ExampleValue{ +types.StringNull(), +}, diags +} + +return ExampleValue{ +types.StringPointerValue(*apiObject), +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromString := NewToFromString(testCase.name, testCase.assocExtType) + + got, err := toFromString.renderFrom() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestToFromString_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { +var diags diag.Diagnostics + +if v.IsNull() { +return nil, diags +} + +if v.IsUnknown() { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue Value Is Unknown", +` + "`" + `"ExampleValue" is unknown.` + "`" + `, +)) + +return nil, diags +} + +a := apisdk.Type(v.ValueStringPointer()) + +return &a, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromString := NewToFromString(testCase.name, testCase.assocExtType) + + got, err := toFromString.renderTo() + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected error: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +}