diff --git a/.changes/unreleased/ENHANCEMENTS-20231023-180006.yaml b/.changes/unreleased/ENHANCEMENTS-20231023-180006.yaml new file mode 100644 index 00000000..8be7f4c9 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20231023-180006.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: Adds code generation for List, Map, Object, and Set attributes that have an + associated external type +time: 2023-10-23T18:00:06.752758+01:00 +custom: + Issue: "75" diff --git a/go.mod b/go.mod index c2d70b7b..a65fc5e0 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.20 require ( github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231019064449-867ccf6fb279 + github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231024091233-c659ac8a54fc github.com/hashicorp/terraform-plugin-framework v1.4.0 + github.com/hashicorp/terraform-plugin-go v0.19.0 github.com/mattn/go-colorable v0.1.12 github.com/mitchellh/cli v1.1.5 ) @@ -21,7 +22,6 @@ require ( github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-multierror v1.0.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.19.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.11 // indirect diff --git a/go.sum b/go.sum index 6494d33a..53729703 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.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-codegen-spec v0.1.1-0.20231024091233-c659ac8a54fc h1:VmMk5vOSJgpWOuBsI4ZBZkcsrkLq0fKoyKvKFnbBuxk= +github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231024091233-c659ac8a54fc/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/list_attribute.go b/internal/datasource_convert/list_attribute.go index 2971e541..01ad71c8 100644 --- a/internal/datasource_convert/list_attribute.go +++ b/internal/datasource_convert/list_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 convertListAttribute(a *datasource.ListAttribute) (datasource_generate.GeneratorListAttribute, error) { @@ -28,8 +29,9 @@ func convertListAttribute(a *datasource.ListAttribute) (datasource_generate.Gene DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - ElementType: a.ElementType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + ElementType: a.ElementType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/map_attribute.go b/internal/datasource_convert/map_attribute.go index c1944425..264f1809 100644 --- a/internal/datasource_convert/map_attribute.go +++ b/internal/datasource_convert/map_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 convertMapAttribute(a *datasource.MapAttribute) (datasource_generate.GeneratorMapAttribute, error) { @@ -28,8 +29,9 @@ func convertMapAttribute(a *datasource.MapAttribute) (datasource_generate.Genera DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - ElementType: a.ElementType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + ElementType: a.ElementType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/object_attribute.go b/internal/datasource_convert/object_attribute.go index 908306e6..b9f8a93c 100644 --- a/internal/datasource_convert/object_attribute.go +++ b/internal/datasource_convert/object_attribute.go @@ -10,26 +10,28 @@ 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 convertObjectAttribute(o *datasource.ObjectAttribute) (datasource_generate.GeneratorObjectAttribute, error) { - if o == nil { +func convertObjectAttribute(a *datasource.ObjectAttribute) (datasource_generate.GeneratorObjectAttribute, error) { + if a == nil { return datasource_generate.GeneratorObjectAttribute{}, fmt.Errorf("*datasource.ObjectAttribute is nil") } return datasource_generate.GeneratorObjectAttribute{ ObjectAttribute: schema.ObjectAttribute{ - Required: isRequired(o.ComputedOptionalRequired), - Optional: isOptional(o.ComputedOptionalRequired), - Computed: isComputed(o.ComputedOptionalRequired), - Sensitive: isSensitive(o.Sensitive), - Description: description(o.Description), - MarkdownDescription: description(o.Description), - DeprecationMessage: deprecationMessage(o.DeprecationMessage), + Required: isRequired(a.ComputedOptionalRequired), + Optional: isOptional(a.ComputedOptionalRequired), + Computed: isComputed(a.ComputedOptionalRequired), + Sensitive: isSensitive(a.Sensitive), + Description: description(a.Description), + MarkdownDescription: description(a.Description), + DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - AttributeTypes: o.AttributeTypes, - CustomType: o.CustomType, - Validators: o.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + AttributeTypes: a.AttributeTypes, + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_convert/set_attribute.go b/internal/datasource_convert/set_attribute.go index b55cd828..75c27e19 100644 --- a/internal/datasource_convert/set_attribute.go +++ b/internal/datasource_convert/set_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 convertSetAttribute(a *datasource.SetAttribute) (datasource_generate.GeneratorSetAttribute, error) { @@ -28,8 +29,9 @@ func convertSetAttribute(a *datasource.SetAttribute) (datasource_generate.Genera DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - ElementType: a.ElementType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + ElementType: a.ElementType, + Validators: a.Validators, }, nil } diff --git a/internal/datasource_generate/embed.go b/internal/datasource_generate/embed.go index c4ee8e00..9e089cb2 100644 --- a/internal/datasource_generate/embed.go +++ b/internal/datasource_generate/embed.go @@ -19,13 +19,13 @@ var float64AttributeTemplate string var int64AttributeTemplate string //go:embed templates/list_attribute.gotmpl -var listAttributeGoTemplate string +var listAttributeTemplate string //go:embed templates/list_nested_attribute.gotmpl var listNestedAttributeGoTemplate string //go:embed templates/map_attribute.gotmpl -var mapAttributeGoTemplate string +var mapAttributeTemplate string //go:embed templates/map_nested_attribute.gotmpl var mapNestedAttributeGoTemplate string @@ -34,10 +34,10 @@ var mapNestedAttributeGoTemplate string var numberAttributeTemplate string //go:embed templates/object_attribute.gotmpl -var objectAttributeGoTemplate string +var objectAttributeTemplate string //go:embed templates/set_attribute.gotmpl -var setAttributeGoTemplate string +var setAttributeTemplate string //go:embed templates/set_nested_attribute.gotmpl var setNestedAttributeGoTemplate string diff --git a/internal/datasource_generate/list_attribute.go b/internal/datasource_generate/list_attribute.go index 506af985..ea0b29d7 100644 --- a/internal/datasource_generate/list_attribute.go +++ b/internal/datasource_generate/list_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorListAttribute struct { schema.ListAttribute + 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 @@ -46,6 +49,12 @@ func (g GeneratorListAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -103,6 +112,7 @@ func (g GeneratorListAttribute) Equal(ga generatorschema.GeneratorAttribute) boo func (g GeneratorListAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string ElementType string GeneratorListAttribute GeneratorListAttribute } @@ -113,12 +123,19 @@ func (g GeneratorListAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorListAttribute: g, } - t, err := template.New("list_attribute").Parse(listAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.ListType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("list_attribute").Parse(listAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -139,9 +156,64 @@ func (g GeneratorListAttribute) ModelField(name generatorschema.FrameworkIdentif ValueType: model.ListValueType, } - 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 GeneratorListAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomListType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomListValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorListAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromList(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/list_attribute_test.go b/internal/datasource_generate/list_attribute_test.go index db4dddc1..40e81317 100644 --- a/internal/datasource_generate/list_attribute_test.go +++ b/internal/datasource_generate/list_attribute_test.go @@ -313,6 +313,110 @@ func TestGeneratorListAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + }, + 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: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ListAttribute", + }, + }, + }, + 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: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ListAttribute", + }, + }, + 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 { @@ -601,7 +705,44 @@ ElementType: types.StringType, }, expected: ` "list_attribute": schema.ListAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"list_attribute": schema.ListAttribute{ +CustomType: ListAttributeType{ +types.ListType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"list_attribute": schema.ListAttribute{ CustomType: my_custom_type, },`, }, @@ -962,6 +1103,37 @@ func TestGeneratorListAttribute_ModelField(t *testing.T) { TfsdkName: "list_attribute", }, }, + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "ListAttribute", + ValueType: "ListAttributeValue", + TfsdkName: "list_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "ListAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "list_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/list_nested_attribute.go b/internal/datasource_generate/list_nested_attribute.go index 5fda1523..9c9f35c2 100644 --- a/internal/datasource_generate/list_nested_attribute.go +++ b/internal/datasource_generate/list_nested_attribute.go @@ -153,7 +153,7 @@ func (g GeneratorListNestedAttribute) CustomTypeAndValue(name string) ([]byte, e return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -175,7 +175,7 @@ func (g GeneratorListNestedAttribute) CustomTypeAndValue(name string) ([]byte, e return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -215,7 +215,7 @@ func (g GeneratorListNestedAttribute) ToFromFunctions(name string) ([]byte, erro fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/list_nested_block.go b/internal/datasource_generate/list_nested_block.go index 51231ec2..a9fef335 100644 --- a/internal/datasource_generate/list_nested_block.go +++ b/internal/datasource_generate/list_nested_block.go @@ -185,7 +185,7 @@ func (g GeneratorListNestedBlock) CustomTypeAndValue(name string) ([]byte, error attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -239,7 +239,7 @@ func (g GeneratorListNestedBlock) CustomTypeAndValue(name string) ([]byte, error attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -293,7 +293,7 @@ func (g GeneratorListNestedBlock) ToFromFunctions(name string) ([]byte, error) { fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/map_attribute.go b/internal/datasource_generate/map_attribute.go index fdf6fe58..00a90c16 100644 --- a/internal/datasource_generate/map_attribute.go +++ b/internal/datasource_generate/map_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorMapAttribute struct { schema.MapAttribute + 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 @@ -46,6 +49,12 @@ func (g GeneratorMapAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -103,6 +112,7 @@ func (g GeneratorMapAttribute) Equal(ga generatorschema.GeneratorAttribute) bool func (g GeneratorMapAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string ElementType string GeneratorMapAttribute GeneratorMapAttribute } @@ -113,12 +123,19 @@ func (g GeneratorMapAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorMapAttribute: g, } - t, err := template.New("map_attribute").Parse(mapAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.MapType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("map_attribute").Parse(mapAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -139,9 +156,64 @@ func (g GeneratorMapAttribute) ModelField(name generatorschema.FrameworkIdentifi ValueType: model.MapValueType, } - 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 GeneratorMapAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomMapType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomMapValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorMapAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromMap(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/map_attribute_test.go b/internal/datasource_generate/map_attribute_test.go index 65846795..a0a3f9a8 100644 --- a/internal/datasource_generate/map_attribute_test.go +++ b/internal/datasource_generate/map_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 TestGeneratorMapAttribute_Schema(t *testing.T) { @@ -284,7 +285,44 @@ ElementType: types.StringType, }, expected: ` "map_attribute": schema.MapAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.MapAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"map_attribute": schema.MapAttribute{ +CustomType: MapAttributeType{ +types.MapType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.MapAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"map_attribute": schema.MapAttribute{ CustomType: my_custom_type, },`, }, @@ -644,6 +682,37 @@ func TestGeneratorMapAttribute_ModelField(t *testing.T) { TfsdkName: "map_attribute", }, }, + "associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "MapAttribute", + ValueType: "MapAttributeValue", + TfsdkName: "map_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "MapAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "map_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/map_nested_attribute.go b/internal/datasource_generate/map_nested_attribute.go index 73a12fa3..172d4443 100644 --- a/internal/datasource_generate/map_nested_attribute.go +++ b/internal/datasource_generate/map_nested_attribute.go @@ -153,7 +153,7 @@ func (g GeneratorMapNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -175,7 +175,7 @@ func (g GeneratorMapNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -215,7 +215,7 @@ func (g GeneratorMapNestedAttribute) ToFromFunctions(name string) ([]byte, error fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/object_attribute.go b/internal/datasource_generate/object_attribute.go index 9ae24c0b..08cc273c 100644 --- a/internal/datasource_generate/object_attribute.go +++ b/internal/datasource_generate/object_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorObjectAttribute struct { schema.ObjectAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. AttributeTypes specschema.ObjectAttributeTypes @@ -46,6 +49,12 @@ func (g GeneratorObjectAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -75,6 +84,7 @@ func (g GeneratorObjectAttribute) Schema(name generatorschema.FrameworkIdentifie type attribute struct { Name string AttributeTypes string + CustomType string GeneratorObjectAttribute GeneratorObjectAttribute } @@ -84,16 +94,19 @@ func (g GeneratorObjectAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorObjectAttribute: g, } - funcMap := template.FuncMap{ - "getAttrTypes": generatorschema.GetAttrTypes, + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.ObjectType{\nAttrTypes: %sValue{}.AttributeTypes(ctx),\n},\n}", name.ToPascalCase(), name.ToPascalCase()) } - t, err := template.New("object_attribute").Funcs(funcMap).Parse(objectAttributeGoTemplate) + t, err := template.New("object_attribute").Parse(objectAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -114,9 +127,64 @@ func (g GeneratorObjectAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.ObjectValueType, } - 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 GeneratorObjectAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + objectType := generatorschema.NewCustomObjectType(name) + + b, err := objectType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + attrTypes := generatorschema.GetAttrTypes(g.AttrTypes()) + + objectValue := generatorschema.NewCustomObjectValue(name, attrTypes) + + b, err = objectValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorObjectAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + attrTypesToFuncs := generatorschema.GetAttrTypesToFuncs(g.AttributeTypes) + + attrTypesFromFuncs := generatorschema.GetAttrTypesFromFuncs(g.AttributeTypes) + + toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, attrTypesToFuncs, attrTypesFromFuncs) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/object_attribute_test.go b/internal/datasource_generate/object_attribute_test.go index b62e87e2..074251fc 100644 --- a/internal/datasource_generate/object_attribute_test.go +++ b/internal/datasource_generate/object_attribute_test.go @@ -257,6 +257,110 @@ func TestGeneratorObjectAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + }, + 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: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ObjectAttribute", + }, + }, + }, + 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: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ObjectAttribute", + }, + }, + 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 { @@ -634,9 +738,53 @@ AttributeTypes: map[string]attr.Type{ }, expected: ` "object_attribute": schema.ObjectAttribute{ -AttributeTypes: map[string]attr.Type{ -"str": types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + AttributeTypes: specschema.ObjectAttributeTypes{ + specschema.ObjectAttributeType{ + Name: "bool", + Bool: &specschema.BoolType{}, + }, + }, + }, + expected: ` +"object_attribute": schema.ObjectAttribute{ +CustomType: ObjectAttributeType{ +types.ObjectType{ +AttrTypes: ObjectAttributeValue{}.AttributeTypes(ctx), +}, }, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + AttributeTypes: specschema.ObjectAttributeTypes{ + specschema.ObjectAttributeType{ + Name: "bool", + Bool: &specschema.BoolType{}, + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"object_attribute": schema.ObjectAttribute{ CustomType: my_custom_type, },`, }, @@ -1075,6 +1223,37 @@ func TestGeneratorObjectAttribute_ModelField(t *testing.T) { TfsdkName: "object_attribute", }, }, + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "ObjectAttribute", + ValueType: "ObjectAttributeValue", + TfsdkName: "object_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "ObjectAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "object_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/set_attribute.go b/internal/datasource_generate/set_attribute.go index fc87978d..e167086d 100644 --- a/internal/datasource_generate/set_attribute.go +++ b/internal/datasource_generate/set_attribute.go @@ -4,6 +4,8 @@ package datasource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorSetAttribute struct { schema.SetAttribute + 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 @@ -46,6 +49,12 @@ func (g GeneratorSetAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -103,6 +112,7 @@ func (g GeneratorSetAttribute) Equal(ga generatorschema.GeneratorAttribute) bool func (g GeneratorSetAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string ElementType string GeneratorSetAttribute GeneratorSetAttribute } @@ -113,12 +123,19 @@ func (g GeneratorSetAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorSetAttribute: g, } - t, err := template.New("set_attribute").Parse(setAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.SetType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("Set_attribute").Parse(setAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -139,9 +156,64 @@ func (g GeneratorSetAttribute) ModelField(name generatorschema.FrameworkIdentifi ValueType: model.SetValueType, } - 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 GeneratorSetAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomSetType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomSetValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorSetAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromSet(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/datasource_generate/set_attribute_test.go b/internal/datasource_generate/set_attribute_test.go index a6a4c096..14ef5145 100644 --- a/internal/datasource_generate/set_attribute_test.go +++ b/internal/datasource_generate/set_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 TestGeneratorSetAttribute_Schema(t *testing.T) { @@ -284,7 +285,44 @@ ElementType: types.StringType, }, expected: ` "set_attribute": schema.SetAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"set_attribute": schema.SetAttribute{ +CustomType: SetAttributeType{ +types.SetType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"set_attribute": schema.SetAttribute{ CustomType: my_custom_type, },`, }, @@ -644,6 +682,37 @@ func TestGeneratorSetAttribute_ModelField(t *testing.T) { TfsdkName: "set_attribute", }, }, + "associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "SetAttribute", + ValueType: "SetAttributeValue", + TfsdkName: "set_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "SetAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "set_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/datasource_generate/set_nested_attribute.go b/internal/datasource_generate/set_nested_attribute.go index e71e5f59..d13b1eea 100644 --- a/internal/datasource_generate/set_nested_attribute.go +++ b/internal/datasource_generate/set_nested_attribute.go @@ -153,7 +153,7 @@ func (g GeneratorSetNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -175,7 +175,7 @@ func (g GeneratorSetNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -215,7 +215,7 @@ func (g GeneratorSetNestedAttribute) ToFromFunctions(name string) ([]byte, error fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/set_nested_block.go b/internal/datasource_generate/set_nested_block.go index bbc51f81..f9e82127 100644 --- a/internal/datasource_generate/set_nested_block.go +++ b/internal/datasource_generate/set_nested_block.go @@ -185,7 +185,7 @@ func (g GeneratorSetNestedBlock) CustomTypeAndValue(name string) ([]byte, error) attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -239,7 +239,7 @@ func (g GeneratorSetNestedBlock) CustomTypeAndValue(name string) ([]byte, error) attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -293,7 +293,7 @@ func (g GeneratorSetNestedBlock) ToFromFunctions(name string) ([]byte, error) { fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/single_nested_attribute.go b/internal/datasource_generate/single_nested_attribute.go index c98ec5c3..2aacdd5c 100644 --- a/internal/datasource_generate/single_nested_attribute.go +++ b/internal/datasource_generate/single_nested_attribute.go @@ -156,7 +156,7 @@ func (g GeneratorSingleNestedAttribute) CustomTypeAndValue(name string) ([]byte, return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -178,7 +178,7 @@ func (g GeneratorSingleNestedAttribute) CustomTypeAndValue(name string) ([]byte, return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -218,7 +218,7 @@ func (g GeneratorSingleNestedAttribute) ToFromFunctions(name string) ([]byte, er fromFuncs := g.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/single_nested_block.go b/internal/datasource_generate/single_nested_block.go index 43f0c981..c500d23d 100644 --- a/internal/datasource_generate/single_nested_block.go +++ b/internal/datasource_generate/single_nested_block.go @@ -199,7 +199,7 @@ func (g GeneratorSingleNestedBlock) CustomTypeAndValue(name string) ([]byte, err attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -253,7 +253,7 @@ func (g GeneratorSingleNestedBlock) CustomTypeAndValue(name string) ([]byte, err attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -307,7 +307,7 @@ func (g GeneratorSingleNestedBlock) ToFromFunctions(name string) ([]byte, error) fromFuncs := g.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/datasource_generate/templates/list_attribute.gotmpl b/internal/datasource_generate/templates/list_attribute.gotmpl index 383d12e3..78c2a9e1 100644 --- a/internal/datasource_generate/templates/list_attribute.gotmpl +++ b/internal/datasource_generate/templates/list_attribute.gotmpl @@ -1,6 +1,10 @@ "{{.Name}}": schema.ListAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, +{{- end}} {{- template "common_attribute" .GeneratorListAttribute }} {{- if gt (len .GeneratorListAttribute.Validators) 0 }} Validators: []validator.List{ diff --git a/internal/datasource_generate/templates/map_attribute.gotmpl b/internal/datasource_generate/templates/map_attribute.gotmpl index a1f42ea8..e558f291 100644 --- a/internal/datasource_generate/templates/map_attribute.gotmpl +++ b/internal/datasource_generate/templates/map_attribute.gotmpl @@ -1,6 +1,10 @@ "{{.Name}}": schema.MapAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, +{{- end}} {{- template "common_attribute" .GeneratorMapAttribute }} {{- if gt (len .GeneratorMapAttribute.Validators) 0 }} Validators: []validator.Map{ diff --git a/internal/datasource_generate/templates/object_attribute.gotmpl b/internal/datasource_generate/templates/object_attribute.gotmpl index 612a1a52..9d846a34 100644 --- a/internal/datasource_generate/templates/object_attribute.gotmpl +++ b/internal/datasource_generate/templates/object_attribute.gotmpl @@ -1,8 +1,12 @@ "{{.Name}}": schema.ObjectAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} AttributeTypes: map[string]attr.Type{ {{.AttributeTypes}} }, +{{- end}} {{- template "common_attribute" .GeneratorObjectAttribute }} {{- if gt (len .GeneratorObjectAttribute.Validators) 0 }} Validators: []validator.Object{ diff --git a/internal/datasource_generate/templates/set_attribute.gotmpl b/internal/datasource_generate/templates/set_attribute.gotmpl index 4e0d19ac..a36f7d01 100644 --- a/internal/datasource_generate/templates/set_attribute.gotmpl +++ b/internal/datasource_generate/templates/set_attribute.gotmpl @@ -1,6 +1,10 @@ "{{.Name}}": schema.SetAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, +{{- end}} {{- template "common_attribute" .GeneratorSetAttribute }} {{- if gt (len .GeneratorSetAttribute.Validators) 0 }} Validators: []validator.Set{ diff --git a/internal/provider_convert/list_attribute.go b/internal/provider_convert/list_attribute.go index 523c598e..f1e966ed 100644 --- a/internal/provider_convert/list_attribute.go +++ b/internal/provider_convert/list_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 convertListAttribute(a *provider.ListAttribute) (provider_generate.GeneratorListAttribute, error) { @@ -27,8 +28,9 @@ func convertListAttribute(a *provider.ListAttribute) (provider_generate.Generato DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - ElementType: a.ElementType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + ElementType: a.ElementType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/map_attribute.go b/internal/provider_convert/map_attribute.go index 66783cb1..e5d6a560 100644 --- a/internal/provider_convert/map_attribute.go +++ b/internal/provider_convert/map_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 convertMapAttribute(a *provider.MapAttribute) (provider_generate.GeneratorMapAttribute, error) { @@ -27,8 +28,9 @@ func convertMapAttribute(a *provider.MapAttribute) (provider_generate.GeneratorM DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - ElementType: a.ElementType, - Validators: a.Validators, + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + ElementType: a.ElementType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/object_attribute.go b/internal/provider_convert/object_attribute.go index 4cdd983f..4039a2ea 100644 --- a/internal/provider_convert/object_attribute.go +++ b/internal/provider_convert/object_attribute.go @@ -10,24 +10,27 @@ 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 convertObjectAttribute(o *provider.ObjectAttribute) (provider_generate.GeneratorObjectAttribute, error) { - if o == nil { +func convertObjectAttribute(a *provider.ObjectAttribute) (provider_generate.GeneratorObjectAttribute, error) { + if a == nil { return provider_generate.GeneratorObjectAttribute{}, fmt.Errorf("*provider.ObjectAttribute is nil") } return provider_generate.GeneratorObjectAttribute{ ObjectAttribute: schema.ObjectAttribute{ - Required: isRequired(o.OptionalRequired), - Optional: isOptional(o.OptionalRequired), - Sensitive: isSensitive(o.Sensitive), - Description: description(o.Description), - MarkdownDescription: description(o.Description), - DeprecationMessage: deprecationMessage(o.DeprecationMessage), + Required: isRequired(a.OptionalRequired), + Optional: isOptional(a.OptionalRequired), + Sensitive: isSensitive(a.Sensitive), + Description: description(a.Description), + MarkdownDescription: description(a.Description), + DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - AttributeTypes: o.AttributeTypes, - CustomType: o.CustomType, - Validators: o.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + AttributeTypes: a.AttributeTypes, + CustomType: a.CustomType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_convert/set_attribute.go b/internal/provider_convert/set_attribute.go index 291324ba..a1f73986 100644 --- a/internal/provider_convert/set_attribute.go +++ b/internal/provider_convert/set_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 convertSetAttribute(a *provider.SetAttribute) (provider_generate.GeneratorSetAttribute, error) { @@ -26,8 +27,10 @@ func convertSetAttribute(a *provider.SetAttribute) (provider_generate.GeneratorS MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - ElementType: a.ElementType, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + ElementType: a.ElementType, + Validators: a.Validators, }, nil } diff --git a/internal/provider_generate/embed.go b/internal/provider_generate/embed.go index 87d684fe..fc876dbc 100644 --- a/internal/provider_generate/embed.go +++ b/internal/provider_generate/embed.go @@ -19,13 +19,13 @@ var float64AttributeTemplate string var int64AttributeTemplate string //go:embed templates/list_attribute.gotmpl -var listAttributeGoTemplate string +var listAttributeTemplate string //go:embed templates/list_nested_attribute.gotmpl var listNestedAttributeGoTemplate string //go:embed templates/map_attribute.gotmpl -var mapAttributeGoTemplate string +var mapAttributeTemplate string //go:embed templates/map_nested_attribute.gotmpl var mapNestedAttributeGoTemplate string @@ -34,10 +34,10 @@ var mapNestedAttributeGoTemplate string var numberAttributeTemplate string //go:embed templates/object_attribute.gotmpl -var objectAttributeGoTemplate string +var objectAttributeTemplate string //go:embed templates/set_attribute.gotmpl -var setAttributeGoTemplate string +var setAttributeTemplate string //go:embed templates/set_nested_attribute.gotmpl var setNestedAttributeGoTemplate string diff --git a/internal/provider_generate/list_attribute.go b/internal/provider_generate/list_attribute.go index e12d7496..65a1d5a8 100644 --- a/internal/provider_generate/list_attribute.go +++ b/internal/provider_generate/list_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorListAttribute struct { schema.ListAttribute + 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 @@ -46,6 +49,12 @@ func (g GeneratorListAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -99,6 +108,7 @@ func (g GeneratorListAttribute) Equal(ga generatorschema.GeneratorAttribute) boo func (g GeneratorListAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string ElementType string GeneratorListAttribute GeneratorListAttribute } @@ -109,12 +119,19 @@ func (g GeneratorListAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorListAttribute: g, } - t, err := template.New("list_attribute").Parse(listAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.ListType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("list_attribute").Parse(listAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -135,9 +152,64 @@ func (g GeneratorListAttribute) ModelField(name generatorschema.FrameworkIdentif ValueType: model.ListValueType, } - 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 GeneratorListAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomListType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomListValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorListAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromList(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/list_attribute_test.go b/internal/provider_generate/list_attribute_test.go index 36f8f41a..7ab9db9d 100644 --- a/internal/provider_generate/list_attribute_test.go +++ b/internal/provider_generate/list_attribute_test.go @@ -313,6 +313,110 @@ func TestGeneratorListAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + }, + 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: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ListAttribute", + }, + }, + }, + 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: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ListAttribute", + }, + }, + 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 { @@ -601,7 +705,44 @@ ElementType: types.StringType, }, expected: ` "list_attribute": schema.ListAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"list_attribute": schema.ListAttribute{ +CustomType: ListAttributeType{ +types.ListType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"list_attribute": schema.ListAttribute{ CustomType: my_custom_type, },`, }, @@ -946,6 +1087,37 @@ func TestGeneratorListAttribute_ModelField(t *testing.T) { TfsdkName: "list_attribute", }, }, + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "ListAttribute", + ValueType: "ListAttributeValue", + TfsdkName: "list_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "ListAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "list_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/list_nested_attribute.go b/internal/provider_generate/list_nested_attribute.go index b74a430e..ca2bc13f 100644 --- a/internal/provider_generate/list_nested_attribute.go +++ b/internal/provider_generate/list_nested_attribute.go @@ -153,7 +153,7 @@ func (g GeneratorListNestedAttribute) CustomTypeAndValue(name string) ([]byte, e return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -175,7 +175,7 @@ func (g GeneratorListNestedAttribute) CustomTypeAndValue(name string) ([]byte, e return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -215,7 +215,7 @@ func (g GeneratorListNestedAttribute) ToFromFunctions(name string) ([]byte, erro fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/list_nested_block.go b/internal/provider_generate/list_nested_block.go index 87247e4c..78be3ef1 100644 --- a/internal/provider_generate/list_nested_block.go +++ b/internal/provider_generate/list_nested_block.go @@ -185,7 +185,7 @@ func (g GeneratorListNestedBlock) CustomTypeAndValue(name string) ([]byte, error attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -239,7 +239,7 @@ func (g GeneratorListNestedBlock) CustomTypeAndValue(name string) ([]byte, error attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -293,7 +293,7 @@ func (g GeneratorListNestedBlock) ToFromFunctions(name string) ([]byte, error) { fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/map_attribute.go b/internal/provider_generate/map_attribute.go index c2826915..fd2eee9d 100644 --- a/internal/provider_generate/map_attribute.go +++ b/internal/provider_generate/map_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorMapAttribute struct { schema.MapAttribute + 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 @@ -46,6 +49,12 @@ func (g GeneratorMapAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -99,6 +108,7 @@ func (g GeneratorMapAttribute) Equal(ga generatorschema.GeneratorAttribute) bool func (g GeneratorMapAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string ElementType string GeneratorMapAttribute GeneratorMapAttribute } @@ -109,12 +119,19 @@ func (g GeneratorMapAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorMapAttribute: g, } - t, err := template.New("map_attribute").Parse(mapAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.MapType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("map_attribute").Parse(mapAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -135,9 +152,64 @@ func (g GeneratorMapAttribute) ModelField(name generatorschema.FrameworkIdentifi ValueType: model.MapValueType, } - 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 GeneratorMapAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomMapType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomMapValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorMapAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromMap(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/map_attribute_test.go b/internal/provider_generate/map_attribute_test.go index 60d918ef..0cfa4eeb 100644 --- a/internal/provider_generate/map_attribute_test.go +++ b/internal/provider_generate/map_attribute_test.go @@ -11,6 +11,7 @@ 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 TestGeneratorMapAttribute_Schema(t *testing.T) { @@ -284,7 +285,44 @@ ElementType: types.StringType, }, expected: ` "map_attribute": schema.MapAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.MapAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"map_attribute": schema.MapAttribute{ +CustomType: MapAttributeType{ +types.MapType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.MapAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"map_attribute": schema.MapAttribute{ CustomType: my_custom_type, },`, }, @@ -628,6 +666,37 @@ func TestGeneratorMapAttribute_ModelField(t *testing.T) { TfsdkName: "map_attribute", }, }, + "associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "MapAttribute", + ValueType: "MapAttributeValue", + TfsdkName: "map_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "MapAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "map_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/map_nested_attribute.go b/internal/provider_generate/map_nested_attribute.go index df29bb74..b0b694b0 100644 --- a/internal/provider_generate/map_nested_attribute.go +++ b/internal/provider_generate/map_nested_attribute.go @@ -153,7 +153,7 @@ func (g GeneratorMapNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -175,7 +175,7 @@ func (g GeneratorMapNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -215,7 +215,7 @@ func (g GeneratorMapNestedAttribute) ToFromFunctions(name string) ([]byte, error fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/object_attribute.go b/internal/provider_generate/object_attribute.go index f982a964..e135feee 100644 --- a/internal/provider_generate/object_attribute.go +++ b/internal/provider_generate/object_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorObjectAttribute struct { schema.ObjectAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. AttributeTypes specschema.ObjectAttributeTypes @@ -46,6 +49,12 @@ func (g GeneratorObjectAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -75,6 +84,7 @@ func (g GeneratorObjectAttribute) Schema(name generatorschema.FrameworkIdentifie type attribute struct { Name string AttributeTypes string + CustomType string GeneratorObjectAttribute GeneratorObjectAttribute } @@ -84,16 +94,19 @@ func (g GeneratorObjectAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorObjectAttribute: g, } - funcMap := template.FuncMap{ - "getAttrTypes": generatorschema.GetAttrTypes, + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.ObjectType{\nAttrTypes: %sValue{}.AttributeTypes(ctx),\n},\n}", name.ToPascalCase(), name.ToPascalCase()) } - t, err := template.New("object_attribute").Funcs(funcMap).Parse(objectAttributeGoTemplate) + t, err := template.New("object_attribute").Parse(objectAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -114,9 +127,64 @@ func (g GeneratorObjectAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.ObjectValueType, } - 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 GeneratorObjectAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + objectType := generatorschema.NewCustomObjectType(name) + + b, err := objectType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + attrTypes := generatorschema.GetAttrTypes(g.AttrTypes()) + + objectValue := generatorschema.NewCustomObjectValue(name, attrTypes) + + b, err = objectValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorObjectAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + attrTypesToFuncs := generatorschema.GetAttrTypesToFuncs(g.AttributeTypes) + + attrTypesFromFuncs := generatorschema.GetAttrTypesFromFuncs(g.AttributeTypes) + + toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, attrTypesToFuncs, attrTypesFromFuncs) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/object_attribute_test.go b/internal/provider_generate/object_attribute_test.go index d6dfbff5..6958358e 100644 --- a/internal/provider_generate/object_attribute_test.go +++ b/internal/provider_generate/object_attribute_test.go @@ -257,6 +257,110 @@ func TestGeneratorObjectAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + }, + 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: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ObjectAttribute", + }, + }, + }, + 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: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ObjectAttribute", + }, + }, + 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 { @@ -634,9 +738,53 @@ AttributeTypes: map[string]attr.Type{ }, expected: ` "object_attribute": schema.ObjectAttribute{ -AttributeTypes: map[string]attr.Type{ -"str": types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + AttributeTypes: specschema.ObjectAttributeTypes{ + specschema.ObjectAttributeType{ + Name: "bool", + Bool: &specschema.BoolType{}, + }, + }, + }, + expected: ` +"object_attribute": schema.ObjectAttribute{ +CustomType: ObjectAttributeType{ +types.ObjectType{ +AttrTypes: ObjectAttributeValue{}.AttributeTypes(ctx), +}, }, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + AttributeTypes: specschema.ObjectAttributeTypes{ + specschema.ObjectAttributeType{ + Name: "bool", + Bool: &specschema.BoolType{}, + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"object_attribute": schema.ObjectAttribute{ CustomType: my_custom_type, },`, }, @@ -1054,6 +1202,37 @@ func TestGeneratorObjectAttribute_ModelField(t *testing.T) { TfsdkName: "object_attribute", }, }, + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "ObjectAttribute", + ValueType: "ObjectAttributeValue", + TfsdkName: "object_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "ObjectAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "object_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/set_attribute.go b/internal/provider_generate/set_attribute.go index 1f0b4f86..96356f6a 100644 --- a/internal/provider_generate/set_attribute.go +++ b/internal/provider_generate/set_attribute.go @@ -4,6 +4,8 @@ package provider_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorSetAttribute struct { schema.SetAttribute + 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 @@ -46,6 +49,12 @@ func (g GeneratorSetAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -99,6 +108,7 @@ func (g GeneratorSetAttribute) Equal(ga generatorschema.GeneratorAttribute) bool func (g GeneratorSetAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string ElementType string GeneratorSetAttribute GeneratorSetAttribute } @@ -109,12 +119,19 @@ func (g GeneratorSetAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorSetAttribute: g, } - t, err := template.New("set_attribute").Parse(setAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.SetType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("Set_attribute").Parse(setAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -135,9 +152,64 @@ func (g GeneratorSetAttribute) ModelField(name generatorschema.FrameworkIdentifi ValueType: model.SetValueType, } - 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 GeneratorSetAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomSetType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomSetValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorSetAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromSet(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/provider_generate/set_attribute_test.go b/internal/provider_generate/set_attribute_test.go index 950aa7fa..a93469ed 100644 --- a/internal/provider_generate/set_attribute_test.go +++ b/internal/provider_generate/set_attribute_test.go @@ -11,6 +11,7 @@ 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 TestGeneratorSetAttribute_Schema(t *testing.T) { @@ -284,7 +285,44 @@ ElementType: types.StringType, }, expected: ` "set_attribute": schema.SetAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"set_attribute": schema.SetAttribute{ +CustomType: SetAttributeType{ +types.SetType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"set_attribute": schema.SetAttribute{ CustomType: my_custom_type, },`, }, @@ -628,6 +666,37 @@ func TestGeneratorSetAttribute_ModelField(t *testing.T) { TfsdkName: "set_attribute", }, }, + "associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "SetAttribute", + ValueType: "SetAttributeValue", + TfsdkName: "set_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "SetAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "set_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/provider_generate/set_nested_attribute.go b/internal/provider_generate/set_nested_attribute.go index 2da67f98..03c1dcd8 100644 --- a/internal/provider_generate/set_nested_attribute.go +++ b/internal/provider_generate/set_nested_attribute.go @@ -153,7 +153,7 @@ func (g GeneratorSetNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -175,7 +175,7 @@ func (g GeneratorSetNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -215,7 +215,7 @@ func (g GeneratorSetNestedAttribute) ToFromFunctions(name string) ([]byte, error fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/set_nested_block.go b/internal/provider_generate/set_nested_block.go index 1a5fd1d7..a1003366 100644 --- a/internal/provider_generate/set_nested_block.go +++ b/internal/provider_generate/set_nested_block.go @@ -185,7 +185,7 @@ func (g GeneratorSetNestedBlock) CustomTypeAndValue(name string) ([]byte, error) attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -239,7 +239,7 @@ func (g GeneratorSetNestedBlock) CustomTypeAndValue(name string) ([]byte, error) attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -293,7 +293,7 @@ func (g GeneratorSetNestedBlock) ToFromFunctions(name string) ([]byte, error) { fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/single_nested_attribute.go b/internal/provider_generate/single_nested_attribute.go index 772834d0..37223c86 100644 --- a/internal/provider_generate/single_nested_attribute.go +++ b/internal/provider_generate/single_nested_attribute.go @@ -156,7 +156,7 @@ func (g GeneratorSingleNestedAttribute) CustomTypeAndValue(name string) ([]byte, return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -178,7 +178,7 @@ func (g GeneratorSingleNestedAttribute) CustomTypeAndValue(name string) ([]byte, return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -218,7 +218,7 @@ func (g GeneratorSingleNestedAttribute) ToFromFunctions(name string) ([]byte, er fromFuncs := g.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/single_nested_block.go b/internal/provider_generate/single_nested_block.go index 94036cf5..86deef7c 100644 --- a/internal/provider_generate/single_nested_block.go +++ b/internal/provider_generate/single_nested_block.go @@ -199,7 +199,7 @@ func (g GeneratorSingleNestedBlock) CustomTypeAndValue(name string) ([]byte, err attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -253,7 +253,7 @@ func (g GeneratorSingleNestedBlock) CustomTypeAndValue(name string) ([]byte, err attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -307,7 +307,7 @@ func (g GeneratorSingleNestedBlock) ToFromFunctions(name string) ([]byte, error) fromFuncs := g.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/provider_generate/templates/list_attribute.gotmpl b/internal/provider_generate/templates/list_attribute.gotmpl index 383d12e3..44e3afdf 100644 --- a/internal/provider_generate/templates/list_attribute.gotmpl +++ b/internal/provider_generate/templates/list_attribute.gotmpl @@ -1,7 +1,10 @@ "{{.Name}}": schema.ListAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, -{{- template "common_attribute" .GeneratorListAttribute }} +{{- end}}{{- template "common_attribute" .GeneratorListAttribute }} {{- if gt (len .GeneratorListAttribute.Validators) 0 }} Validators: []validator.List{ {{- range .GeneratorListAttribute.Validators}} diff --git a/internal/provider_generate/templates/map_attribute.gotmpl b/internal/provider_generate/templates/map_attribute.gotmpl index a1f42ea8..e558f291 100644 --- a/internal/provider_generate/templates/map_attribute.gotmpl +++ b/internal/provider_generate/templates/map_attribute.gotmpl @@ -1,6 +1,10 @@ "{{.Name}}": schema.MapAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, +{{- end}} {{- template "common_attribute" .GeneratorMapAttribute }} {{- if gt (len .GeneratorMapAttribute.Validators) 0 }} Validators: []validator.Map{ diff --git a/internal/provider_generate/templates/object_attribute.gotmpl b/internal/provider_generate/templates/object_attribute.gotmpl index 612a1a52..9d846a34 100644 --- a/internal/provider_generate/templates/object_attribute.gotmpl +++ b/internal/provider_generate/templates/object_attribute.gotmpl @@ -1,8 +1,12 @@ "{{.Name}}": schema.ObjectAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} AttributeTypes: map[string]attr.Type{ {{.AttributeTypes}} }, +{{- end}} {{- template "common_attribute" .GeneratorObjectAttribute }} {{- if gt (len .GeneratorObjectAttribute.Validators) 0 }} Validators: []validator.Object{ diff --git a/internal/provider_generate/templates/set_attribute.gotmpl b/internal/provider_generate/templates/set_attribute.gotmpl index 4e0d19ac..e38bc9fa 100644 --- a/internal/provider_generate/templates/set_attribute.gotmpl +++ b/internal/provider_generate/templates/set_attribute.gotmpl @@ -1,7 +1,10 @@ "{{.Name}}": schema.SetAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, -{{- template "common_attribute" .GeneratorSetAttribute }} +{{- end}}{{- template "common_attribute" .GeneratorSetAttribute }} {{- if gt (len .GeneratorSetAttribute.Validators) 0 }} Validators: []validator.Set{ {{- range .GeneratorSetAttribute.Validators}} diff --git a/internal/resource_convert/list_attribute.go b/internal/resource_convert/list_attribute.go index 8bb09d59..50f97fda 100644 --- a/internal/resource_convert/list_attribute.go +++ b/internal/resource_convert/list_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 convertListAttribute(a *resource.ListAttribute) (resource_generate.GeneratorListAttribute, error) { @@ -27,10 +28,12 @@ func convertListAttribute(a *resource.ListAttribute) (resource_generate.Generato MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - ElementType: a.ElementType, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + ElementType: a.ElementType, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/map_attribute.go b/internal/resource_convert/map_attribute.go index f1474e52..f68859e1 100644 --- a/internal/resource_convert/map_attribute.go +++ b/internal/resource_convert/map_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 convertMapAttribute(a *resource.MapAttribute) (resource_generate.GeneratorMapAttribute, error) { @@ -27,10 +28,12 @@ func convertMapAttribute(a *resource.MapAttribute) (resource_generate.GeneratorM MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - ElementType: a.ElementType, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + ElementType: a.ElementType, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/object_attribute.go b/internal/resource_convert/object_attribute.go index 8dd5687f..0cd81bcd 100644 --- a/internal/resource_convert/object_attribute.go +++ b/internal/resource_convert/object_attribute.go @@ -10,27 +10,30 @@ 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 convertObjectAttribute(o *resource.ObjectAttribute) (resource_generate.GeneratorObjectAttribute, error) { - if o == nil { +func convertObjectAttribute(a *resource.ObjectAttribute) (resource_generate.GeneratorObjectAttribute, error) { + if a == nil { return resource_generate.GeneratorObjectAttribute{}, fmt.Errorf("*resource.ObjectAttribute is nil") } return resource_generate.GeneratorObjectAttribute{ ObjectAttribute: schema.ObjectAttribute{ - Required: isRequired(o.ComputedOptionalRequired), - Optional: isOptional(o.ComputedOptionalRequired), - Computed: isComputed(o.ComputedOptionalRequired), - Sensitive: isSensitive(o.Sensitive), - Description: description(o.Description), - MarkdownDescription: description(o.Description), - DeprecationMessage: deprecationMessage(o.DeprecationMessage), + Required: isRequired(a.ComputedOptionalRequired), + Optional: isOptional(a.ComputedOptionalRequired), + Computed: isComputed(a.ComputedOptionalRequired), + Sensitive: isSensitive(a.Sensitive), + Description: description(a.Description), + MarkdownDescription: description(a.Description), + DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - AttributeTypes: o.AttributeTypes, - CustomType: o.CustomType, - Default: o.Default, - PlanModifiers: o.PlanModifiers, - Validators: o.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + AttributeTypes: a.AttributeTypes, + CustomType: a.CustomType, + Default: a.Default, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_convert/set_attribute.go b/internal/resource_convert/set_attribute.go index 68c97a33..f19d6f54 100644 --- a/internal/resource_convert/set_attribute.go +++ b/internal/resource_convert/set_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 convertSetAttribute(a *resource.SetAttribute) (resource_generate.GeneratorSetAttribute, error) { @@ -27,10 +28,12 @@ func convertSetAttribute(a *resource.SetAttribute) (resource_generate.GeneratorS MarkdownDescription: description(a.Description), DeprecationMessage: deprecationMessage(a.DeprecationMessage), }, - CustomType: a.CustomType, - Default: a.Default, - ElementType: a.ElementType, - PlanModifiers: a.PlanModifiers, - Validators: a.Validators, + + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + CustomType: a.CustomType, + Default: a.Default, + ElementType: a.ElementType, + PlanModifiers: a.PlanModifiers, + Validators: a.Validators, }, nil } diff --git a/internal/resource_generate/embed.go b/internal/resource_generate/embed.go index e9c19e7e..9b6f703d 100644 --- a/internal/resource_generate/embed.go +++ b/internal/resource_generate/embed.go @@ -19,13 +19,13 @@ var float64AttributeTemplate string var int64AttributeTemplate string //go:embed templates/list_attribute.gotmpl -var listAttributeGoTemplate string +var listAttributeTemplate string //go:embed templates/list_nested_attribute.gotmpl var listNestedAttributeGoTemplate string //go:embed templates/map_attribute.gotmpl -var mapAttributeGoTemplate string +var mapAttributeTemplate string //go:embed templates/map_nested_attribute.gotmpl var mapNestedAttributeGoTemplate string @@ -34,10 +34,10 @@ var mapNestedAttributeGoTemplate string var numberAttributeTemplate string //go:embed templates/object_attribute.gotmpl -var objectAttributeGoTemplate string +var objectAttributeTemplate string //go:embed templates/set_attribute.gotmpl -var setAttributeGoTemplate string +var setAttributeTemplate string //go:embed templates/set_nested_attribute.gotmpl var setNestedAttributeGoTemplate string diff --git a/internal/resource_generate/list_attribute.go b/internal/resource_generate/list_attribute.go index 5344d7be..58e59725 100644 --- a/internal/resource_generate/list_attribute.go +++ b/internal/resource_generate/list_attribute.go @@ -4,6 +4,8 @@ package resource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorListAttribute struct { schema.ListAttribute + 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 +61,12 @@ func (g GeneratorListAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -131,6 +140,7 @@ func listDefault(d *specschema.ListDefault) string { func (g GeneratorListAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string ElementType string GeneratorListAttribute GeneratorListAttribute @@ -143,12 +153,19 @@ func (g GeneratorListAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorListAttribute: g, } - t, err := template.New("list_attribute").Parse(listAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.ListType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("list_attribute").Parse(listAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -169,9 +186,64 @@ func (g GeneratorListAttribute) ModelField(name generatorschema.FrameworkIdentif ValueType: model.ListValueType, } - 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 GeneratorListAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomListType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomListValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorListAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromList(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/list_attribute_test.go b/internal/resource_generate/list_attribute_test.go index caef73d4..a407de80 100644 --- a/internal/resource_generate/list_attribute_test.go +++ b/internal/resource_generate/list_attribute_test.go @@ -466,6 +466,110 @@ func TestGeneratorListAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + }, + 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: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ListAttribute", + }, + }, + }, + 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: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ListAttribute", + }, + }, + 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 { @@ -754,7 +858,44 @@ ElementType: types.StringType, }, expected: ` "list_attribute": schema.ListAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"list_attribute": schema.ListAttribute{ +CustomType: ListAttributeType{ +types.ListType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"list_attribute": schema.ListAttribute{ CustomType: my_custom_type, },`, }, @@ -1161,6 +1302,37 @@ func TestGeneratorListAttribute_ModelField(t *testing.T) { TfsdkName: "list_attribute", }, }, + "associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "ListAttribute", + ValueType: "ListAttributeValue", + TfsdkName: "list_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorListAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "ListAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "list_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/list_nested_attribute.go b/internal/resource_generate/list_nested_attribute.go index b61cbdec..00a9ce9a 100644 --- a/internal/resource_generate/list_nested_attribute.go +++ b/internal/resource_generate/list_nested_attribute.go @@ -178,7 +178,7 @@ func (g GeneratorListNestedAttribute) CustomTypeAndValue(name string) ([]byte, e return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -200,7 +200,7 @@ func (g GeneratorListNestedAttribute) CustomTypeAndValue(name string) ([]byte, e return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -240,7 +240,7 @@ func (g GeneratorListNestedAttribute) ToFromFunctions(name string) ([]byte, erro fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/list_nested_block.go b/internal/resource_generate/list_nested_block.go index 98ccc117..2702c1b6 100644 --- a/internal/resource_generate/list_nested_block.go +++ b/internal/resource_generate/list_nested_block.go @@ -200,7 +200,7 @@ func (g GeneratorListNestedBlock) CustomTypeAndValue(name string) ([]byte, error attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -254,7 +254,7 @@ func (g GeneratorListNestedBlock) CustomTypeAndValue(name string) ([]byte, error attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -310,7 +310,7 @@ func (g GeneratorListNestedBlock) ToFromFunctions(name string) ([]byte, error) { fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/map_attribute.go b/internal/resource_generate/map_attribute.go index ff39a965..4c407609 100644 --- a/internal/resource_generate/map_attribute.go +++ b/internal/resource_generate/map_attribute.go @@ -4,6 +4,8 @@ package resource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorMapAttribute struct { schema.MapAttribute + 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 +61,12 @@ func (g GeneratorMapAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -135,6 +144,7 @@ func mapDefault(d *specschema.MapDefault) string { func (g GeneratorMapAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string ElementType string GeneratorMapAttribute GeneratorMapAttribute @@ -147,12 +157,19 @@ func (g GeneratorMapAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorMapAttribute: g, } - t, err := template.New("map_attribute").Parse(mapAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.MapType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("map_attribute").Parse(mapAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -173,9 +190,64 @@ func (g GeneratorMapAttribute) ModelField(name generatorschema.FrameworkIdentifi ValueType: model.MapValueType, } - 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 GeneratorMapAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomMapType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomMapValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorMapAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromMap(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/map_attribute_test.go b/internal/resource_generate/map_attribute_test.go index 7e6fe9f0..4a82cf10 100644 --- a/internal/resource_generate/map_attribute_test.go +++ b/internal/resource_generate/map_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 TestGeneratorMapAttribute_Schema(t *testing.T) { @@ -284,7 +285,44 @@ ElementType: types.StringType, }, expected: ` "map_attribute": schema.MapAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.MapAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"map_attribute": schema.MapAttribute{ +CustomType: MapAttributeType{ +types.MapType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.MapAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"map_attribute": schema.MapAttribute{ CustomType: my_custom_type, },`, }, @@ -690,6 +728,37 @@ func TestGeneratorMapAttribute_ModelField(t *testing.T) { TfsdkName: "map_attribute", }, }, + "associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "MapAttribute", + ValueType: "MapAttributeValue", + TfsdkName: "map_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorMapAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "MapAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "map_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/map_nested_attribute.go b/internal/resource_generate/map_nested_attribute.go index 8e883d06..c5a4b009 100644 --- a/internal/resource_generate/map_nested_attribute.go +++ b/internal/resource_generate/map_nested_attribute.go @@ -178,7 +178,7 @@ func (g GeneratorMapNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -200,7 +200,7 @@ func (g GeneratorMapNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -240,7 +240,7 @@ func (g GeneratorMapNestedAttribute) ToFromFunctions(name string) ([]byte, error fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/object_attribute.go b/internal/resource_generate/object_attribute.go index 063a199a..4c48b25b 100644 --- a/internal/resource_generate/object_attribute.go +++ b/internal/resource_generate/object_attribute.go @@ -4,6 +4,8 @@ package resource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorObjectAttribute struct { schema.ObjectAttribute + AssociatedExternalType *generatorschema.AssocExtType // The "specschema" types are used instead of the types within the attribute // because support for extracting custom import information is required. AttributeTypes specschema.ObjectAttributeTypes @@ -58,6 +61,12 @@ func (g GeneratorObjectAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -106,6 +115,7 @@ func (g GeneratorObjectAttribute) Schema(name generatorschema.FrameworkIdentifie type attribute struct { Name string AttributeTypes string + CustomType string Default string GeneratorObjectAttribute GeneratorObjectAttribute } @@ -117,12 +127,19 @@ func (g GeneratorObjectAttribute) Schema(name generatorschema.FrameworkIdentifie GeneratorObjectAttribute: g, } - t, err := template.New("object_attribute").Parse(objectAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.ObjectType{\nAttrTypes: %sValue{}.AttributeTypes(ctx),\n},\n}", name.ToPascalCase(), name.ToPascalCase()) + } + + t, err := template.New("object_attribute").Parse(objectAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -143,9 +160,64 @@ func (g GeneratorObjectAttribute) ModelField(name generatorschema.FrameworkIdent ValueType: model.ObjectValueType, } - 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 GeneratorObjectAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + objectType := generatorschema.NewCustomObjectType(name) + + b, err := objectType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + attrTypes := generatorschema.GetAttrTypes(g.AttrTypes()) + + objectValue := generatorschema.NewCustomObjectValue(name, attrTypes) + + b, err = objectValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorObjectAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + attrTypesToFuncs := generatorschema.GetAttrTypesToFuncs(g.AttributeTypes) + + attrTypesFromFuncs := generatorschema.GetAttrTypesFromFuncs(g.AttributeTypes) + + toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, attrTypesToFuncs, attrTypesFromFuncs) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/object_attribute_test.go b/internal/resource_generate/object_attribute_test.go index 35367e1d..f03e8969 100644 --- a/internal/resource_generate/object_attribute_test.go +++ b/internal/resource_generate/object_attribute_test.go @@ -410,6 +410,110 @@ func TestGeneratorObjectAttribute_Imports(t *testing.T) { }, }, }, + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + }, + 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: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ObjectAttribute", + }, + }, + }, + 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: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Import: &code.Import{ + Path: "github.com/api", + }, + Type: "*api.ObjectAttribute", + }, + }, + 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 { @@ -787,9 +891,53 @@ AttributeTypes: map[string]attr.Type{ }, expected: ` "object_attribute": schema.ObjectAttribute{ -AttributeTypes: map[string]attr.Type{ -"str": types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + AttributeTypes: specschema.ObjectAttributeTypes{ + specschema.ObjectAttributeType{ + Name: "bool", + Bool: &specschema.BoolType{}, + }, + }, + }, + expected: ` +"object_attribute": schema.ObjectAttribute{ +CustomType: ObjectAttributeType{ +types.ObjectType{ +AttrTypes: ObjectAttributeValue{}.AttributeTypes(ctx), +}, }, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ObjectAttribute", + }, + }, + AttributeTypes: specschema.ObjectAttributeTypes{ + specschema.ObjectAttributeType{ + Name: "bool", + Bool: &specschema.BoolType{}, + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"object_attribute": schema.ObjectAttribute{ CustomType: my_custom_type, },`, }, @@ -1284,6 +1432,37 @@ func TestGeneratorObjectAttribute_ModelField(t *testing.T) { TfsdkName: "object_attribute", }, }, + "associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "ObjectAttribute", + ValueType: "ObjectAttributeValue", + TfsdkName: "object_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorObjectAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "ObjectAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "object_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/set_attribute.go b/internal/resource_generate/set_attribute.go index a97195a4..908788be 100644 --- a/internal/resource_generate/set_attribute.go +++ b/internal/resource_generate/set_attribute.go @@ -4,6 +4,8 @@ package resource_generate import ( + "bytes" + "fmt" "strings" "text/template" @@ -17,6 +19,7 @@ import ( type GeneratorSetAttribute struct { schema.SetAttribute + 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 +61,12 @@ func (g GeneratorSetAttribute) Imports() *generatorschema.Imports { imports.Append(customValidatorImports) } + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + return imports } @@ -135,6 +144,7 @@ func setDefault(d *specschema.SetDefault) string { func (g GeneratorSetAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { type attribute struct { Name string + CustomType string Default string ElementType string GeneratorSetAttribute GeneratorSetAttribute @@ -147,12 +157,19 @@ func (g GeneratorSetAttribute) Schema(name generatorschema.FrameworkIdentifier) GeneratorSetAttribute: g, } - t, err := template.New("set_attribute").Parse(setAttributeGoTemplate) + switch { + case g.CustomType != nil: + a.CustomType = g.CustomType.Type + case g.AssociatedExternalType != nil: + a.CustomType = fmt.Sprintf("%sType{\ntypes.SetType{\nElemType: %s,\n},\n}", name.ToPascalCase(), generatorschema.GetElementType(g.ElementType)) + } + + t, err := template.New("Set_attribute").Parse(setAttributeTemplate) if err != nil { return "", err } - if _, err = addCommonAttributeTemplate(t); err != nil { + if _, err = addAttributeTemplate(t); err != nil { return "", err } @@ -173,9 +190,64 @@ func (g GeneratorSetAttribute) ModelField(name generatorschema.FrameworkIdentifi ValueType: model.SetValueType, } - 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 GeneratorSetAttribute) CustomTypeAndValue(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + var buf bytes.Buffer + + listType := generatorschema.NewCustomSetType(name) + + b, err := listType.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + elemType := generatorschema.GetElementType(g.ElementType) + + listValue := generatorschema.NewCustomSetValue(name, elemType) + + b, err = listValue.Render() + + if err != nil { + return nil, err + } + + buf.Write(b) + + return buf.Bytes(), nil +} + +func (g GeneratorSetAttribute) ToFromFunctions(name string) ([]byte, error) { + if g.AssociatedExternalType == nil { + return nil, nil + } + + elementTypeType := generatorschema.GetElementType(g.ElementType) + elementTypeValue := generatorschema.GetElementValueType(g.ElementType) + elementFrom := generatorschema.GetElementFromFunc(g.ElementType) + + toFrom := generatorschema.NewToFromSet(name, g.AssociatedExternalType, elementTypeType, elementTypeValue, elementFrom) + + b, err := toFrom.Render() + + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/internal/resource_generate/set_attribute_test.go b/internal/resource_generate/set_attribute_test.go index b5df26c2..203c8feb 100644 --- a/internal/resource_generate/set_attribute_test.go +++ b/internal/resource_generate/set_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 TestGeneratorSetAttribute_Schema(t *testing.T) { @@ -284,7 +285,44 @@ ElementType: types.StringType, }, expected: ` "set_attribute": schema.SetAttribute{ -ElementType: types.StringType, +CustomType: my_custom_type, +},`, + }, + + "associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + ElementType: specschema.ElementType{ + String: &specschema.StringType{}, + }, + }, + expected: ` +"set_attribute": schema.SetAttribute{ +CustomType: SetAttributeType{ +types.SetType{ +ElemType: types.StringType, +}, +}, +},`, + }, + + "custom-type-overriding-associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.ListAttribute", + }, + }, + CustomType: &specschema.CustomType{ + Type: "my_custom_type", + }, + }, + expected: ` +"set_attribute": schema.SetAttribute{ CustomType: my_custom_type, },`, }, @@ -690,6 +728,37 @@ func TestGeneratorSetAttribute_ModelField(t *testing.T) { TfsdkName: "set_attribute", }, }, + "associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + expected: model.Field{ + Name: "SetAttribute", + ValueType: "SetAttributeValue", + TfsdkName: "set_attribute", + }, + }, + "custom-type-overriding-associated-external-type": { + input: GeneratorSetAttribute{ + AssociatedExternalType: &generatorschema.AssocExtType{ + AssociatedExternalType: &specschema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + CustomType: &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + }, + expected: model.Field{ + Name: "SetAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "set_attribute", + }, + }, } for name, testCase := range testCases { diff --git a/internal/resource_generate/set_nested_attribute.go b/internal/resource_generate/set_nested_attribute.go index 60d955cf..9aad7d8e 100644 --- a/internal/resource_generate/set_nested_attribute.go +++ b/internal/resource_generate/set_nested_attribute.go @@ -178,7 +178,7 @@ func (g GeneratorSetNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -200,7 +200,7 @@ func (g GeneratorSetNestedAttribute) CustomTypeAndValue(name string) ([]byte, er return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -240,7 +240,7 @@ func (g GeneratorSetNestedAttribute) ToFromFunctions(name string) ([]byte, error fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/set_nested_block.go b/internal/resource_generate/set_nested_block.go index 35ff7979..2ec949db 100644 --- a/internal/resource_generate/set_nested_block.go +++ b/internal/resource_generate/set_nested_block.go @@ -200,7 +200,7 @@ func (g GeneratorSetNestedBlock) CustomTypeAndValue(name string) ([]byte, error) attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -254,7 +254,7 @@ func (g GeneratorSetNestedBlock) CustomTypeAndValue(name string) ([]byte, error) attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -308,7 +308,7 @@ func (g GeneratorSetNestedBlock) ToFromFunctions(name string) ([]byte, error) { fromFuncs := g.NestedObject.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.NestedObject.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/single_nested_attribute.go b/internal/resource_generate/single_nested_attribute.go index cc50e94a..dbf120ac 100644 --- a/internal/resource_generate/single_nested_attribute.go +++ b/internal/resource_generate/single_nested_attribute.go @@ -176,7 +176,7 @@ func (g GeneratorSingleNestedAttribute) CustomTypeAndValue(name string) ([]byte, return nil, err } - objectType := generatorschema.NewCustomObjectType(name, attributeAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributeAttrValues) b, err := objectType.Render() @@ -198,7 +198,7 @@ func (g GeneratorSingleNestedAttribute) CustomTypeAndValue(name string) ([]byte, return nil, err } - objectValue := generatorschema.NewCustomObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributeTypes, attributeAttrTypes, attributeAttrValues) b, err = objectValue.Render() @@ -238,7 +238,7 @@ func (g GeneratorSingleNestedAttribute) ToFromFunctions(name string) ([]byte, er fromFuncs := g.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/single_nested_block.go b/internal/resource_generate/single_nested_block.go index c16b3cf5..2f981380 100644 --- a/internal/resource_generate/single_nested_block.go +++ b/internal/resource_generate/single_nested_block.go @@ -209,7 +209,7 @@ func (g GeneratorSingleNestedBlock) CustomTypeAndValue(name string) ([]byte, err attributesBlocksAttrValues[k] = v } - objectType := generatorschema.NewCustomObjectType(name, attributesBlocksAttrValues) + objectType := generatorschema.NewCustomNestedObjectType(name, attributesBlocksAttrValues) b, err := objectType.Render() @@ -263,7 +263,7 @@ func (g GeneratorSingleNestedBlock) CustomTypeAndValue(name string) ([]byte, err attributesBlocksAttrTypes[k] = v } - objectValue := generatorschema.NewCustomObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) + objectValue := generatorschema.NewCustomNestedObjectValue(name, attributesBlocksTypes, attributesBlocksAttrTypes, attributesBlocksAttrValues) b, err = objectValue.Render() @@ -317,7 +317,7 @@ func (g GeneratorSingleNestedBlock) ToFromFunctions(name string) ([]byte, error) fromFuncs := g.Attributes.FromFuncs() - toFrom := generatorschema.NewToFromObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) + toFrom := generatorschema.NewToFromNestedObject(name, g.AssociatedExternalType, toFuncs, fromFuncs) b, err := toFrom.Render() diff --git a/internal/resource_generate/templates/list_attribute.gotmpl b/internal/resource_generate/templates/list_attribute.gotmpl index b0088c80..953dc41b 100644 --- a/internal/resource_generate/templates/list_attribute.gotmpl +++ b/internal/resource_generate/templates/list_attribute.gotmpl @@ -1,7 +1,10 @@ "{{.Name}}": schema.ListAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, -{{- template "common_attribute" .GeneratorListAttribute }} +{{- end}}{{- template "common_attribute" .GeneratorListAttribute }} {{- if gt (len .GeneratorListAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.List{ {{- range $planmodifier := .GeneratorListAttribute.PlanModifiers}} diff --git a/internal/resource_generate/templates/map_attribute.gotmpl b/internal/resource_generate/templates/map_attribute.gotmpl index b074b839..b33467c7 100644 --- a/internal/resource_generate/templates/map_attribute.gotmpl +++ b/internal/resource_generate/templates/map_attribute.gotmpl @@ -1,6 +1,10 @@ "{{.Name}}": schema.MapAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, +{{- end}} {{- template "common_attribute" .GeneratorMapAttribute }} {{- if gt (len .GeneratorMapAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Map{ diff --git a/internal/resource_generate/templates/object_attribute.gotmpl b/internal/resource_generate/templates/object_attribute.gotmpl index efe91445..763513bf 100644 --- a/internal/resource_generate/templates/object_attribute.gotmpl +++ b/internal/resource_generate/templates/object_attribute.gotmpl @@ -1,8 +1,12 @@ "{{.Name}}": schema.ObjectAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} AttributeTypes: map[string]attr.Type{ {{.AttributeTypes}} }, +{{- end}} {{- template "common_attribute" .GeneratorObjectAttribute }} {{- if gt (len .GeneratorObjectAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Object{ diff --git a/internal/resource_generate/templates/set_attribute.gotmpl b/internal/resource_generate/templates/set_attribute.gotmpl index a72fb1b5..05bc3dce 100644 --- a/internal/resource_generate/templates/set_attribute.gotmpl +++ b/internal/resource_generate/templates/set_attribute.gotmpl @@ -1,7 +1,10 @@ "{{.Name}}": schema.SetAttribute{ +{{- if .CustomType}} +CustomType: {{.CustomType}}, +{{- else}} ElementType: {{.ElementType}}, -{{- template "common_attribute" .GeneratorSetAttribute }} +{{- end}}{{- template "common_attribute" .GeneratorSetAttribute }} {{- if gt (len .GeneratorSetAttribute.PlanModifiers) 0 }} PlanModifiers: []planmodifier.Set{ {{- range $planmodifier := .GeneratorSetAttribute.PlanModifiers}} diff --git a/internal/schema/attrtypes.go b/internal/schema/attrtypes.go index d918a737..e145db62 100644 --- a/internal/schema/attrtypes.go +++ b/internal/schema/attrtypes.go @@ -84,3 +84,71 @@ func GetAttrTypes(attrTypes specschema.ObjectAttributeTypes) string { return aTypes.String() } + +type AttrTypesToFuncs struct { + AttrValue string + ToFunc string +} + +// GetAttrTypesToFuncs returns string representations of the function that is used +// for converting to an API Go type from a framework type. +// TODO: Handle custom type, and types other than primitives. +func GetAttrTypesToFuncs(a specschema.ObjectAttributeTypes) map[string]AttrTypesToFuncs { + attrTypesFuncs := make(map[string]AttrTypesToFuncs, len(a)) + + for _, v := range a { + switch { + case v.Bool != nil: + attrTypesFuncs[v.Name] = AttrTypesToFuncs{ + AttrValue: "types.Bool", + ToFunc: "ValueBoolPointer", + } + case v.Float64 != nil: + attrTypesFuncs[v.Name] = AttrTypesToFuncs{ + AttrValue: "types.Float64", + ToFunc: "ValueFloat64Pointer", + } + case v.Int64 != nil: + attrTypesFuncs[v.Name] = AttrTypesToFuncs{ + AttrValue: "types.Int64", + ToFunc: "ValueInt64Pointer", + } + case v.Number != nil: + attrTypesFuncs[v.Name] = AttrTypesToFuncs{ + AttrValue: "types.Number", + ToFunc: "ValueBigFloat", + } + case v.String != nil: + attrTypesFuncs[v.Name] = AttrTypesToFuncs{ + AttrValue: "types.String", + ToFunc: "ValueStringPointer", + } + } + } + + return attrTypesFuncs +} + +// GetAttrTypesFromFuncs returns string representations of the function that is used +// for converting from an API Go type to a framework type. +// TODO: Handle custom type, and types other than primitives. +func GetAttrTypesFromFuncs(a specschema.ObjectAttributeTypes) map[string]string { + attrTypesFuncs := make(map[string]string, len(a)) + + for _, v := range a { + switch { + case v.Bool != nil: + attrTypesFuncs[v.Name] = "types.BoolPointerValue" + case v.Float64 != nil: + attrTypesFuncs[v.Name] = "types.Float64PointerValue" + case v.Int64 != nil: + attrTypesFuncs[v.Name] = "types.Int64PointerValue" + case v.Number != nil: + attrTypesFuncs[v.Name] = "types.NumberValue" + case v.String != nil: + attrTypesFuncs[v.Name] = "types.StringPointerValue" + } + } + + return attrTypesFuncs +} diff --git a/internal/schema/custom_list.go b/internal/schema/custom_list.go new file mode 100644 index 00000000..90d7c687 --- /dev/null +++ b/internal/schema/custom_list.go @@ -0,0 +1,349 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomListType struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomListType(name string) CustomListType { + t := map[string]string{ + "equal": ListTypeEqualTemplate, + "string": ListTypeStringTemplate, + "type": ListTypeTypeTemplate, + "typable": ListTypeTypableTemplate, + "valueFromList": ListTypeValueFromListTemplate, + "valueFromTerraform": ListTypeValueFromTerraformTemplate, + "valueType": ListTypeValueTypeTemplate, + } + + return CustomListType{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomListType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromList, + 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 CustomListType) 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 CustomListType) 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 CustomListType) 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 CustomListType) 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 CustomListType) renderValueFromList() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromList"]) + + 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 CustomListType) 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 CustomListType) 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 CustomListValue struct { + Name FrameworkIdentifier + ElementType string + templates map[string]string +} + +func NewCustomListValue(name, elemType string) CustomListValue { + t := map[string]string{ + "equal": ListValueEqualTemplate, + "type": ListValueTypeTemplate, + "valuable": ListValueValuableTemplate, + "value": ListValueValueTemplate, + } + + return CustomListValue{ + Name: FrameworkIdentifier(name), + ElementType: elemType, + templates: t, + } +} + +func (c CustomListValue) 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 CustomListValue) 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 CustomListValue) 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 + ElementType string + }{ + Name: c.Name.ToPascalCase(), + ElementType: c.ElementType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomListValue) 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 CustomListValue) 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_list_test.go b/internal/schema/custom_list_test.go new file mode 100644 index 00000000..d5cf7401 --- /dev/null +++ b/internal/schema/custom_list_test.go @@ -0,0 +1,467 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomListType_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.ListType.Equal(other.ListType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.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 TestCustomListType_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() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.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 TestCustomListType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.ListTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.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 TestCustomListType_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.ListType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.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 TestCustomListType_renderValueFromList(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.ListValue", + }, + expected: []byte(` +func (t ExampleType) ValueFromList(ctx context.Context, in basetypes.ListValue) (basetypes.ListValuable, diag.Diagnostics) { +return ExampleValue{ +ListValue: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.renderValueFromList() + + 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 TestCustomListType_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.ListType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +listValue, ok := attrValue.(basetypes.ListValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +listValuable, diags := t.ValueFromList(ctx, listValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting ListValue to ListValuable: %v", diags) +} + +return listValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.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 TestCustomListType_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() + + customListType := NewCustomListType(testCase.name) + + got, err := customListType.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 TestCustomListValue_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.ListValue.Equal(other.ListValue) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListValue := NewCustomListValue(testCase.name, testCase.elementType) + + got, err := customListValue.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 TestCustomListValue_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +ListType: basetypes.ListType{ +ElemType: types.BoolType, +}, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListValue := NewCustomListValue(testCase.name, testCase.elementType) + + got, err := customListValue.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 TestCustomListValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(`var _ basetypes.ListValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListValue := NewCustomListValue(testCase.name, testCase.elementType) + + got, err := customListValue.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 TestCustomListValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(`type ExampleValue struct { +basetypes.ListValue +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customListValue := NewCustomListValue(testCase.name, testCase.elementType) + + got, err := customListValue.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_map.go b/internal/schema/custom_map.go new file mode 100644 index 00000000..6704e51a --- /dev/null +++ b/internal/schema/custom_map.go @@ -0,0 +1,349 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomMapType struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomMapType(name string) CustomMapType { + t := map[string]string{ + "equal": MapTypeEqualTemplate, + "string": MapTypeStringTemplate, + "type": MapTypeTypeTemplate, + "typable": MapTypeTypableTemplate, + "valueFromMap": MapTypeValueFromMapTemplate, + "valueFromTerraform": MapTypeValueFromTerraformTemplate, + "valueType": MapTypeValueTypeTemplate, + } + + return CustomMapType{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomMapType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromMap, + 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 CustomMapType) 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 CustomMapType) 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 CustomMapType) 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 CustomMapType) 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 CustomMapType) renderValueFromMap() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromMap"]) + + 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 CustomMapType) 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 CustomMapType) 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 CustomMapValue struct { + Name FrameworkIdentifier + ElementType string + templates map[string]string +} + +func NewCustomMapValue(name, elemType string) CustomMapValue { + t := map[string]string{ + "equal": MapValueEqualTemplate, + "type": MapValueTypeTemplate, + "valuable": MapValueValuableTemplate, + "value": MapValueValueTemplate, + } + + return CustomMapValue{ + Name: FrameworkIdentifier(name), + ElementType: elemType, + templates: t, + } +} + +func (c CustomMapValue) 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 CustomMapValue) 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 CustomMapValue) 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 + ElementType string + }{ + Name: c.Name.ToPascalCase(), + ElementType: c.ElementType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomMapValue) 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 CustomMapValue) 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_map_test.go b/internal/schema/custom_map_test.go new file mode 100644 index 00000000..e4b6e817 --- /dev/null +++ b/internal/schema/custom_map_test.go @@ -0,0 +1,467 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomMapType_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.MapType.Equal(other.MapType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.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 TestCustomMapType_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() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.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 TestCustomMapType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.MapTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.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 TestCustomMapType_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.MapType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.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 TestCustomMapType_renderValueFromMap(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.MapValue", + }, + expected: []byte(` +func (t ExampleType) ValueFromMap(ctx context.Context, in basetypes.MapValue) (basetypes.MapValuable, diag.Diagnostics) { +return ExampleValue{ +MapValue: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.renderValueFromMap() + + 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 TestCustomMapType_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.MapType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +mapValue, ok := attrValue.(basetypes.MapValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +mapValuable, diags := t.ValueFromMap(ctx, mapValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting MapValue to MapValuable: %v", diags) +} + +return mapValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.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 TestCustomMapType_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() + + customMapType := NewCustomMapType(testCase.name) + + got, err := customMapType.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 TestCustomMapValue_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.MapValue.Equal(other.MapValue) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapValue := NewCustomMapValue(testCase.name, testCase.elementType) + + got, err := customMapValue.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 TestCustomMapValue_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +MapType: basetypes.MapType{ +ElemType: types.BoolType, +}, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapValue := NewCustomMapValue(testCase.name, testCase.elementType) + + got, err := customMapValue.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 TestCustomMapValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(`var _ basetypes.MapValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapValue := NewCustomMapValue(testCase.name, testCase.elementType) + + got, err := customMapValue.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 TestCustomMapValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(`type ExampleValue struct { +basetypes.MapValue +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customMapValue := NewCustomMapValue(testCase.name, testCase.elementType) + + got, err := customMapValue.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_nested_object.go b/internal/schema/custom_nested_object.go new file mode 100644 index 00000000..55b3096e --- /dev/null +++ b/internal/schema/custom_nested_object.go @@ -0,0 +1,633 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomNestedObjectType struct { + Name FrameworkIdentifier + AttrValues map[FrameworkIdentifier]string + templates map[string]string +} + +func NewCustomNestedObjectType(name string, attrValues map[string]string) CustomNestedObjectType { + t := map[string]string{ + "equal": NestedObjectTypeEqualTemplate, + "string": NestedObjectTypeStringTemplate, + "typable": NestedObjectTypeTypableTemplate, + "type": NestedObjectTypeTypeTemplate, + "value": NestedObjectTypeValueTemplate, + "valueFromObject": NestedObjectTypeValueFromObjectTemplate, + "valueFromTerraform": NestedObjectTypeValueFromTerraformTemplate, + "valueMust": NestedObjectTypeValueMustTemplate, + "valueNull": NestedObjectTypeValueNullTemplate, + "valueType": NestedObjectTypeValueTypeTemplate, + "valueUnknown": NestedObjectTypeValueUnknownTemplate, + } + + a := make(map[FrameworkIdentifier]string, len(attrValues)) + + for k, v := range attrValues { + a[FrameworkIdentifier(k)] = v + } + + return CustomNestedObjectType{ + Name: FrameworkIdentifier(name), + AttrValues: a, + templates: t, + } +} + +func (c CustomNestedObjectType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromObject, + c.renderValueNull, + c.renderValueUnknown, + c.renderValue, + c.renderValueMust, + 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 CustomNestedObjectType) 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 CustomNestedObjectType) 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 CustomNestedObjectType) 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 CustomNestedObjectType) 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 CustomNestedObjectType) 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 + AttrValues map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttrValues: c.AttrValues, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNestedObjectType) renderValueFromObject() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromObject"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AttrValues map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttrValues: c.AttrValues, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNestedObjectType) 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 CustomNestedObjectType) renderValueMust() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueMust"]) + + 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 CustomNestedObjectType) renderValueNull() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueNull"]) + + 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 CustomNestedObjectType) 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 +} + +func (c CustomNestedObjectType) renderValueUnknown() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueUnknown"]) + + 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 CustomNestedObjectValue struct { + Name FrameworkIdentifier + AttributeTypes map[FrameworkIdentifier]string + AttrTypes map[FrameworkIdentifier]string + AttrValues map[FrameworkIdentifier]string + templates map[string]string +} + +func NewCustomNestedObjectValue(name string, attributeTypes, attrTypes, attrValues map[string]string) CustomNestedObjectValue { + t := map[string]string{ + "attributeTypes": NestedObjectValueAttributeTypesTemplate, + "equal": NestedObjectValueEqualTemplate, + "isNull": NestedObjectValueIsNullTemplate, + "isUnknown": NestedObjectValueIsUnknownTemplate, + "string": NestedObjectValueStringTemplate, + "toObjectValue": NestedObjectValueToObjectValueTemplate, + "toTerraformValue": NestedObjectValueToTerraformValueTemplate, + "type": NestedObjectValueTypeTemplate, + "valuable": NestedObjectValueValuableTemplate, + "value": NestedObjectValueValueTemplate, + } + + attribTypes := make(map[FrameworkIdentifier]string, len(attributeTypes)) + + for k, v := range attributeTypes { + attribTypes[FrameworkIdentifier(k)] = v + } + + attrTyps := make(map[FrameworkIdentifier]string, len(attrTypes)) + + for k, v := range attrTypes { + attrTyps[FrameworkIdentifier(k)] = v + } + + attrVals := make(map[FrameworkIdentifier]string, len(attrValues)) + + for k, v := range attrValues { + attrVals[FrameworkIdentifier(k)] = v + } + + return CustomNestedObjectValue{ + Name: FrameworkIdentifier(name), + AttributeTypes: attribTypes, + AttrTypes: attrTyps, + AttrValues: attrVals, + templates: t, + } +} + +func (c CustomNestedObjectValue) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderValuable, + c.renderValue, + c.renderToTerraformValue, + c.renderIsNull, + c.renderIsUnknown, + c.renderString, + c.renderToObjectValue, + c.renderEqual, + c.renderType, + c.renderAttributeTypes, + } + + 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 CustomNestedObjectValue) renderAttributeTypes() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["attributeTypes"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AttrTypes map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttrTypes: c.AttrTypes, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNestedObjectValue) 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 + AttrValues map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttrValues: c.AttrValues, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNestedObjectValue) renderIsNull() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["isNull"]) + + 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 CustomNestedObjectValue) renderIsUnknown() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["isUnknown"]) + + 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 CustomNestedObjectValue) 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 CustomNestedObjectValue) renderToObjectValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["toObjectValue"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AttributeTypes map[FrameworkIdentifier]string + AttrTypes map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttributeTypes: c.AttributeTypes, + AttrTypes: c.AttrTypes, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNestedObjectValue) renderToTerraformValue() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["toTerraformValue"]) + + if err != nil { + return nil, err + } + + err = t.Execute(&buf, struct { + Name string + AttrTypes map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttrTypes: c.AttrTypes, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomNestedObjectValue) 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 CustomNestedObjectValue) 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 CustomNestedObjectValue) 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 + AttrValues map[FrameworkIdentifier]string + }{ + Name: c.Name.ToPascalCase(), + AttrValues: c.AttrValues, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/custom_nested_object_test.go b/internal/schema/custom_nested_object_test.go new file mode 100644 index 00000000..09405074 --- /dev/null +++ b/internal/schema/custom_nested_object_test.go @@ -0,0 +1,1075 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomNestedObjectType_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.ObjectType.Equal(other.ObjectType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.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 TestCustomNestedObjectType_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() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.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 TestCustomNestedObjectType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.ObjectTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.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 TestCustomNestedObjectType_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.ObjectType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.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 TestCustomNestedObjectType_renderValue(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 NewExampleValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 +ctx := context.Background() + +for name, attributeType := range attributeTypes { +attribute, ok := attributes[name] + +if !ok { +diags.AddError( +"Missing ExampleValue Attribute Value", +"While creating a ExampleValue value, a missing attribute value was detected. "+ +"A ExampleValue must contain values for all attributes, even if null or unknown. "+ +"This is always an issue with the provider and should be reported to the provider developers.\n\n"+ +fmt.Sprintf("ExampleValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), +) + +continue +} + +if !attributeType.Equal(attribute.Type(ctx)) { +diags.AddError( +"Invalid ExampleValue Attribute Type", +"While creating a ExampleValue value, an invalid attribute value was detected. "+ +"A ExampleValue must use a matching attribute type for the value. "+ +"This is always an issue with the provider and should be reported to the provider developers.\n\n"+ +fmt.Sprintf("ExampleValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ +fmt.Sprintf("ExampleValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), +) +} +} + +for name := range attributes { +_, ok := attributeTypes[name] + +if !ok { +diags.AddError( +"Extra ExampleValue Attribute Value", +"While creating a ExampleValue value, an extra attribute value was detected. "+ +"A ExampleValue must not contain values beyond the expected attribute types. "+ +"This is always an issue with the provider and should be reported to the provider developers.\n\n"+ +fmt.Sprintf("Extra ExampleValue Attribute Name: %s", name), +) +} +} + +if diags.HasError() { +return NewExampleValueUnknown(), diags +} + + +boolAttributeAttribute, ok := attributes["bool_attribute"] + +if !ok { +diags.AddError( +"Attribute Missing", +` + "`bool_attribute is missing from object`" + `) + +return NewExampleValueUnknown(), diags +} + +boolAttributeVal, ok := boolAttributeAttribute.(basetypes.BoolValue) + +if !ok { +diags.AddError( +"Attribute Wrong Type", +fmt.Sprintf(` + "`bool_attribute expected to be basetypes.BoolValue, was: %T`" + `, boolAttributeAttribute)) +} + + +if diags.HasError() { +return NewExampleValueUnknown(), diags +} + +return ExampleValue{ +BoolAttribute: boolAttributeVal, +state: attr.ValueStateKnown, +}, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, testCase.attrValues) + + got, err := customObjectType.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) + } + }) + } +} + +func TestCustomNestedObjectType_renderValueFromObject(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) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { +var diags diag.Diagnostics + +attributes := in.Attributes() + + +boolAttributeAttribute, ok := attributes["bool_attribute"] + +if !ok { +diags.AddError( +"Attribute Missing", +` + "`bool_attribute is missing from object`" + `) + +return nil, diags +} + +boolAttributeVal, ok := boolAttributeAttribute.(basetypes.BoolValue) + +if !ok { +diags.AddError( +"Attribute Wrong Type", +fmt.Sprintf(` + "`bool_attribute expected to be basetypes.BoolValue, was: %T`" + `, boolAttributeAttribute)) +} + + +if diags.HasError() { +return nil, diags +} + +return ExampleValue{ +BoolAttribute: boolAttributeVal, +state: attr.ValueStateKnown, +}, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, testCase.attrValues) + + got, err := customObjectType.renderValueFromObject() + + 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 TestCustomNestedObjectType_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) { +if in.Type() == nil { +return NewExampleValueNull(), nil +} + +if !in.Type().Equal(t.TerraformType(ctx)) { +return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) +} + +if !in.IsKnown() { +return NewExampleValueUnknown(), nil +} + +if in.IsNull() { +return NewExampleValueNull(), nil +} + +attributes := map[string]attr.Value{} + +val := map[string]tftypes.Value{} + +err := in.As(&val) + +if err != nil { +return nil, err +} + +for k, v := range val { +a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + +if err != nil { +return nil, err +} + +attributes[k] = a +} + +return NewExampleValueMust(t.AttrTypes, attributes), nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.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 TestCustomNestedObjectType_renderValueMust(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func NewExampleValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ExampleValue { +object, diags := NewExampleValue(attributeTypes, attributes) + +if diags.HasError() { +// This could potentially be added to the diag package. +diagsStrings := make([]string, 0, len(diags)) + +for _, diagnostic := range diags { +diagsStrings = append(diagsStrings, fmt.Sprintf( +"%s | %s | %s", +diagnostic.Severity(), +diagnostic.Summary(), +diagnostic.Detail())) +} + +panic("NewExampleValueMust received error(s): " + strings.Join(diagsStrings, "\n")) +} + +return object +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.renderValueMust() + + 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 TestCustomNestedObjectType_renderValueNull(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func NewExampleValueNull() ExampleValue { +return ExampleValue{ +state: attr.ValueStateNull, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.renderValueNull() + + 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 TestCustomNestedObjectType_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() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.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 TestCustomNestedObjectType_renderValueUnknown(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func NewExampleValueUnknown() ExampleValue { +return ExampleValue{ +state: attr.ValueStateUnknown, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectType := NewCustomNestedObjectType(testCase.name, nil) + + got, err := customObjectType.renderValueUnknown() + + 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 TestCustomNestedObjectValue_renderAttributeTypes(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrTypes map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrTypes: map[string]string{ + "bool_attribute": "basetypes.BoolType{}", + }, + expected: []byte(` +func (v ExampleValue) AttributeTypes(ctx context.Context) map[string]attr.Type { +return map[string]attr.Type{ +"bool_attribute": basetypes.BoolType{}, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, testCase.attrTypes, nil) + + got, err := customObjectValue.renderAttributeTypes() + + 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 TestCustomNestedObjectValue_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 +} + +if v.state != other.state { +return false +} + +if v.state != attr.ValueStateKnown { +return true +} + + +if !v.BoolAttribute.Equal(other.BoolAttribute) { +return false +} + + +return true +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, testCase.attrValues) + + got, err := customObjectValue.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 TestCustomNestedObjectValue_renderIsNull(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) IsNull() bool { +return v.state == attr.ValueStateNull +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, nil) + + got, err := customObjectValue.renderIsNull() + + 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 TestCustomNestedObjectValue_renderIsUnknown(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) IsUnknown() bool { +return v.state == attr.ValueStateUnknown +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, nil) + + got, err := customObjectValue.renderIsUnknown() + + 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 TestCustomNestedObjectValue_renderString(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(` +func (v ExampleValue) String() string { +return "ExampleValue" +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, nil) + + got, err := customObjectValue.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 TestCustomNestedObjectValue_renderToObjectValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attributeTypes map[string]string + attrTypes map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attributeTypes: map[string]string{ + "bool_attribute": "Bool", + }, + attrTypes: map[string]string{ + "bool_attribute": "basetypes.BoolType{}", + }, + expected: []byte(` +func (v ExampleValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { +objVal, diags := types.ObjectValue( +map[string]attr.Type{ +"bool_attribute": basetypes.BoolType{}, +}, +map[string]attr.Value{ +"bool_attribute": v.BoolAttribute, +}) + +return objVal, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, testCase.attributeTypes, testCase.attrTypes, nil) + + got, err := customObjectValue.renderToObjectValue() + + 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 TestCustomNestedObjectValue_renderToTerraformValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + attrTypes map[string]string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + attrTypes: map[string]string{ + "bool_attribute": "basetypes.BoolType{}", + }, + expected: []byte(`func (v ExampleValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { +attrTypes := make(map[string]tftypes.Type, 1) + +var val tftypes.Value +var err error + + +attrTypes["bool_attribute"] = basetypes.BoolType{}.TerraformType(ctx) + +objectType := tftypes.Object{AttributeTypes: attrTypes} + +switch v.state { +case attr.ValueStateKnown: +vals := make(map[string]tftypes.Value, 1) + + +val, err = v.BoolAttribute.ToTerraformValue(ctx) + +if err != nil { +return tftypes.NewValue(objectType, tftypes.UnknownValue), err +} + +vals["bool_attribute"] = val + + + +if err := tftypes.ValidateValue(objectType, vals); err != nil { +return tftypes.NewValue(objectType, tftypes.UnknownValue), err +} + +return tftypes.NewValue(objectType, vals), nil +case attr.ValueStateNull: +return tftypes.NewValue(objectType, nil), nil +case attr.ValueStateUnknown: +return tftypes.NewValue(objectType, tftypes.UnknownValue), nil +default: +panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, testCase.attrTypes, nil) + + got, err := customObjectValue.renderToTerraformValue() + + 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 TestCustomNestedObjectValue_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{ +basetypes.ObjectType{ +AttrTypes: v.AttributeTypes(ctx), +}, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, nil) + + got, err := customObjectValue.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 TestCustomNestedObjectValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.ObjectValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, nil) + + got, err := customObjectValue.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 TestCustomNestedObjectValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`type ExampleValue struct { +state attr.ValueState +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customObjectValue := NewCustomNestedObjectValue(testCase.name, nil, nil, nil) + + got, err := customObjectValue.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) + } + }) + } +} + +var equateErrorMessage = cmp.Comparer(func(x, y error) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + + return x.Error() == y.Error() +}) diff --git a/internal/schema/custom_object.go b/internal/schema/custom_object.go index 0191f8a0..abc1d55e 100644 --- a/internal/schema/custom_object.go +++ b/internal/schema/custom_object.go @@ -9,36 +9,24 @@ import ( ) type CustomObjectType struct { - Name FrameworkIdentifier - AttrValues map[FrameworkIdentifier]string - templates map[string]string + Name FrameworkIdentifier + templates map[string]string } -func NewCustomObjectType(name string, attrValues map[string]string) CustomObjectType { +func NewCustomObjectType(name string) CustomObjectType { t := map[string]string{ "equal": ObjectTypeEqualTemplate, "string": ObjectTypeStringTemplate, - "typable": ObjectTypeTypableTemplate, "type": ObjectTypeTypeTemplate, - "value": ObjectTypeValueTemplate, + "typable": ObjectTypeTypableTemplate, "valueFromObject": ObjectTypeValueFromObjectTemplate, "valueFromTerraform": ObjectTypeValueFromTerraformTemplate, - "valueMust": ObjectTypeValueMustTemplate, - "valueNull": ObjectTypeValueNullTemplate, "valueType": ObjectTypeValueTypeTemplate, - "valueUnknown": ObjectTypeValueUnknownTemplate, - } - - a := make(map[FrameworkIdentifier]string, len(attrValues)) - - for k, v := range attrValues { - a[FrameworkIdentifier(k)] = v } return CustomObjectType{ - Name: FrameworkIdentifier(name), - AttrValues: a, - templates: t, + Name: FrameworkIdentifier(name), + templates: t, } } @@ -51,10 +39,6 @@ func (c CustomObjectType) Render() ([]byte, error) { c.renderEqual, c.renderString, c.renderValueFromObject, - c.renderValueNull, - c.renderValueUnknown, - c.renderValue, - c.renderValueMust, c.renderValueFromTerraform, c.renderValueType, } @@ -118,28 +102,6 @@ func (c CustomObjectType) renderString() ([]byte, error) { return buf.Bytes(), nil } -func (c CustomObjectType) 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 CustomObjectType) renderType() ([]byte, error) { var buf bytes.Buffer @@ -162,58 +124,10 @@ func (c CustomObjectType) renderType() ([]byte, error) { return buf.Bytes(), nil } -func (c CustomObjectType) 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 - AttrValues map[FrameworkIdentifier]string - }{ - Name: c.Name.ToPascalCase(), - AttrValues: c.AttrValues, - }) - - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c CustomObjectType) renderValueFromObject() ([]byte, error) { - var buf bytes.Buffer - - t, err := template.New("").Parse(c.templates["valueFromObject"]) - - if err != nil { - return nil, err - } - - err = t.Execute(&buf, struct { - Name string - AttrValues map[FrameworkIdentifier]string - }{ - Name: c.Name.ToPascalCase(), - AttrValues: c.AttrValues, - }) - - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c CustomObjectType) renderValueFromTerraform() ([]byte, error) { +func (c CustomObjectType) renderTypable() ([]byte, error) { var buf bytes.Buffer - t, err := template.New("").Parse(c.templates["valueFromTerraform"]) + t, err := template.New("").Parse(c.templates["typable"]) if err != nil { return nil, err @@ -232,10 +146,10 @@ func (c CustomObjectType) renderValueFromTerraform() ([]byte, error) { return buf.Bytes(), nil } -func (c CustomObjectType) renderValueMust() ([]byte, error) { +func (c CustomObjectType) renderValueFromObject() ([]byte, error) { var buf bytes.Buffer - t, err := template.New("").Parse(c.templates["valueMust"]) + t, err := template.New("").Parse(c.templates["valueFromObject"]) if err != nil { return nil, err @@ -254,10 +168,10 @@ func (c CustomObjectType) renderValueMust() ([]byte, error) { return buf.Bytes(), nil } -func (c CustomObjectType) renderValueNull() ([]byte, error) { +func (c CustomObjectType) renderValueFromTerraform() ([]byte, error) { var buf bytes.Buffer - t, err := template.New("").Parse(c.templates["valueNull"]) + t, err := template.New("").Parse(c.templates["valueFromTerraform"]) if err != nil { return nil, err @@ -298,74 +212,25 @@ func (c CustomObjectType) renderValueType() ([]byte, error) { return buf.Bytes(), nil } -func (c CustomObjectType) renderValueUnknown() ([]byte, error) { - var buf bytes.Buffer - - t, err := template.New("").Parse(c.templates["valueUnknown"]) - - 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 CustomObjectValue struct { - Name FrameworkIdentifier - AttributeTypes map[FrameworkIdentifier]string - AttrTypes map[FrameworkIdentifier]string - AttrValues map[FrameworkIdentifier]string - templates map[string]string + Name FrameworkIdentifier + AttrTypes string + templates map[string]string } -func NewCustomObjectValue(name string, attributeTypes, attrTypes, attrValues map[string]string) CustomObjectValue { +func NewCustomObjectValue(name, attrTypes string) CustomObjectValue { t := map[string]string{ - "attributeTypes": ObjectValueAttributeTypesTemplate, - "equal": ObjectValueEqualTemplate, - "isNull": ObjectValueIsNullTemplate, - "isUnknown": ObjectValueIsUnknownTemplate, - "string": ObjectValueStringTemplate, - "toObjectValue": ObjectValueToObjectValueTemplate, - "toTerraformValue": ObjectValueToTerraformValueTemplate, - "type": ObjectValueTypeTemplate, - "valuable": ObjectValueValuableTemplate, - "value": ObjectValueValueTemplate, - } - - attribTypes := make(map[FrameworkIdentifier]string, len(attributeTypes)) - - for k, v := range attributeTypes { - attribTypes[FrameworkIdentifier(k)] = v - } - - attrTyps := make(map[FrameworkIdentifier]string, len(attrTypes)) - - for k, v := range attrTypes { - attrTyps[FrameworkIdentifier(k)] = v - } - - attrVals := make(map[FrameworkIdentifier]string, len(attrValues)) - - for k, v := range attrValues { - attrVals[FrameworkIdentifier(k)] = v + "attributeTypes": ObjectValueAttributeTypesTemplate, + "equal": ObjectValueEqualTemplate, + "type": ObjectValueTypeTemplate, + "valuable": ObjectValueValuableTemplate, + "value": ObjectValueValueTemplate, } return CustomObjectValue{ - Name: FrameworkIdentifier(name), - AttributeTypes: attribTypes, - AttrTypes: attrTyps, - AttrValues: attrVals, - templates: t, + Name: FrameworkIdentifier(name), + AttrTypes: attrTypes, + templates: t, } } @@ -375,11 +240,6 @@ func (c CustomObjectValue) Render() ([]byte, error) { renderFuncs := []func() ([]byte, error){ c.renderValuable, c.renderValue, - c.renderToTerraformValue, - c.renderIsNull, - c.renderIsUnknown, - c.renderString, - c.renderToObjectValue, c.renderEqual, c.renderType, c.renderAttributeTypes, @@ -411,7 +271,7 @@ func (c CustomObjectValue) renderAttributeTypes() ([]byte, error) { err = t.Execute(&buf, struct { Name string - AttrTypes map[FrameworkIdentifier]string + AttrTypes string }{ Name: c.Name.ToPascalCase(), AttrTypes: c.AttrTypes, @@ -433,52 +293,6 @@ func (c CustomObjectValue) renderEqual() ([]byte, error) { return nil, err } - err = t.Execute(&buf, struct { - Name string - AttrValues map[FrameworkIdentifier]string - }{ - Name: c.Name.ToPascalCase(), - AttrValues: c.AttrValues, - }) - - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c CustomObjectValue) renderIsNull() ([]byte, error) { - var buf bytes.Buffer - - t, err := template.New("").Parse(c.templates["isNull"]) - - 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 CustomObjectValue) renderIsUnknown() ([]byte, error) { - var buf bytes.Buffer - - t, err := template.New("").Parse(c.templates["isUnknown"]) - - if err != nil { - return nil, err - } - err = t.Execute(&buf, struct { Name string }{ @@ -492,78 +306,6 @@ func (c CustomObjectValue) renderIsUnknown() ([]byte, error) { return buf.Bytes(), nil } -func (c CustomObjectValue) 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 CustomObjectValue) renderToObjectValue() ([]byte, error) { - var buf bytes.Buffer - - t, err := template.New("").Parse(c.templates["toObjectValue"]) - - if err != nil { - return nil, err - } - - err = t.Execute(&buf, struct { - Name string - AttributeTypes map[FrameworkIdentifier]string - AttrTypes map[FrameworkIdentifier]string - }{ - Name: c.Name.ToPascalCase(), - AttributeTypes: c.AttributeTypes, - AttrTypes: c.AttrTypes, - }) - - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c CustomObjectValue) renderToTerraformValue() ([]byte, error) { - var buf bytes.Buffer - - t, err := template.New("").Parse(c.templates["toTerraformValue"]) - - if err != nil { - return nil, err - } - - err = t.Execute(&buf, struct { - Name string - AttrTypes map[FrameworkIdentifier]string - }{ - Name: c.Name.ToPascalCase(), - AttrTypes: c.AttrTypes, - }) - - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - func (c CustomObjectValue) renderType() ([]byte, error) { var buf bytes.Buffer @@ -574,9 +316,11 @@ func (c CustomObjectValue) renderType() ([]byte, error) { } err = t.Execute(&buf, struct { - Name string + Name string + ElementType string }{ - Name: c.Name.ToPascalCase(), + Name: c.Name.ToPascalCase(), + ElementType: c.AttrTypes, }) if err != nil { @@ -618,11 +362,9 @@ func (c CustomObjectValue) renderValue() ([]byte, error) { } err = t.Execute(&buf, struct { - Name string - AttrValues map[FrameworkIdentifier]string + Name string }{ - Name: c.Name.ToPascalCase(), - AttrValues: c.AttrValues, + Name: c.Name.ToPascalCase(), }) if err != nil { diff --git a/internal/schema/custom_object_test.go b/internal/schema/custom_object_test.go index 753148fb..07a83e45 100644 --- a/internal/schema/custom_object_test.go +++ b/internal/schema/custom_object_test.go @@ -37,7 +37,7 @@ return t.ObjectType.Equal(other.ObjectType) t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, nil) + customObjectType := NewCustomObjectType(testCase.name) got, err := customObjectType.renderEqual() @@ -75,7 +75,7 @@ return "ExampleType" t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, nil) + customObjectType := NewCustomObjectType(testCase.name) got, err := customObjectType.renderString() @@ -110,7 +110,7 @@ func TestCustomObjectType_renderTypable(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, nil) + customObjectType := NewCustomObjectType(testCase.name) got, err := customObjectType.renderTypable() @@ -147,7 +147,7 @@ basetypes.ObjectType t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, nil) + customObjectType := NewCustomObjectType(testCase.name) got, err := customObjectType.renderType() @@ -162,125 +162,6 @@ basetypes.ObjectType } } -func TestCustomObjectType_renderValue(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 NewExampleValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ExampleValue, diag.Diagnostics) { -var diags diag.Diagnostics - -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 -ctx := context.Background() - -for name, attributeType := range attributeTypes { -attribute, ok := attributes[name] - -if !ok { -diags.AddError( -"Missing ExampleValue Attribute Value", -"While creating a ExampleValue value, a missing attribute value was detected. "+ -"A ExampleValue must contain values for all attributes, even if null or unknown. "+ -"This is always an issue with the provider and should be reported to the provider developers.\n\n"+ -fmt.Sprintf("ExampleValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), -) - -continue -} - -if !attributeType.Equal(attribute.Type(ctx)) { -diags.AddError( -"Invalid ExampleValue Attribute Type", -"While creating a ExampleValue value, an invalid attribute value was detected. "+ -"A ExampleValue must use a matching attribute type for the value. "+ -"This is always an issue with the provider and should be reported to the provider developers.\n\n"+ -fmt.Sprintf("ExampleValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ -fmt.Sprintf("ExampleValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), -) -} -} - -for name := range attributes { -_, ok := attributeTypes[name] - -if !ok { -diags.AddError( -"Extra ExampleValue Attribute Value", -"While creating a ExampleValue value, an extra attribute value was detected. "+ -"A ExampleValue must not contain values beyond the expected attribute types. "+ -"This is always an issue with the provider and should be reported to the provider developers.\n\n"+ -fmt.Sprintf("Extra ExampleValue Attribute Name: %s", name), -) -} -} - -if diags.HasError() { -return NewExampleValueUnknown(), diags -} - - -boolAttributeAttribute, ok := attributes["bool_attribute"] - -if !ok { -diags.AddError( -"Attribute Missing", -` + "`bool_attribute is missing from object`" + `) - -return NewExampleValueUnknown(), diags -} - -boolAttributeVal, ok := boolAttributeAttribute.(basetypes.BoolValue) - -if !ok { -diags.AddError( -"Attribute Wrong Type", -fmt.Sprintf(` + "`bool_attribute expected to be basetypes.BoolValue, was: %T`" + `, boolAttributeAttribute)) -} - - -if diags.HasError() { -return NewExampleValueUnknown(), diags -} - -return ExampleValue{ -BoolAttribute: boolAttributeVal, -state: attr.ValueStateKnown, -}, diags -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectType := NewCustomObjectType(testCase.name, testCase.attrValues) - - got, err := customObjectType.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) - } - }) - } -} - func TestCustomObjectType_renderValueFromObject(t *testing.T) { t.Parallel() @@ -293,42 +174,13 @@ func TestCustomObjectType_renderValueFromObject(t *testing.T) { "default": { name: "Example", attrValues: map[string]string{ - "bool_attribute": "basetypes.BoolValue", + "bool_attribute": "basetypes.ObjectValue", }, expected: []byte(` func (t ExampleType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { -var diags diag.Diagnostics - -attributes := in.Attributes() - - -boolAttributeAttribute, ok := attributes["bool_attribute"] - -if !ok { -diags.AddError( -"Attribute Missing", -` + "`bool_attribute is missing from object`" + `) - -return nil, diags -} - -boolAttributeVal, ok := boolAttributeAttribute.(basetypes.BoolValue) - -if !ok { -diags.AddError( -"Attribute Wrong Type", -fmt.Sprintf(` + "`bool_attribute expected to be basetypes.BoolValue, was: %T`" + `, boolAttributeAttribute)) -} - - -if diags.HasError() { -return nil, diags -} - return ExampleValue{ -BoolAttribute: boolAttributeVal, -state: attr.ValueStateKnown, -}, diags +ObjectValue: in, +}, nil }`), }, } @@ -339,7 +191,7 @@ state: attr.ValueStateKnown, t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, testCase.attrValues) + customObjectType := NewCustomObjectType(testCase.name) got, err := customObjectType.renderValueFromObject() @@ -366,138 +218,25 @@ func TestCustomObjectType_renderValueFromTerraform(t *testing.T) { name: "Example", expected: []byte(` func (t ExampleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { -if in.Type() == nil { -return NewExampleValueNull(), nil -} - -if !in.Type().Equal(t.TerraformType(ctx)) { -return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) -} - -if !in.IsKnown() { -return NewExampleValueUnknown(), nil -} - -if in.IsNull() { -return NewExampleValueNull(), nil -} - -attributes := map[string]attr.Value{} - -val := map[string]tftypes.Value{} - -err := in.As(&val) +attrValue, err := t.ObjectType.ValueFromTerraform(ctx, in) if err != nil { return nil, err } -for k, v := range val { -a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - -if err != nil { -return nil, err -} - -attributes[k] = a -} - -return NewExampleValueMust(t.AttrTypes, attributes), nil -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectType := NewCustomObjectType(testCase.name, nil) - - got, err := customObjectType.renderValueFromTerraform() - - if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { - t.Errorf("unexpected error: %s", diff) - } +objectValue, ok := attrValue.(basetypes.ObjectValue) - if diff := cmp.Diff(got, testCase.expected); diff != "" { - t.Errorf("unexpected difference: %s", diff) - } - }) - } +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) } -func TestCustomObjectType_renderValueMust(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - expected: []byte(` -func NewExampleValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ExampleValue { -object, diags := NewExampleValue(attributeTypes, attributes) +objectValuable, diags := t.ValueFromObject(ctx, objectValue) if diags.HasError() { -// This could potentially be added to the diag package. -diagsStrings := make([]string, 0, len(diags)) - -for _, diagnostic := range diags { -diagsStrings = append(diagsStrings, fmt.Sprintf( -"%s | %s | %s", -diagnostic.Severity(), -diagnostic.Summary(), -diagnostic.Detail())) -} - -panic("NewExampleValueMust received error(s): " + strings.Join(diagsStrings, "\n")) -} - -return object -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectType := NewCustomObjectType(testCase.name, nil) - - got, err := customObjectType.renderValueMust() - - 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) - } - }) - } +return nil, fmt.Errorf("unexpected error converting ObjectValue to ObjectValuable: %v", diags) } -func TestCustomObjectType_renderValueNull(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - expected: []byte(` -func NewExampleValueNull() ExampleValue { -return ExampleValue{ -state: attr.ValueStateNull, -} +return objectValuable, nil }`), }, } @@ -508,9 +247,9 @@ state: attr.ValueStateNull, t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, nil) + customObjectType := NewCustomObjectType(testCase.name) - got, err := customObjectType.renderValueNull() + got, err := customObjectType.renderValueFromTerraform() if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected error: %s", diff) @@ -546,7 +285,7 @@ return ExampleValue{} t.Run(name, func(t *testing.T) { t.Parallel() - customObjectType := NewCustomObjectType(testCase.name, nil) + customObjectType := NewCustomObjectType(testCase.name) got, err := customObjectType.renderValueType() @@ -561,104 +300,18 @@ return ExampleValue{} } } -func TestCustomObjectType_renderValueUnknown(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - expected: []byte(` -func NewExampleValueUnknown() ExampleValue { -return ExampleValue{ -state: attr.ValueStateUnknown, -} -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectType := NewCustomObjectType(testCase.name, nil) - - got, err := customObjectType.renderValueUnknown() - - 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 TestCustomObjectValue_renderAttributeTypes(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - attrTypes map[string]string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - attrTypes: map[string]string{ - "bool_attribute": "basetypes.BoolType{}", - }, - expected: []byte(` -func (v ExampleValue) AttributeTypes(ctx context.Context) map[string]attr.Type { -return map[string]attr.Type{ -"bool_attribute": basetypes.BoolType{}, -} -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectValue := NewCustomObjectValue(testCase.name, nil, testCase.attrTypes, nil) - - got, err := customObjectValue.renderAttributeTypes() - - 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 TestCustomObjectValue_renderEqual(t *testing.T) { t.Parallel() testCases := map[string]struct { name string - attrValues map[string]string + elementType string expected []byte expectedError error }{ "default": { - name: "Example", - attrValues: map[string]string{ - "bool_attribute": "basetypes.BoolValue", - }, + name: "Example", + elementType: "types.BoolType", expected: []byte(` func (v ExampleValue) Equal(o attr.Value) bool { other, ok := o.(ExampleValue) @@ -667,21 +320,7 @@ if !ok { return false } -if v.state != other.state { -return false -} - -if v.state != attr.ValueStateKnown { -return true -} - - -if !v.BoolAttribute.Equal(other.BoolAttribute) { -return false -} - - -return true +return v.ObjectValue.Equal(other.ObjectValue) }`), }, } @@ -692,7 +331,7 @@ return true t.Run(name, func(t *testing.T) { t.Parallel() - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, testCase.attrValues) + customObjectValue := NewCustomObjectValue(testCase.name, testCase.elementType) got, err := customObjectValue.renderEqual() @@ -707,265 +346,22 @@ return true } } -func TestCustomObjectValue_renderIsNull(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - expected: []byte(` -func (v ExampleValue) IsNull() bool { -return v.state == attr.ValueStateNull -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, nil) - - got, err := customObjectValue.renderIsNull() - - 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 TestCustomObjectValue_renderIsUnknown(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - expected: []byte(` -func (v ExampleValue) IsUnknown() bool { -return v.state == attr.ValueStateUnknown -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, nil) - - got, err := customObjectValue.renderIsUnknown() - - 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 TestCustomObjectValue_renderString(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - expected: []byte(` -func (v ExampleValue) String() string { -return "ExampleValue" -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, nil) - - got, err := customObjectValue.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 TestCustomObjectValue_renderToObjectValue(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - attributeTypes map[string]string - attrTypes map[string]string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - attributeTypes: map[string]string{ - "bool_attribute": "Bool", - }, - attrTypes: map[string]string{ - "bool_attribute": "basetypes.BoolType{}", - }, - expected: []byte(` -func (v ExampleValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { -objVal, diags := types.ObjectValue( -map[string]attr.Type{ -"bool_attribute": basetypes.BoolType{}, -}, -map[string]attr.Value{ -"bool_attribute": v.BoolAttribute, -}) - -return objVal, diags -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectValue := NewCustomObjectValue(testCase.name, testCase.attributeTypes, testCase.attrTypes, nil) - - got, err := customObjectValue.renderToObjectValue() - - 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 TestCustomObjectValue_renderToTerraformValue(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - name string - attrTypes map[string]string - expected []byte - expectedError error - }{ - "default": { - name: "Example", - attrTypes: map[string]string{ - "bool_attribute": "basetypes.BoolType{}", - }, - expected: []byte(`func (v ExampleValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { -attrTypes := make(map[string]tftypes.Type, 1) - -var val tftypes.Value -var err error - - -attrTypes["bool_attribute"] = basetypes.BoolType{}.TerraformType(ctx) - -objectType := tftypes.Object{AttributeTypes: attrTypes} - -switch v.state { -case attr.ValueStateKnown: -vals := make(map[string]tftypes.Value, 1) - - -val, err = v.BoolAttribute.ToTerraformValue(ctx) - -if err != nil { -return tftypes.NewValue(objectType, tftypes.UnknownValue), err -} - -vals["bool_attribute"] = val - - - -if err := tftypes.ValidateValue(objectType, vals); err != nil { -return tftypes.NewValue(objectType, tftypes.UnknownValue), err -} - -return tftypes.NewValue(objectType, vals), nil -case attr.ValueStateNull: -return tftypes.NewValue(objectType, nil), nil -case attr.ValueStateUnknown: -return tftypes.NewValue(objectType, tftypes.UnknownValue), nil -default: -panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) -} -}`), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - customObjectValue := NewCustomObjectValue(testCase.name, nil, testCase.attrTypes, nil) - - got, err := customObjectValue.renderToTerraformValue() - - 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 TestCustomObjectValue_renderType(t *testing.T) { t.Parallel() testCases := map[string]struct { name string + elementType string expected []byte expectedError error }{ "default": { - name: "Example", + name: "Example", + elementType: "types.BoolType", expected: []byte(` func (v ExampleValue) Type(ctx context.Context) attr.Type { return ExampleType{ -basetypes.ObjectType{ +ObjectType: basetypes.ObjectType{ AttrTypes: v.AttributeTypes(ctx), }, } @@ -979,7 +375,7 @@ AttrTypes: v.AttributeTypes(ctx), t.Run(name, func(t *testing.T) { t.Parallel() - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, nil) + customObjectValue := NewCustomObjectValue(testCase.name, testCase.elementType) got, err := customObjectValue.renderType() @@ -999,12 +395,14 @@ func TestCustomObjectValue_renderValuable(t *testing.T) { testCases := map[string]struct { name string + elementType string expected []byte expectedError error }{ "default": { - name: "Example", - expected: []byte(`var _ basetypes.ObjectValuable = ExampleValue{}`), + name: "Example", + elementType: "types.BoolType", + expected: []byte(`var _ basetypes.ObjectValuable = ExampleValue{}`), }, } @@ -1014,7 +412,7 @@ func TestCustomObjectValue_renderValuable(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, nil) + customObjectValue := NewCustomObjectValue(testCase.name, testCase.elementType) got, err := customObjectValue.renderValuable() @@ -1034,13 +432,15 @@ func TestCustomObjectValue_renderValue(t *testing.T) { testCases := map[string]struct { name string + elementType string expected []byte expectedError error }{ "default": { - name: "Example", + name: "Example", + elementType: "types.BoolType", expected: []byte(`type ExampleValue struct { -state attr.ValueState +basetypes.ObjectValue }`), }, } @@ -1051,7 +451,7 @@ state attr.ValueState t.Run(name, func(t *testing.T) { t.Parallel() - customObjectValue := NewCustomObjectValue(testCase.name, nil, nil, nil) + customObjectValue := NewCustomObjectValue(testCase.name, testCase.elementType) got, err := customObjectValue.renderValue() @@ -1065,11 +465,3 @@ state attr.ValueState }) } } - -var equateErrorMessage = cmp.Comparer(func(x, y error) bool { - if x == nil || y == nil { - return x == nil && y == nil - } - - return x.Error() == y.Error() -}) diff --git a/internal/schema/custom_set.go b/internal/schema/custom_set.go new file mode 100644 index 00000000..0985b726 --- /dev/null +++ b/internal/schema/custom_set.go @@ -0,0 +1,349 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type CustomSetType struct { + Name FrameworkIdentifier + templates map[string]string +} + +func NewCustomSetType(name string) CustomSetType { + t := map[string]string{ + "equal": SetTypeEqualTemplate, + "string": SetTypeStringTemplate, + "type": SetTypeTypeTemplate, + "typable": SetTypeTypableTemplate, + "valueFromSet": SetTypeValueFromSetTemplate, + "valueFromTerraform": SetTypeValueFromTerraformTemplate, + "valueType": SetTypeValueTypeTemplate, + } + + return CustomSetType{ + Name: FrameworkIdentifier(name), + templates: t, + } +} + +func (c CustomSetType) Render() ([]byte, error) { + var buf bytes.Buffer + + renderFuncs := []func() ([]byte, error){ + c.renderTypable, + c.renderType, + c.renderEqual, + c.renderString, + c.renderValueFromSet, + 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 CustomSetType) 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 CustomSetType) 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 CustomSetType) 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 CustomSetType) 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 CustomSetType) renderValueFromSet() ([]byte, error) { + var buf bytes.Buffer + + t, err := template.New("").Parse(c.templates["valueFromSet"]) + + 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 CustomSetType) 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 CustomSetType) 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 CustomSetValue struct { + Name FrameworkIdentifier + ElementType string + templates map[string]string +} + +func NewCustomSetValue(name, elemType string) CustomSetValue { + t := map[string]string{ + "equal": SetValueEqualTemplate, + "type": SetValueTypeTemplate, + "valuable": SetValueValuableTemplate, + "value": SetValueValueTemplate, + } + + return CustomSetValue{ + Name: FrameworkIdentifier(name), + ElementType: elemType, + templates: t, + } +} + +func (c CustomSetValue) 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 CustomSetValue) 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 CustomSetValue) 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 + ElementType string + }{ + Name: c.Name.ToPascalCase(), + ElementType: c.ElementType, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (c CustomSetValue) 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 CustomSetValue) 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_set_test.go b/internal/schema/custom_set_test.go new file mode 100644 index 00000000..28d93897 --- /dev/null +++ b/internal/schema/custom_set_test.go @@ -0,0 +1,467 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCustomSetType_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.SetType.Equal(other.SetType) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.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 TestCustomSetType_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() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.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 TestCustomSetType_renderTypable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + expected: []byte(`var _ basetypes.SetTypable = ExampleType{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.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 TestCustomSetType_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.SetType +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.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 TestCustomSetType_renderValueFromSet(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.SetValue", + }, + expected: []byte(` +func (t ExampleType) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { +return ExampleValue{ +SetValue: in, +}, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.renderValueFromSet() + + 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 TestCustomSetType_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.SetType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +listValue, ok := attrValue.(basetypes.SetValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +listValuable, diags := t.ValueFromSet(ctx, listValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) +} + +return listValuable, nil +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.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 TestCustomSetType_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() + + customSetType := NewCustomSetType(testCase.name) + + got, err := customSetType.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 TestCustomSetValue_renderEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(` +func (v ExampleValue) Equal(o attr.Value) bool { +other, ok := o.(ExampleValue) + +if !ok { +return false +} + +return v.SetValue.Equal(other.SetValue) +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetValue := NewCustomSetValue(testCase.name, testCase.elementType) + + got, err := customSetValue.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 TestCustomSetValue_renderType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(` +func (v ExampleValue) Type(ctx context.Context) attr.Type { +return ExampleType{ +SetType: basetypes.SetType{ +ElemType: types.BoolType, +}, +} +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetValue := NewCustomSetValue(testCase.name, testCase.elementType) + + got, err := customSetValue.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 TestCustomSetValue_renderValuable(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(`var _ basetypes.SetValuable = ExampleValue{}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetValue := NewCustomSetValue(testCase.name, testCase.elementType) + + got, err := customSetValue.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 TestCustomSetValue_renderValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + elementType string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + elementType: "types.BoolType", + expected: []byte(`type ExampleValue struct { +basetypes.SetValue +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + customSetValue := NewCustomSetValue(testCase.name, testCase.elementType) + + got, err := customSetValue.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/elements.go b/internal/schema/elements.go index 7d978f73..51d58d87 100644 --- a/internal/schema/elements.go +++ b/internal/schema/elements.go @@ -62,3 +62,77 @@ func GetElementType(e specschema.ElementType) string { return "" } + +// GetElementValueType generates the strings for use within templates for specifying the value types +// to use with collection (i.e., list, map and set) element types. +func GetElementValueType(e specschema.ElementType) string { + switch { + case e.Bool != nil: + if e.Bool.CustomType != nil { + return e.Bool.CustomType.ValueType + } + return "types.Bool" + case e.Float64 != nil: + if e.Float64.CustomType != nil { + return e.Float64.CustomType.ValueType + } + return "types.Float64" + case e.Int64 != nil: + if e.Int64.CustomType != nil { + return e.Int64.CustomType.ValueType + } + return "types.Int64" + case e.List != nil: + if e.List.CustomType != nil { + return e.List.CustomType.ValueType + } + return "types.List" + case e.Map != nil: + if e.Map.CustomType != nil { + return e.Map.CustomType.ValueType + } + return "types.Map" + case e.Number != nil: + if e.Number.CustomType != nil { + return e.Number.CustomType.ValueType + } + return "types.Number" + case e.Object != nil: + if e.Object.CustomType != nil { + return e.Object.CustomType.ValueType + } + return "types.Object" + case e.Set != nil: + if e.Set.CustomType != nil { + return e.Set.CustomType.ValueType + } + return "types.Set" + case e.String != nil: + if e.String.CustomType != nil { + return e.String.CustomType.ValueType + } + return "types.String" + } + + return "" +} + +// GetElementFromFunc returns a string representation of the function that is used +// for converting from an API Go type to a framework type. +// TODO: Handle custom type, and types other than primitives. +func GetElementFromFunc(e specschema.ElementType) string { + switch { + case e.Bool != nil: + return "types.BoolPointerValue" + case e.Float64 != nil: + return "types.Float64PointerValue" + case e.Int64 != nil: + return "types.Int64PointerValue" + case e.Number != nil: + return "types.NumberValue" + case e.String != nil: + return "types.StringPointerValue" + } + + return "" +} diff --git a/internal/schema/embed.go b/internal/schema/embed.go index a96bcf6c..7cb48717 100644 --- a/internal/schema/embed.go +++ b/internal/schema/embed.go @@ -142,6 +142,96 @@ var Int64ValueValueTemplate string //go:embed templates/int64_value_valuable.gotmpl var Int64ValueValuableTemplate string +// List From/To + +//go:embed templates/list_from.gotmpl +var ListFromTemplate string + +//go:embed templates/list_to.gotmpl +var ListToTemplate string + +// List Type + +//go:embed templates/list_type_equal.gotmpl +var ListTypeEqualTemplate string + +//go:embed templates/list_type_string.gotmpl +var ListTypeStringTemplate string + +//go:embed templates/list_type_type.gotmpl +var ListTypeTypeTemplate string + +//go:embed templates/list_type_typable.gotmpl +var ListTypeTypableTemplate string + +//go:embed templates/list_type_value_from_list.gotmpl +var ListTypeValueFromListTemplate string + +//go:embed templates/list_type_value_from_terraform.gotmpl +var ListTypeValueFromTerraformTemplate string + +//go:embed templates/list_type_value_type.gotmpl +var ListTypeValueTypeTemplate string + +// List Value + +//go:embed templates/list_value_equal.gotmpl +var ListValueEqualTemplate string + +//go:embed templates/list_value_type.gotmpl +var ListValueTypeTemplate string + +//go:embed templates/list_value_value.gotmpl +var ListValueValueTemplate string + +//go:embed templates/list_value_valuable.gotmpl +var ListValueValuableTemplate string + +// Map From/To + +//go:embed templates/map_from.gotmpl +var MapFromTemplate string + +//go:embed templates/map_to.gotmpl +var MapToTemplate string + +// Map Type + +//go:embed templates/map_type_equal.gotmpl +var MapTypeEqualTemplate string + +//go:embed templates/map_type_string.gotmpl +var MapTypeStringTemplate string + +//go:embed templates/map_type_type.gotmpl +var MapTypeTypeTemplate string + +//go:embed templates/map_type_typable.gotmpl +var MapTypeTypableTemplate string + +//go:embed templates/map_type_value_from_map.gotmpl +var MapTypeValueFromMapTemplate string + +//go:embed templates/map_type_value_from_terraform.gotmpl +var MapTypeValueFromTerraformTemplate string + +//go:embed templates/map_type_value_type.gotmpl +var MapTypeValueTypeTemplate string + +// Map Value + +//go:embed templates/map_value_equal.gotmpl +var MapValueEqualTemplate string + +//go:embed templates/map_value_type.gotmpl +var MapValueTypeTemplate string + +//go:embed templates/map_value_value.gotmpl +var MapValueValueTemplate string + +//go:embed templates/map_value_valuable.gotmpl +var MapValueValuableTemplate string + // Number From/To //go:embed templates/number_from.gotmpl @@ -187,6 +277,84 @@ var NumberValueValueTemplate string //go:embed templates/number_value_valuable.gotmpl var NumberValueValuableTemplate string +// NestedObject From/To + +//go:embed templates/nested_object_from.gotmpl +var NestedObjectFromTemplate string + +//go:embed templates/nested_object_to.gotmpl +var NestedObjectToTemplate string + +// NestedObject Type + +//go:embed templates/nested_object_type_equal.gotmpl +var NestedObjectTypeEqualTemplate string + +//go:embed templates/nested_object_type_string.gotmpl +var NestedObjectTypeStringTemplate string + +//go:embed templates/nested_object_type_typable.gotmpl +var NestedObjectTypeTypableTemplate string + +//go:embed templates/nested_object_type_type.gotmpl +var NestedObjectTypeTypeTemplate string + +//go:embed templates/nested_object_type_value.gotmpl +var NestedObjectTypeValueTemplate string + +//go:embed templates/nested_object_type_value_from_object.gotmpl +var NestedObjectTypeValueFromObjectTemplate string + +//go:embed templates/nested_object_type_value_from_terraform.gotmpl +var NestedObjectTypeValueFromTerraformTemplate string + +//go:embed templates/nested_object_type_value_must.gotmpl +var NestedObjectTypeValueMustTemplate string + +//go:embed templates/nested_object_type_value_null.gotmpl +var NestedObjectTypeValueNullTemplate string + +//go:embed templates/nested_object_type_value_type.gotmpl +var NestedObjectTypeValueTypeTemplate string + +//go:embed templates/nested_object_type_value_unknown.gotmpl +var NestedObjectTypeValueUnknownTemplate string + +// NestedObject Value + +//go:embed templates/nested_object_value_attribute_types.gotmpl +var NestedObjectValueAttributeTypesTemplate string + +//go:embed templates/nested_object_value_equal.gotmpl +var NestedObjectValueEqualTemplate string + +//go:embed templates/nested_object_value_is_null.gotmpl +var NestedObjectValueIsNullTemplate string + +//go:embed templates/nested_object_value_is_unknown.gotmpl +var NestedObjectValueIsUnknownTemplate string + +//go:embed templates/nested_object_value_string.gotmpl +var NestedObjectValueStringTemplate string + +//go:embed templates/nested_object_value_to_object_value.gotmpl +var NestedObjectValueToObjectValueTemplate string + +//go:embed templates/nested_object_value_to_terraform_value.gotmpl +var NestedObjectValueToTerraformValueTemplate string + +//go:embed templates/nested_object_value_type.gotmpl +var NestedObjectValueTypeTemplate string + +//go:embed templates/nested_object_value_valuable.gotmpl +var NestedObjectValueValuableTemplate string + +//go:embed templates/nested_object_value_value.gotmpl +var NestedObjectValueValueTemplate string + +//go:embed templates/schema.gotmpl +var SchemaGoTemplate string + // Object From/To //go:embed templates/object_from.gotmpl @@ -203,14 +371,11 @@ var ObjectTypeEqualTemplate string //go:embed templates/object_type_string.gotmpl var ObjectTypeStringTemplate string -//go:embed templates/object_type_typable.gotmpl -var ObjectTypeTypableTemplate string - //go:embed templates/object_type_type.gotmpl var ObjectTypeTypeTemplate string -//go:embed templates/object_type_value.gotmpl -var ObjectTypeValueTemplate string +//go:embed templates/object_type_typable.gotmpl +var ObjectTypeTypableTemplate string //go:embed templates/object_type_value_from_object.gotmpl var ObjectTypeValueFromObjectTemplate string @@ -218,18 +383,9 @@ var ObjectTypeValueFromObjectTemplate string //go:embed templates/object_type_value_from_terraform.gotmpl var ObjectTypeValueFromTerraformTemplate string -//go:embed templates/object_type_value_must.gotmpl -var ObjectTypeValueMustTemplate string - -//go:embed templates/object_type_value_null.gotmpl -var ObjectTypeValueNullTemplate string - //go:embed templates/object_type_value_type.gotmpl var ObjectTypeValueTypeTemplate string -//go:embed templates/object_type_value_unknown.gotmpl -var ObjectTypeValueUnknownTemplate string - // Object Value //go:embed templates/object_value_attribute_types.gotmpl @@ -238,32 +394,59 @@ var ObjectValueAttributeTypesTemplate string //go:embed templates/object_value_equal.gotmpl var ObjectValueEqualTemplate string -//go:embed templates/object_value_is_null.gotmpl -var ObjectValueIsNullTemplate string +//go:embed templates/object_value_type.gotmpl +var ObjectValueTypeTemplate string + +//go:embed templates/object_value_value.gotmpl +var ObjectValueValueTemplate string -//go:embed templates/object_value_is_unknown.gotmpl -var ObjectValueIsUnknownTemplate string +//go:embed templates/object_value_valuable.gotmpl +var ObjectValueValuableTemplate string -//go:embed templates/object_value_string.gotmpl -var ObjectValueStringTemplate string +// Set From/To -//go:embed templates/object_value_to_object_value.gotmpl -var ObjectValueToObjectValueTemplate string +//go:embed templates/set_from.gotmpl +var SetFromTemplate string -//go:embed templates/object_value_to_terraform_value.gotmpl -var ObjectValueToTerraformValueTemplate string +//go:embed templates/set_to.gotmpl +var SetToTemplate string -//go:embed templates/object_value_type.gotmpl -var ObjectValueTypeTemplate string +// Set Type -//go:embed templates/object_value_valuable.gotmpl -var ObjectValueValuableTemplate string +//go:embed templates/set_type_equal.gotmpl +var SetTypeEqualTemplate string -//go:embed templates/object_value_value.gotmpl -var ObjectValueValueTemplate string +//go:embed templates/set_type_string.gotmpl +var SetTypeStringTemplate string -//go:embed templates/schema.gotmpl -var SchemaGoTemplate string +//go:embed templates/set_type_type.gotmpl +var SetTypeTypeTemplate string + +//go:embed templates/set_type_typable.gotmpl +var SetTypeTypableTemplate string + +//go:embed templates/set_type_value_from_set.gotmpl +var SetTypeValueFromSetTemplate string + +//go:embed templates/set_type_value_from_terraform.gotmpl +var SetTypeValueFromTerraformTemplate string + +//go:embed templates/set_type_value_type.gotmpl +var SetTypeValueTypeTemplate string + +// Set Value + +//go:embed templates/set_value_equal.gotmpl +var SetValueEqualTemplate string + +//go:embed templates/set_value_type.gotmpl +var SetValueTypeTemplate string + +//go:embed templates/set_value_value.gotmpl +var SetValueValueTemplate string + +//go:embed templates/set_value_valuable.gotmpl +var SetValueValuableTemplate string // String From/To diff --git a/internal/schema/templates/list_from.gotmpl b/internal/schema/templates/list_from.gotmpl new file mode 100644 index 00000000..4c3bf517 --- /dev/null +++ b/internal/schema/templates/list_from.gotmpl @@ -0,0 +1,30 @@ + +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.ListNull({{.ElementTypeType}}), +}, diags +} + +var elems []{{.ElementTypeValue}} + +for _, e := range *apiObject { +elems = append(elems, {{.ElementFrom}}(e)) +} + +l, d := basetypes.NewListValueFrom(ctx, {{.ElementTypeType}}, elems) + +diags.Append(d...) + +if diags.HasError() { +return {{.Name}}Value{ +types.ListUnknown({{.ElementTypeType}}), +}, diags +} + +return {{.Name}}Value{ +l, +}, diags +} diff --git a/internal/schema/templates/list_to.gotmpl b/internal/schema/templates/list_to.gotmpl new file mode 100644 index 00000000..4be7e418 --- /dev/null +++ b/internal/schema/templates/list_to.gotmpl @@ -0,0 +1,28 @@ +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 +} + +var {{.AssocExtType.ToCamelCase}} {{.AssocExtType.TypeReference}} + +d := v.ElementsAs(ctx, &{{.AssocExtType.ToCamelCase}}, false) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &{{.AssocExtType.ToCamelCase}}, diags +} \ No newline at end of file diff --git a/internal/schema/templates/list_type_equal.gotmpl b/internal/schema/templates/list_type_equal.gotmpl new file mode 100644 index 00000000..92240326 --- /dev/null +++ b/internal/schema/templates/list_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.ListType.Equal(other.ListType) +} \ No newline at end of file diff --git a/internal/schema/templates/list_type_string.gotmpl b/internal/schema/templates/list_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/list_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/list_type_typable.gotmpl b/internal/schema/templates/list_type_typable.gotmpl new file mode 100644 index 00000000..5d663dfb --- /dev/null +++ b/internal/schema/templates/list_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.ListTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/list_type_type.gotmpl b/internal/schema/templates/list_type_type.gotmpl new file mode 100644 index 00000000..c7639f50 --- /dev/null +++ b/internal/schema/templates/list_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.ListType +} \ No newline at end of file diff --git a/internal/schema/templates/list_type_value_from_list.gotmpl b/internal/schema/templates/list_type_value_from_list.gotmpl new file mode 100644 index 00000000..dabfb55f --- /dev/null +++ b/internal/schema/templates/list_type_value_from_list.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromList(ctx context.Context, in basetypes.ListValue) (basetypes.ListValuable, diag.Diagnostics) { +return {{.Name}}Value{ +ListValue: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/list_type_value_from_terraform.gotmpl b/internal/schema/templates/list_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..9fb8efd3 --- /dev/null +++ b/internal/schema/templates/list_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.ListType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +listValue, ok := attrValue.(basetypes.ListValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +listValuable, diags := t.ValueFromList(ctx, listValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting ListValue to ListValuable: %v", diags) +} + +return listValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/list_type_value_type.gotmpl b/internal/schema/templates/list_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/list_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/list_value_equal.gotmpl b/internal/schema/templates/list_value_equal.gotmpl new file mode 100644 index 00000000..b5047aa3 --- /dev/null +++ b/internal/schema/templates/list_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.ListValue.Equal(other.ListValue) +} \ No newline at end of file diff --git a/internal/schema/templates/list_value_type.gotmpl b/internal/schema/templates/list_value_type.gotmpl new file mode 100644 index 00000000..135463af --- /dev/null +++ b/internal/schema/templates/list_value_type.gotmpl @@ -0,0 +1,8 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +ListType: basetypes.ListType{ +ElemType: {{.ElementType}}, +}, +} +} \ No newline at end of file diff --git a/internal/schema/templates/list_value_valuable.gotmpl b/internal/schema/templates/list_value_valuable.gotmpl new file mode 100644 index 00000000..289dd3e3 --- /dev/null +++ b/internal/schema/templates/list_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.ListValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/list_value_value.gotmpl b/internal/schema/templates/list_value_value.gotmpl new file mode 100644 index 00000000..a175d1ad --- /dev/null +++ b/internal/schema/templates/list_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.ListValue +} \ No newline at end of file diff --git a/internal/schema/templates/map_from.gotmpl b/internal/schema/templates/map_from.gotmpl new file mode 100644 index 00000000..9dbf745a --- /dev/null +++ b/internal/schema/templates/map_from.gotmpl @@ -0,0 +1,30 @@ + +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.MapNull({{.ElementTypeType}}), +}, diags +} + +elems := make(map[string]{{.ElementTypeValue}}) + +for k, e := range *apiObject { +elems[k] = {{.ElementFrom}}(e) +} + +l, d := basetypes.NewMapValueFrom(ctx, {{.ElementTypeType}}, elems) + +diags.Append(d...) + +if diags.HasError() { +return {{.Name}}Value{ +types.MapUnknown({{.ElementTypeType}}), +}, diags +} + +return {{.Name}}Value{ +l, +}, diags +} diff --git a/internal/schema/templates/map_to.gotmpl b/internal/schema/templates/map_to.gotmpl new file mode 100644 index 00000000..4be7e418 --- /dev/null +++ b/internal/schema/templates/map_to.gotmpl @@ -0,0 +1,28 @@ +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 +} + +var {{.AssocExtType.ToCamelCase}} {{.AssocExtType.TypeReference}} + +d := v.ElementsAs(ctx, &{{.AssocExtType.ToCamelCase}}, false) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &{{.AssocExtType.ToCamelCase}}, diags +} \ No newline at end of file diff --git a/internal/schema/templates/map_type_equal.gotmpl b/internal/schema/templates/map_type_equal.gotmpl new file mode 100644 index 00000000..4fb30481 --- /dev/null +++ b/internal/schema/templates/map_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.MapType.Equal(other.MapType) +} \ No newline at end of file diff --git a/internal/schema/templates/map_type_string.gotmpl b/internal/schema/templates/map_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/map_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/map_type_typable.gotmpl b/internal/schema/templates/map_type_typable.gotmpl new file mode 100644 index 00000000..57299978 --- /dev/null +++ b/internal/schema/templates/map_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.MapTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/map_type_type.gotmpl b/internal/schema/templates/map_type_type.gotmpl new file mode 100644 index 00000000..4e80e289 --- /dev/null +++ b/internal/schema/templates/map_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.MapType +} \ No newline at end of file diff --git a/internal/schema/templates/map_type_value_from_map.gotmpl b/internal/schema/templates/map_type_value_from_map.gotmpl new file mode 100644 index 00000000..426126ee --- /dev/null +++ b/internal/schema/templates/map_type_value_from_map.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromMap(ctx context.Context, in basetypes.MapValue) (basetypes.MapValuable, diag.Diagnostics) { +return {{.Name}}Value{ +MapValue: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/map_type_value_from_terraform.gotmpl b/internal/schema/templates/map_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..0da65a1a --- /dev/null +++ b/internal/schema/templates/map_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.MapType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +mapValue, ok := attrValue.(basetypes.MapValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +mapValuable, diags := t.ValueFromMap(ctx, mapValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting MapValue to MapValuable: %v", diags) +} + +return mapValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/map_type_value_type.gotmpl b/internal/schema/templates/map_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/map_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/map_value_equal.gotmpl b/internal/schema/templates/map_value_equal.gotmpl new file mode 100644 index 00000000..7662fabb --- /dev/null +++ b/internal/schema/templates/map_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.MapValue.Equal(other.MapValue) +} \ No newline at end of file diff --git a/internal/schema/templates/map_value_type.gotmpl b/internal/schema/templates/map_value_type.gotmpl new file mode 100644 index 00000000..15438603 --- /dev/null +++ b/internal/schema/templates/map_value_type.gotmpl @@ -0,0 +1,8 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +MapType: basetypes.MapType{ +ElemType: {{.ElementType}}, +}, +} +} \ No newline at end of file diff --git a/internal/schema/templates/map_value_valuable.gotmpl b/internal/schema/templates/map_value_valuable.gotmpl new file mode 100644 index 00000000..ea7a924b --- /dev/null +++ b/internal/schema/templates/map_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.MapValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/map_value_value.gotmpl b/internal/schema/templates/map_value_value.gotmpl new file mode 100644 index 00000000..dc684fc8 --- /dev/null +++ b/internal/schema/templates/map_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.MapValue +} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_from.gotmpl b/internal/schema/templates/nested_object_from.gotmpl new file mode 100644 index 00000000..027eff49 --- /dev/null +++ b/internal/schema/templates/nested_object_from.gotmpl @@ -0,0 +1,31 @@ + +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 New{{.Name}}ValueNull(), diags +} +{{- range $key, $value := .FromFuncs}} +{{- if $value.AssocExtType}} + +{{$key.ToCamelCase}}Val, d := {{$key.ToPascalCase}}Value{}.From{{$value.AssocExtType.ToPascalCase}}(ctx, apiObject.{{$key.ToPascalCase}}) + +diags.Append(d...) + +if diags.HasError() { +return New{{$.Name}}ValueUnknown(), diags +} +{{- end}} +{{- end}} + +return {{.Name}}Value{ +{{- range $key, $value := .FromFuncs}} +{{- if $value.AssocExtType}} +{{$key.ToPascalCase}}: {{$key.ToCamelCase}}Val, +{{- else if $value.Default}} +{{$key.ToPascalCase}}: types.{{$value.Default}}(apiObject.{{$key.ToPascalCase}}), +{{- end}} +{{- end}} +state: attr.ValueStateKnown, +}, diags +} diff --git a/internal/schema/templates/nested_object_to.gotmpl b/internal/schema/templates/nested_object_to.gotmpl new file mode 100644 index 00000000..737e8ec0 --- /dev/null +++ b/internal/schema/templates/nested_object_to.gotmpl @@ -0,0 +1,38 @@ +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 +} +{{- range $key, $value := .ToFuncs}} +{{- if $value.AssocExtType}} + +{{$value.AssocExtType.ToCamelCase}}, d := v.{{$key.ToPascalCase}}.To{{$value.AssocExtType.ToPascalCase}}(ctx) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} +{{- end}} +{{- end}} + +return &{{.AssocExtType.TypeReference}}{ +{{- range $key, $value := .ToFuncs}} +{{- if $value.AssocExtType}} +{{$key.ToPascalCase}}: {{$value.AssocExtType.ToCamelCase}}, +{{- else if $value.Default}} +{{$key.ToPascalCase}}: v.{{$key.ToPascalCase}}.{{$value.Default}}(), +{{- end}} +{{- end}} +}, diags +} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_type_equal.gotmpl b/internal/schema/templates/nested_object_type_equal.gotmpl new file mode 100644 index 00000000..11eb5c47 --- /dev/null +++ b/internal/schema/templates/nested_object_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.ObjectType.Equal(other.ObjectType) +} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_type_string.gotmpl b/internal/schema/templates/nested_object_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/nested_object_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/nested_object_type_typable.gotmpl b/internal/schema/templates/nested_object_type_typable.gotmpl new file mode 100644 index 00000000..5195a01a --- /dev/null +++ b/internal/schema/templates/nested_object_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.ObjectTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_type_type.gotmpl b/internal/schema/templates/nested_object_type_type.gotmpl new file mode 100644 index 00000000..fdde0ae9 --- /dev/null +++ b/internal/schema/templates/nested_object_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.ObjectType +} \ No newline at end of file diff --git a/internal/schema/templates/object_type_value.gotmpl b/internal/schema/templates/nested_object_type_value.gotmpl similarity index 100% rename from internal/schema/templates/object_type_value.gotmpl rename to internal/schema/templates/nested_object_type_value.gotmpl diff --git a/internal/schema/templates/nested_object_type_value_from_object.gotmpl b/internal/schema/templates/nested_object_type_value_from_object.gotmpl new file mode 100644 index 00000000..e727b396 --- /dev/null +++ b/internal/schema/templates/nested_object_type_value_from_object.gotmpl @@ -0,0 +1,37 @@ + +func (t {{.Name}}Type) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { +var diags diag.Diagnostics + +attributes := in.Attributes() + +{{range $key, $value := .AttrValues }} +{{$key.ToCamelCase}}Attribute, ok := attributes["{{$key}}"] + +if !ok { +diags.AddError( +"Attribute Missing", +`{{$key}} is missing from object`) + +return nil, diags +} + +{{$key.ToCamelCase}}Val, ok := {{$key.ToCamelCase}}Attribute.({{$value}}) + +if !ok { +diags.AddError( +"Attribute Wrong Type", +fmt.Sprintf(`{{$key}} expected to be {{$value}}, was: %T`, {{$key.ToCamelCase}}Attribute)) +} +{{end}} + +if diags.HasError() { +return nil, diags +} + +return {{.Name}}Value{ +{{- range $key, $value := .AttrValues }} +{{$key.ToPascalCase}}: {{$key.ToCamelCase}}Val, +{{- end}} +state: attr.ValueStateKnown, +}, diags +} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_type_value_from_terraform.gotmpl b/internal/schema/templates/nested_object_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..a94d4a10 --- /dev/null +++ b/internal/schema/templates/nested_object_type_value_from_terraform.gotmpl @@ -0,0 +1,40 @@ + +func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { +if in.Type() == nil { +return New{{.Name}}ValueNull(), nil +} + +if !in.Type().Equal(t.TerraformType(ctx)) { +return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) +} + +if !in.IsKnown() { +return New{{.Name}}ValueUnknown(), nil +} + +if in.IsNull() { +return New{{.Name}}ValueNull(), nil +} + +attributes := map[string]attr.Value{} + +val := map[string]tftypes.Value{} + +err := in.As(&val) + +if err != nil { +return nil, err +} + +for k, v := range val { +a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + +if err != nil { +return nil, err +} + +attributes[k] = a +} + +return New{{.Name}}ValueMust(t.AttrTypes, attributes), nil +} \ No newline at end of file diff --git a/internal/schema/templates/object_type_value_must.gotmpl b/internal/schema/templates/nested_object_type_value_must.gotmpl similarity index 100% rename from internal/schema/templates/object_type_value_must.gotmpl rename to internal/schema/templates/nested_object_type_value_must.gotmpl diff --git a/internal/schema/templates/object_type_value_null.gotmpl b/internal/schema/templates/nested_object_type_value_null.gotmpl similarity index 100% rename from internal/schema/templates/object_type_value_null.gotmpl rename to internal/schema/templates/nested_object_type_value_null.gotmpl diff --git a/internal/schema/templates/nested_object_type_value_type.gotmpl b/internal/schema/templates/nested_object_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/nested_object_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/object_type_value_unknown.gotmpl b/internal/schema/templates/nested_object_type_value_unknown.gotmpl similarity index 100% rename from internal/schema/templates/object_type_value_unknown.gotmpl rename to internal/schema/templates/nested_object_type_value_unknown.gotmpl diff --git a/internal/schema/templates/nested_object_value_attribute_types.gotmpl b/internal/schema/templates/nested_object_value_attribute_types.gotmpl new file mode 100644 index 00000000..8d7a5f7b --- /dev/null +++ b/internal/schema/templates/nested_object_value_attribute_types.gotmpl @@ -0,0 +1,8 @@ + +func (v {{.Name}}Value) AttributeTypes(ctx context.Context) map[string]attr.Type { +return map[string]attr.Type{ +{{- range $key, $value := .AttrTypes }} +"{{$key}}": {{$value}}, +{{- end}} +} +} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_value_equal.gotmpl b/internal/schema/templates/nested_object_value_equal.gotmpl new file mode 100644 index 00000000..c442ed2c --- /dev/null +++ b/internal/schema/templates/nested_object_value_equal.gotmpl @@ -0,0 +1,30 @@ + +func (v {{.Name}}Value) Equal(o attr.Value) bool { +other, ok := o.({{.Name}}Value) + +if !ok { +return false +} + +if v.state != other.state { +return false +} + +if v.state != attr.ValueStateKnown { +return true +} + +{{range $key, $value := .AttrValues }} +{{- if eq $value "baseTypes.BoolValue" "baseTypes.Float64Value" "baseTypes.Int64Value" "baseTypes.NumberValue" "baseTypes.StringValue"}} +if v.{{$key.ToPascalCase}} != other.{{$key.ToPascalCase}} { +return false +} +{{- else}} +if !v.{{$key.ToPascalCase}}.Equal(other.{{$key.ToPascalCase}}) { +return false +} +{{- end}} +{{end}} + +return true +} \ No newline at end of file diff --git a/internal/schema/templates/object_value_is_null.gotmpl b/internal/schema/templates/nested_object_value_is_null.gotmpl similarity index 100% rename from internal/schema/templates/object_value_is_null.gotmpl rename to internal/schema/templates/nested_object_value_is_null.gotmpl diff --git a/internal/schema/templates/object_value_is_unknown.gotmpl b/internal/schema/templates/nested_object_value_is_unknown.gotmpl similarity index 100% rename from internal/schema/templates/object_value_is_unknown.gotmpl rename to internal/schema/templates/nested_object_value_is_unknown.gotmpl diff --git a/internal/schema/templates/object_value_string.gotmpl b/internal/schema/templates/nested_object_value_string.gotmpl similarity index 100% rename from internal/schema/templates/object_value_string.gotmpl rename to internal/schema/templates/nested_object_value_string.gotmpl diff --git a/internal/schema/templates/object_value_to_object_value.gotmpl b/internal/schema/templates/nested_object_value_to_object_value.gotmpl similarity index 100% rename from internal/schema/templates/object_value_to_object_value.gotmpl rename to internal/schema/templates/nested_object_value_to_object_value.gotmpl diff --git a/internal/schema/templates/object_value_to_terraform_value.gotmpl b/internal/schema/templates/nested_object_value_to_terraform_value.gotmpl similarity index 100% rename from internal/schema/templates/object_value_to_terraform_value.gotmpl rename to internal/schema/templates/nested_object_value_to_terraform_value.gotmpl diff --git a/internal/schema/templates/nested_object_value_type.gotmpl b/internal/schema/templates/nested_object_value_type.gotmpl new file mode 100644 index 00000000..8cbe0dd6 --- /dev/null +++ b/internal/schema/templates/nested_object_value_type.gotmpl @@ -0,0 +1,8 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +basetypes.ObjectType{ +AttrTypes: v.AttributeTypes(ctx), +}, +} +} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_value_valuable.gotmpl b/internal/schema/templates/nested_object_value_valuable.gotmpl new file mode 100644 index 00000000..0ddaed93 --- /dev/null +++ b/internal/schema/templates/nested_object_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.ObjectValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/nested_object_value_value.gotmpl b/internal/schema/templates/nested_object_value_value.gotmpl new file mode 100644 index 00000000..12b74387 --- /dev/null +++ b/internal/schema/templates/nested_object_value_value.gotmpl @@ -0,0 +1,6 @@ +type {{.Name}}Value struct { +{{- range $key, $value := .AttrValues }} +{{$key.ToPascalCase}} {{$value}} `tfsdk:"{{$key}}"` +{{- end}} +state attr.ValueState +} \ No newline at end of file diff --git a/internal/schema/templates/object_from.gotmpl b/internal/schema/templates/object_from.gotmpl index 39ff2b92..f37ceab6 100644 --- a/internal/schema/templates/object_from.gotmpl +++ b/internal/schema/templates/object_from.gotmpl @@ -3,29 +3,26 @@ func (v {{.Name}}Value) From{{.AssocExtType.ToPascalCase}}(ctx context.Context, var diags diag.Diagnostics if apiObject == nil { -return New{{.Name}}ValueNull(), diags +return {{.Name}}Value{ +types.ObjectNull(v.AttributeTypes(ctx)), +}, diags } -{{- range $key, $value := .FromFuncs}} -{{- if $value.AssocExtType}} -{{$key.ToCamelCase}}Val, d := {{$key.ToPascalCase}}Value{}.From{{$value.AssocExtType.ToPascalCase}}(ctx, apiObject.{{$key.ToPascalCase}}) +o, d := basetypes.NewObjectValue(v.AttributeTypes(ctx), map[string]attr.Value{ +{{- range $key, $value := .AttrTypesFromFuncs}} +"{{$key}}": {{$value}}(apiObject.{{$key.ToPascalCase}}), +{{- end}} +}) diags.Append(d...) if diags.HasError() { -return New{{$.Name}}ValueNull(), diags +return {{.Name}}Value{ +types.ObjectUnknown(v.AttributeTypes(ctx)), +}, diags } -{{- end}} -{{- end}} return {{.Name}}Value{ -{{- range $key, $value := .FromFuncs}} -{{- if $value.AssocExtType}} -{{$key.ToPascalCase}}: {{$key.ToCamelCase}}Val, -{{- else if $value.Default}} -{{$key.ToPascalCase}}: types.{{$value.Default}}(apiObject.{{$key.ToPascalCase}}), -{{- end}} -{{- end}} -state: attr.ValueStateKnown, +o, }, diags } diff --git a/internal/schema/templates/object_to.gotmpl b/internal/schema/templates/object_to.gotmpl index 737e8ec0..5b39e16e 100644 --- a/internal/schema/templates/object_to.gotmpl +++ b/internal/schema/templates/object_to.gotmpl @@ -13,26 +13,30 @@ diags.Append(diag.NewErrorDiagnostic( return nil, diags } -{{- range $key, $value := .ToFuncs}} -{{- if $value.AssocExtType}} -{{$value.AssocExtType.ToCamelCase}}, d := v.{{$key.ToPascalCase}}.To{{$value.AssocExtType.ToPascalCase}}(ctx) +attributes := v.Attributes() -diags.Append(d...) +{{- range $key, $value := .AttrTypesToFuncs}} + +{{$key}}Attribute, ok := attributes["{{$key}}"].({{$value.AttrValue}}) + +if !ok { +diags.Append(diag.NewErrorDiagnostic( +"{{$.Name}}Value {{$key}} is unexpected type", +fmt.Sprintf(`"{{$.Name}}Value" {{$key}} is type of %T".`, attributes["{{$key}}"]), +)) +} +{{- end}} if diags.HasError() { return nil, diags } -{{- end}} -{{- end}} -return &{{.AssocExtType.TypeReference}}{ -{{- range $key, $value := .ToFuncs}} -{{- if $value.AssocExtType}} -{{$key.ToPascalCase}}: {{$value.AssocExtType.ToCamelCase}}, -{{- else if $value.Default}} -{{$key.ToPascalCase}}: v.{{$key.ToPascalCase}}.{{$value.Default}}(), -{{- end}} +{{.AssocExtType.ToCamelCase}} := {{.AssocExtType.TypeReference}} { +{{- range $key, $value := .AttrTypesToFuncs}} +{{$key.ToPascalCase}}: {{$key}}Attribute.{{$value.ToFunc}}(), {{- end}} -}, diags +} + +return &{{.AssocExtType.ToCamelCase}}, diags } \ No newline at end of file diff --git a/internal/schema/templates/object_type_value_from_object.gotmpl b/internal/schema/templates/object_type_value_from_object.gotmpl index e727b396..595aa1f5 100644 --- a/internal/schema/templates/object_type_value_from_object.gotmpl +++ b/internal/schema/templates/object_type_value_from_object.gotmpl @@ -1,37 +1,6 @@ func (t {{.Name}}Type) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { -var diags diag.Diagnostics - -attributes := in.Attributes() - -{{range $key, $value := .AttrValues }} -{{$key.ToCamelCase}}Attribute, ok := attributes["{{$key}}"] - -if !ok { -diags.AddError( -"Attribute Missing", -`{{$key}} is missing from object`) - -return nil, diags -} - -{{$key.ToCamelCase}}Val, ok := {{$key.ToCamelCase}}Attribute.({{$value}}) - -if !ok { -diags.AddError( -"Attribute Wrong Type", -fmt.Sprintf(`{{$key}} expected to be {{$value}}, was: %T`, {{$key.ToCamelCase}}Attribute)) -} -{{end}} - -if diags.HasError() { -return nil, diags -} - return {{.Name}}Value{ -{{- range $key, $value := .AttrValues }} -{{$key.ToPascalCase}}: {{$key.ToCamelCase}}Val, -{{- end}} -state: attr.ValueStateKnown, -}, diags +ObjectValue: in, +}, nil } \ No newline at end of file diff --git a/internal/schema/templates/object_type_value_from_terraform.gotmpl b/internal/schema/templates/object_type_value_from_terraform.gotmpl index a94d4a10..6584125e 100644 --- a/internal/schema/templates/object_type_value_from_terraform.gotmpl +++ b/internal/schema/templates/object_type_value_from_terraform.gotmpl @@ -1,40 +1,22 @@ func (t {{.Name}}Type) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { -if in.Type() == nil { -return New{{.Name}}ValueNull(), nil -} - -if !in.Type().Equal(t.TerraformType(ctx)) { -return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) -} - -if !in.IsKnown() { -return New{{.Name}}ValueUnknown(), nil -} - -if in.IsNull() { -return New{{.Name}}ValueNull(), nil -} - -attributes := map[string]attr.Value{} - -val := map[string]tftypes.Value{} - -err := in.As(&val) +attrValue, err := t.ObjectType.ValueFromTerraform(ctx, in) if err != nil { return nil, err } -for k, v := range val { -a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) +objectValue, ok := attrValue.(basetypes.ObjectValue) -if err != nil { -return nil, err +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) } -attributes[k] = a +objectValuable, diags := t.ValueFromObject(ctx, objectValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting ObjectValue to ObjectValuable: %v", diags) } -return New{{.Name}}ValueMust(t.AttrTypes, attributes), nil +return objectValuable, nil } \ No newline at end of file diff --git a/internal/schema/templates/object_value_attribute_types.gotmpl b/internal/schema/templates/object_value_attribute_types.gotmpl index 8d7a5f7b..181d1172 100644 --- a/internal/schema/templates/object_value_attribute_types.gotmpl +++ b/internal/schema/templates/object_value_attribute_types.gotmpl @@ -1,8 +1,6 @@ func (v {{.Name}}Value) AttributeTypes(ctx context.Context) map[string]attr.Type { return map[string]attr.Type{ -{{- range $key, $value := .AttrTypes }} -"{{$key}}": {{$value}}, -{{- end}} +{{.AttrTypes }} } } \ No newline at end of file diff --git a/internal/schema/templates/object_value_equal.gotmpl b/internal/schema/templates/object_value_equal.gotmpl index c442ed2c..36623186 100644 --- a/internal/schema/templates/object_value_equal.gotmpl +++ b/internal/schema/templates/object_value_equal.gotmpl @@ -6,25 +6,5 @@ if !ok { return false } -if v.state != other.state { -return false -} - -if v.state != attr.ValueStateKnown { -return true -} - -{{range $key, $value := .AttrValues }} -{{- if eq $value "baseTypes.BoolValue" "baseTypes.Float64Value" "baseTypes.Int64Value" "baseTypes.NumberValue" "baseTypes.StringValue"}} -if v.{{$key.ToPascalCase}} != other.{{$key.ToPascalCase}} { -return false -} -{{- else}} -if !v.{{$key.ToPascalCase}}.Equal(other.{{$key.ToPascalCase}}) { -return false -} -{{- end}} -{{end}} - -return true +return v.ObjectValue.Equal(other.ObjectValue) } \ No newline at end of file diff --git a/internal/schema/templates/object_value_type.gotmpl b/internal/schema/templates/object_value_type.gotmpl index 8cbe0dd6..ef6064c1 100644 --- a/internal/schema/templates/object_value_type.gotmpl +++ b/internal/schema/templates/object_value_type.gotmpl @@ -1,7 +1,7 @@ func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { return {{.Name}}Type{ -basetypes.ObjectType{ +ObjectType: basetypes.ObjectType{ AttrTypes: v.AttributeTypes(ctx), }, } diff --git a/internal/schema/templates/object_value_value.gotmpl b/internal/schema/templates/object_value_value.gotmpl index 12b74387..c954d8dd 100644 --- a/internal/schema/templates/object_value_value.gotmpl +++ b/internal/schema/templates/object_value_value.gotmpl @@ -1,6 +1,3 @@ type {{.Name}}Value struct { -{{- range $key, $value := .AttrValues }} -{{$key.ToPascalCase}} {{$value}} `tfsdk:"{{$key}}"` -{{- end}} -state attr.ValueState +basetypes.ObjectValue } \ No newline at end of file diff --git a/internal/schema/templates/set_from.gotmpl b/internal/schema/templates/set_from.gotmpl new file mode 100644 index 00000000..c6e274b3 --- /dev/null +++ b/internal/schema/templates/set_from.gotmpl @@ -0,0 +1,30 @@ + +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.SetNull({{.ElementTypeType}}), +}, diags +} + +var elems []{{.ElementTypeValue}} + +for _, e := range *apiObject { +elems = append(elems, {{.ElementFrom}}(e)) +} + +l, d := basetypes.NewSetValueFrom(ctx, {{.ElementTypeType}}, elems) + +diags.Append(d...) + +if diags.HasError() { +return {{.Name}}Value{ +types.SetUnknown({{.ElementTypeType}}), +}, diags +} + +return {{.Name}}Value{ +l, +}, diags +} diff --git a/internal/schema/templates/set_to.gotmpl b/internal/schema/templates/set_to.gotmpl new file mode 100644 index 00000000..4be7e418 --- /dev/null +++ b/internal/schema/templates/set_to.gotmpl @@ -0,0 +1,28 @@ +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 +} + +var {{.AssocExtType.ToCamelCase}} {{.AssocExtType.TypeReference}} + +d := v.ElementsAs(ctx, &{{.AssocExtType.ToCamelCase}}, false) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &{{.AssocExtType.ToCamelCase}}, diags +} \ No newline at end of file diff --git a/internal/schema/templates/set_type_equal.gotmpl b/internal/schema/templates/set_type_equal.gotmpl new file mode 100644 index 00000000..51eaa4fe --- /dev/null +++ b/internal/schema/templates/set_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.SetType.Equal(other.SetType) +} \ No newline at end of file diff --git a/internal/schema/templates/set_type_string.gotmpl b/internal/schema/templates/set_type_string.gotmpl new file mode 100644 index 00000000..f01b8ac3 --- /dev/null +++ b/internal/schema/templates/set_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/set_type_typable.gotmpl b/internal/schema/templates/set_type_typable.gotmpl new file mode 100644 index 00000000..5493ed2b --- /dev/null +++ b/internal/schema/templates/set_type_typable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.SetTypable = {{.Name}}Type{} \ No newline at end of file diff --git a/internal/schema/templates/set_type_type.gotmpl b/internal/schema/templates/set_type_type.gotmpl new file mode 100644 index 00000000..21c6cf09 --- /dev/null +++ b/internal/schema/templates/set_type_type.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Type struct { +basetypes.SetType +} \ No newline at end of file diff --git a/internal/schema/templates/set_type_value_from_set.gotmpl b/internal/schema/templates/set_type_value_from_set.gotmpl new file mode 100644 index 00000000..3d84d308 --- /dev/null +++ b/internal/schema/templates/set_type_value_from_set.gotmpl @@ -0,0 +1,6 @@ + +func (t {{.Name}}Type) ValueFromSet(ctx context.Context, in basetypes.SetValue) (basetypes.SetValuable, diag.Diagnostics) { +return {{.Name}}Value{ +SetValue: in, +}, nil +} \ No newline at end of file diff --git a/internal/schema/templates/set_type_value_from_terraform.gotmpl b/internal/schema/templates/set_type_value_from_terraform.gotmpl new file mode 100644 index 00000000..4c7c1d4e --- /dev/null +++ b/internal/schema/templates/set_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.SetType.ValueFromTerraform(ctx, in) + +if err != nil { +return nil, err +} + +listValue, ok := attrValue.(basetypes.SetValue) + +if !ok { +return nil, fmt.Errorf("unexpected value type of %T", attrValue) +} + +listValuable, diags := t.ValueFromSet(ctx, listValue) + +if diags.HasError() { +return nil, fmt.Errorf("unexpected error converting SetValue to SetValuable: %v", diags) +} + +return listValuable, nil +} \ No newline at end of file diff --git a/internal/schema/templates/set_type_value_type.gotmpl b/internal/schema/templates/set_type_value_type.gotmpl new file mode 100644 index 00000000..1f7b7fea --- /dev/null +++ b/internal/schema/templates/set_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/set_value_equal.gotmpl b/internal/schema/templates/set_value_equal.gotmpl new file mode 100644 index 00000000..feca8855 --- /dev/null +++ b/internal/schema/templates/set_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.SetValue.Equal(other.SetValue) +} \ No newline at end of file diff --git a/internal/schema/templates/set_value_type.gotmpl b/internal/schema/templates/set_value_type.gotmpl new file mode 100644 index 00000000..1ebd991b --- /dev/null +++ b/internal/schema/templates/set_value_type.gotmpl @@ -0,0 +1,8 @@ + +func (v {{.Name}}Value) Type(ctx context.Context) attr.Type { +return {{.Name}}Type{ +SetType: basetypes.SetType{ +ElemType: {{.ElementType}}, +}, +} +} \ No newline at end of file diff --git a/internal/schema/templates/set_value_valuable.gotmpl b/internal/schema/templates/set_value_valuable.gotmpl new file mode 100644 index 00000000..007cc556 --- /dev/null +++ b/internal/schema/templates/set_value_valuable.gotmpl @@ -0,0 +1 @@ +var _ basetypes.SetValuable = {{.Name}}Value{} \ No newline at end of file diff --git a/internal/schema/templates/set_value_value.gotmpl b/internal/schema/templates/set_value_value.gotmpl new file mode 100644 index 00000000..e6d74d29 --- /dev/null +++ b/internal/schema/templates/set_value_value.gotmpl @@ -0,0 +1,3 @@ +type {{.Name}}Value struct { +basetypes.SetValue +} \ No newline at end of file diff --git a/internal/schema/to_from_list.go b/internal/schema/to_from_list.go new file mode 100644 index 00000000..ebd6ebc5 --- /dev/null +++ b/internal/schema/to_from_list.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromList struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + ElementTypeType string + ElementTypeValue string + ElementFrom string + templates map[string]string +} + +func NewToFromList(name string, assocExtType *AssocExtType, elemTypeType, elemTypeValue, elemFrom string) ToFromList { + t := map[string]string{ + "from": ListFromTemplate, + "to": ListToTemplate, + } + + return ToFromList{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + ElementTypeType: elemTypeType, + ElementTypeValue: elemTypeValue, + ElementFrom: elemFrom, + templates: t, + } +} + +func (o ToFromList) 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 ToFromList) 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 ToFromList) 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 + ElementTypeType string + ElementTypeValue string + ElementFrom string + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + ElementTypeType: o.ElementTypeType, + ElementTypeValue: o.ElementTypeValue, + ElementFrom: o.ElementFrom, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_list_test.go b/internal/schema/to_from_list_test.go new file mode 100644 index 00000000..20ab533d --- /dev/null +++ b/internal/schema/to_from_list_test.go @@ -0,0 +1,169 @@ +// 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 TestToFromList_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + elemTypeType string + elemTypeValue string + elemFrom string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + elemTypeType: "types.BoolType", + elemTypeValue: "types.Bool", + elemFrom: "types.BoolPointerValue", + 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.ListNull(types.BoolType), +}, diags +} + +var elems []types.Bool + +for _, e := range *apiObject { +elems = append(elems, types.BoolPointerValue(e)) +} + +l, d := basetypes.NewListValueFrom(ctx, types.BoolType, elems) + +diags.Append(d...) + +if diags.HasError() { +return ExampleValue{ +types.ListUnknown(types.BoolType), +}, diags +} + +return ExampleValue{ +l, +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromList := NewToFromList(testCase.name, testCase.assocExtType, testCase.elemTypeType, testCase.elemTypeValue, testCase.elemFrom) + + got, err := toFromList.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 TestToFromList_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + elemTypeType string + elemTypeValue string + elemFrom string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + elemTypeType: "types.BoolType", + elemTypeValue: "types.Bool", + elemFrom: "types.BoolPointerValue", + 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 +} + +var apisdkType apisdk.Type + +d := v.ElementsAs(ctx, &apisdkType, false) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &apisdkType, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromList := NewToFromList(testCase.name, testCase.assocExtType, testCase.elemTypeType, testCase.elemTypeValue, testCase.elemFrom) + + got, err := toFromList.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_map.go b/internal/schema/to_from_map.go new file mode 100644 index 00000000..c814fe26 --- /dev/null +++ b/internal/schema/to_from_map.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromMap struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + ElementTypeType string + ElementTypeValue string + ElementFrom string + templates map[string]string +} + +func NewToFromMap(name string, assocExtType *AssocExtType, elemTypeType, elemTypeValue, elemFrom string) ToFromMap { + t := map[string]string{ + "from": MapFromTemplate, + "to": MapToTemplate, + } + + return ToFromMap{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + ElementTypeType: elemTypeType, + ElementTypeValue: elemTypeValue, + ElementFrom: elemFrom, + templates: t, + } +} + +func (o ToFromMap) 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 ToFromMap) 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 ToFromMap) 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 + ElementTypeType string + ElementTypeValue string + ElementFrom string + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + ElementTypeType: o.ElementTypeType, + ElementTypeValue: o.ElementTypeValue, + ElementFrom: o.ElementFrom, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_map_test.go b/internal/schema/to_from_map_test.go new file mode 100644 index 00000000..0707e2d3 --- /dev/null +++ b/internal/schema/to_from_map_test.go @@ -0,0 +1,169 @@ +// 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 TestToFromMap_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + elemTypeType string + elemTypeValue string + elemFrom string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + elemTypeType: "types.BoolType", + elemTypeValue: "types.Bool", + elemFrom: "types.BoolPointerValue", + 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.MapNull(types.BoolType), +}, diags +} + +elems := make(map[string]types.Bool) + +for k, e := range *apiObject { +elems[k] = types.BoolPointerValue(e) +} + +l, d := basetypes.NewMapValueFrom(ctx, types.BoolType, elems) + +diags.Append(d...) + +if diags.HasError() { +return ExampleValue{ +types.MapUnknown(types.BoolType), +}, diags +} + +return ExampleValue{ +l, +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromMap := NewToFromMap(testCase.name, testCase.assocExtType, testCase.elemTypeType, testCase.elemTypeValue, testCase.elemFrom) + + got, err := toFromMap.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 TestToFromMap_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + elemTypeType string + elemTypeValue string + elemFrom string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + elemTypeType: "types.BoolType", + elemTypeValue: "types.Bool", + elemFrom: "types.BoolPointerValue", + 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 +} + +var apisdkType apisdk.Type + +d := v.ElementsAs(ctx, &apisdkType, false) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &apisdkType, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromMap := NewToFromMap(testCase.name, testCase.assocExtType, testCase.elemTypeType, testCase.elemTypeValue, testCase.elemFrom) + + got, err := toFromMap.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_nested_object.go b/internal/schema/to_from_nested_object.go new file mode 100644 index 00000000..e0ab1154 --- /dev/null +++ b/internal/schema/to_from_nested_object.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromNestedObject struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + ToFuncs map[FrameworkIdentifier]ToFromConversion + FromFuncs map[FrameworkIdentifier]ToFromConversion + templates map[string]string +} + +func NewToFromNestedObject(name string, assocExtType *AssocExtType, toFuncs, fromFuncs map[string]ToFromConversion) ToFromNestedObject { + t := map[string]string{ + "from": NestedObjectFromTemplate, + "to": NestedObjectToTemplate, + } + + tf := make(map[FrameworkIdentifier]ToFromConversion, len(toFuncs)) + + for k, v := range toFuncs { + tf[FrameworkIdentifier(k)] = v + } + + ff := make(map[FrameworkIdentifier]ToFromConversion, len(fromFuncs)) + + for k, v := range fromFuncs { + ff[FrameworkIdentifier(k)] = v + } + + return ToFromNestedObject{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + FromFuncs: ff, + ToFuncs: tf, + templates: t, + } +} + +func (o ToFromNestedObject) 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 ToFromNestedObject) 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 + ToFuncs map[FrameworkIdentifier]ToFromConversion + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + ToFuncs: o.ToFuncs, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o ToFromNestedObject) 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 + FromFuncs map[FrameworkIdentifier]ToFromConversion + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + FromFuncs: o.FromFuncs, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_nested_object_test.go b/internal/schema/to_from_nested_object_test.go new file mode 100644 index 00000000..ef716064 --- /dev/null +++ b/internal/schema/to_from_nested_object_test.go @@ -0,0 +1,228 @@ +// 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 TestToFromNestedObject_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + fromFuncs map[string]ToFromConversion + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + AssociatedExternalType: &schema.AssociatedExternalType{ + Type: "*apisdk.Type", + }, + }, + fromFuncs: map[string]ToFromConversion{ + "bool_attribute": { + Default: "BoolPointerValue", + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return NewExampleValueNull(), diags +} + +return ExampleValue{ +BoolAttribute: types.BoolPointerValue(apiObject.BoolAttribute), +state: attr.ValueStateKnown, +}, diags +} +`), + }, + "nested-assoc-ext-type": { + name: "Example", + assocExtType: &AssocExtType{ + AssociatedExternalType: &schema.AssociatedExternalType{ + Type: "*apisdk.Type", + }, + }, + fromFuncs: map[string]ToFromConversion{ + "bool_attribute": { + AssocExtType: &AssocExtType{ + AssociatedExternalType: &schema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + }, + expected: []byte(` +func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { +var diags diag.Diagnostics + +if apiObject == nil { +return NewExampleValueNull(), diags +} + +boolAttributeVal, d := BoolAttributeValue{}.FromApiBoolAttribute(ctx, apiObject.BoolAttribute) + +diags.Append(d...) + +if diags.HasError() { +return NewExampleValueUnknown(), diags +} + +return ExampleValue{ +BoolAttribute: boolAttributeVal, +state: attr.ValueStateKnown, +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromObject := NewToFromNestedObject(testCase.name, testCase.assocExtType, nil, testCase.fromFuncs) + + got, err := toFromObject.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 TestToFromNestedObject_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + toFuncs map[string]ToFromConversion + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + toFuncs: map[string]ToFromConversion{ + "bool_attribute": { + Default: "ValueBoolPointer", + }, + }, + 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 +} + +return &apisdk.Type{ +BoolAttribute: v.BoolAttribute.ValueBoolPointer(), +}, diags +}`), + }, + "nested-assoc-ext-type": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + toFuncs: map[string]ToFromConversion{ + "bool_attribute": { + AssocExtType: &AssocExtType{ + AssociatedExternalType: &schema.AssociatedExternalType{ + Type: "*api.BoolAttribute", + }, + }, + }, + }, + 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 +} + +apiBoolAttribute, d := v.BoolAttribute.ToApiBoolAttribute(ctx) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &apisdk.Type{ +BoolAttribute: apiBoolAttribute, +}, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromObject := NewToFromNestedObject(testCase.name, testCase.assocExtType, testCase.toFuncs, nil) + + got, err := toFromObject.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_object.go b/internal/schema/to_from_object.go index b0e5ed00..b3344b35 100644 --- a/internal/schema/to_from_object.go +++ b/internal/schema/to_from_object.go @@ -9,37 +9,37 @@ import ( ) type ToFromObject struct { - Name FrameworkIdentifier - AssocExtType *AssocExtType - ToFuncs map[FrameworkIdentifier]ToFromConversion - FromFuncs map[FrameworkIdentifier]ToFromConversion - templates map[string]string + Name FrameworkIdentifier + AssocExtType *AssocExtType + AttrTypesToFuncs map[FrameworkIdentifier]AttrTypesToFuncs + AttrTypesFromFuncs map[FrameworkIdentifier]string + templates map[string]string } -func NewToFromObject(name string, assocExtType *AssocExtType, toFuncs, fromFuncs map[string]ToFromConversion) ToFromObject { +func NewToFromObject(name string, assocExtType *AssocExtType, attrTypesToFuncs map[string]AttrTypesToFuncs, attrTypesFromFuncs map[string]string) ToFromObject { t := map[string]string{ "from": ObjectFromTemplate, "to": ObjectToTemplate, } - tf := make(map[FrameworkIdentifier]ToFromConversion, len(toFuncs)) + attf := make(map[FrameworkIdentifier]AttrTypesToFuncs, len(attrTypesToFuncs)) - for k, v := range toFuncs { - tf[FrameworkIdentifier(k)] = v + for k, v := range attrTypesToFuncs { + attf[FrameworkIdentifier(k)] = v } - ff := make(map[FrameworkIdentifier]ToFromConversion, len(fromFuncs)) + atff := make(map[FrameworkIdentifier]string, len(attrTypesFromFuncs)) - for k, v := range fromFuncs { - ff[FrameworkIdentifier(k)] = v + for k, v := range attrTypesFromFuncs { + atff[FrameworkIdentifier(k)] = v } return ToFromObject{ - Name: FrameworkIdentifier(name), - AssocExtType: assocExtType, - FromFuncs: ff, - ToFuncs: tf, - templates: t, + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + AttrTypesToFuncs: attf, + AttrTypesFromFuncs: atff, + templates: t, } } @@ -76,13 +76,13 @@ func (o ToFromObject) renderTo() ([]byte, error) { } err = t.Execute(&buf, struct { - Name string - AssocExtType *AssocExtType - ToFuncs map[FrameworkIdentifier]ToFromConversion + Name string + AssocExtType *AssocExtType + AttrTypesToFuncs map[FrameworkIdentifier]AttrTypesToFuncs }{ - Name: o.Name.ToPascalCase(), - AssocExtType: o.AssocExtType, - ToFuncs: o.ToFuncs, + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + AttrTypesToFuncs: o.AttrTypesToFuncs, }) if err != nil { @@ -102,13 +102,13 @@ func (o ToFromObject) renderFrom() ([]byte, error) { } err = t.Execute(&buf, struct { - Name string - AssocExtType *AssocExtType - FromFuncs map[FrameworkIdentifier]ToFromConversion + Name string + AssocExtType *AssocExtType + AttrTypesFromFuncs map[FrameworkIdentifier]string }{ - Name: o.Name.ToPascalCase(), - AssocExtType: o.AssocExtType, - FromFuncs: o.FromFuncs, + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + AttrTypesFromFuncs: o.AttrTypesFromFuncs, }) if err != nil { diff --git a/internal/schema/to_from_object_test.go b/internal/schema/to_from_object_test.go index 9fcb0f56..dfd13be6 100644 --- a/internal/schema/to_from_object_test.go +++ b/internal/schema/to_from_object_test.go @@ -15,74 +15,57 @@ func TestToFromObject_renderFrom(t *testing.T) { t.Parallel() testCases := map[string]struct { - name string - assocExtType *AssocExtType - fromFuncs map[string]ToFromConversion - expected []byte - expectedError error + name string + assocExtType *AssocExtType + attrTypesFromFuncs map[string]string + expected []byte + expectedError error }{ "default": { name: "Example", assocExtType: &AssocExtType{ - AssociatedExternalType: &schema.AssociatedExternalType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, Type: "*apisdk.Type", }, }, - fromFuncs: map[string]ToFromConversion{ - "bool_attribute": { - Default: "BoolPointerValue", - }, + attrTypesFromFuncs: map[string]string{ + "bool": "types.BoolPointerValue", + "float64": "types.Float64PointerValue", + "int64": "types.Int64PointerValue", + "number": "types.NumberValue", + "string": "types.StringPointerValue", }, expected: []byte(` func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { var diags diag.Diagnostics if apiObject == nil { -return NewExampleValueNull(), diags -} - return ExampleValue{ -BoolAttribute: types.BoolPointerValue(apiObject.BoolAttribute), -state: attr.ValueStateKnown, +types.ObjectNull(v.AttributeTypes(ctx)), }, diags } -`), - }, - "nested-assoc-ext-type": { - name: "Example", - assocExtType: &AssocExtType{ - AssociatedExternalType: &schema.AssociatedExternalType{ - Type: "*apisdk.Type", - }, - }, - fromFuncs: map[string]ToFromConversion{ - "bool_attribute": { - AssocExtType: &AssocExtType{ - AssociatedExternalType: &schema.AssociatedExternalType{ - Type: "*api.BoolAttribute", - }, - }, - }, - }, - expected: []byte(` -func (v ExampleValue) FromApisdkType(ctx context.Context, apiObject *apisdk.Type) (ExampleValue, diag.Diagnostics) { -var diags diag.Diagnostics -if apiObject == nil { -return NewExampleValueNull(), diags -} - -boolAttributeVal, d := BoolAttributeValue{}.FromApiBoolAttribute(ctx, apiObject.BoolAttribute) +o, d := basetypes.NewObjectValue(v.AttributeTypes(ctx), map[string]attr.Value{ +"bool": types.BoolPointerValue(apiObject.Bool), +"float64": types.Float64PointerValue(apiObject.Float64), +"int64": types.Int64PointerValue(apiObject.Int64), +"number": types.NumberValue(apiObject.Number), +"string": types.StringPointerValue(apiObject.String), +}) diags.Append(d...) if diags.HasError() { -return NewExampleValueNull(), diags +return ExampleValue{ +types.ObjectUnknown(v.AttributeTypes(ctx)), +}, diags } return ExampleValue{ -BoolAttribute: boolAttributeVal, -state: attr.ValueStateKnown, +o, }, diags } `), @@ -95,7 +78,7 @@ state: attr.ValueStateKnown, t.Run(name, func(t *testing.T) { t.Parallel() - toFromObject := NewToFromObject(testCase.name, testCase.assocExtType, nil, testCase.fromFuncs) + toFromObject := NewToFromObject(testCase.name, testCase.assocExtType, nil, testCase.attrTypesFromFuncs) got, err := toFromObject.renderFrom() @@ -114,11 +97,12 @@ func TestToFromObject_renderTo(t *testing.T) { t.Parallel() testCases := map[string]struct { - name string - assocExtType *AssocExtType - toFuncs map[string]ToFromConversion - expected []byte - expectedError error + name string + assocExtType *AssocExtType + attrTypesToFuncs map[string]AttrTypesToFuncs + attrTypesFromFuncs map[string]string + expected []byte + expectedError error }{ "default": { name: "Example", @@ -130,9 +114,27 @@ func TestToFromObject_renderTo(t *testing.T) { Type: "*apisdk.Type", }, }, - toFuncs: map[string]ToFromConversion{ - "bool_attribute": { - Default: "ValueBoolPointer", + attrTypesToFuncs: map[string]AttrTypesToFuncs{ + "bool": { + AttrValue: "types.Bool", + ToFunc: "ValueBoolPointer", + }, + "float64": { + AttrValue: "types.Float64", + ToFunc: "ValueFloat64Pointer", + }, + "int64": { + AttrValue: "types.Int64", + ToFunc: "ValueInt64Pointer", + }, + + "number": { + AttrValue: "types.Number", + ToFunc: "ValueBigFloat", + }, + "string": { + AttrValue: "types.String", + ToFunc: "ValueStringPointer", }, }, expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { @@ -151,57 +153,66 @@ diags.Append(diag.NewErrorDiagnostic( return nil, diags } -return &apisdk.Type{ -BoolAttribute: v.BoolAttribute.ValueBoolPointer(), -}, diags -}`), - }, - "nested-assoc-ext-type": { - name: "Example", - assocExtType: &AssocExtType{ - &schema.AssociatedExternalType{ - Import: &code.Import{ - Path: "example.com/apisdk", - }, - Type: "*apisdk.Type", - }, - }, - toFuncs: map[string]ToFromConversion{ - "bool_attribute": { - AssocExtType: &AssocExtType{ - AssociatedExternalType: &schema.AssociatedExternalType{ - Type: "*api.BoolAttribute", - }, - }, - }, - }, - expected: []byte(`func (v ExampleValue) ToApisdkType(ctx context.Context) (*apisdk.Type, diag.Diagnostics) { -var diags diag.Diagnostics +attributes := v.Attributes() -if v.IsNull() { -return nil, diags +boolAttribute, ok := attributes["bool"].(types.Bool) + +if !ok { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue bool is unexpected type", +fmt.Sprintf(` + "`" + `"ExampleValue" bool is type of %T".` + "`" + `, attributes["bool"]), +)) } -if v.IsUnknown() { +float64Attribute, ok := attributes["float64"].(types.Float64) + +if !ok { diags.Append(diag.NewErrorDiagnostic( -"ExampleValue Value Is Unknown", -` + "`" + `"ExampleValue" is unknown.` + "`" + `, +"ExampleValue float64 is unexpected type", +fmt.Sprintf(` + "`" + `"ExampleValue" float64 is type of %T".` + "`" + `, attributes["float64"]), )) +} -return nil, diags +int64Attribute, ok := attributes["int64"].(types.Int64) + +if !ok { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue int64 is unexpected type", +fmt.Sprintf(` + "`" + `"ExampleValue" int64 is type of %T".` + "`" + `, attributes["int64"]), +)) } -apiBoolAttribute, d := v.BoolAttribute.ToApiBoolAttribute(ctx) +numberAttribute, ok := attributes["number"].(types.Number) -diags.Append(d...) +if !ok { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue number is unexpected type", +fmt.Sprintf(` + "`" + `"ExampleValue" number is type of %T".` + "`" + `, attributes["number"]), +)) +} + +stringAttribute, ok := attributes["string"].(types.String) + +if !ok { +diags.Append(diag.NewErrorDiagnostic( +"ExampleValue string is unexpected type", +fmt.Sprintf(` + "`" + `"ExampleValue" string is type of %T".` + "`" + `, attributes["string"]), +)) +} if diags.HasError() { return nil, diags } -return &apisdk.Type{ -BoolAttribute: apiBoolAttribute, -}, diags +apisdkType := apisdk.Type { +Bool: boolAttribute.ValueBoolPointer(), +Float64: float64Attribute.ValueFloat64Pointer(), +Int64: int64Attribute.ValueInt64Pointer(), +Number: numberAttribute.ValueBigFloat(), +String: stringAttribute.ValueStringPointer(), +} + +return &apisdkType, diags }`), }, } @@ -212,7 +223,7 @@ BoolAttribute: apiBoolAttribute, t.Run(name, func(t *testing.T) { t.Parallel() - toFromObject := NewToFromObject(testCase.name, testCase.assocExtType, testCase.toFuncs, nil) + toFromObject := NewToFromObject(testCase.name, testCase.assocExtType, testCase.attrTypesToFuncs, nil) got, err := toFromObject.renderTo() diff --git a/internal/schema/to_from_set.go b/internal/schema/to_from_set.go new file mode 100644 index 00000000..a0687ebd --- /dev/null +++ b/internal/schema/to_from_set.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "bytes" + "text/template" +) + +type ToFromSet struct { + Name FrameworkIdentifier + AssocExtType *AssocExtType + ElementTypeType string + ElementTypeValue string + ElementFrom string + templates map[string]string +} + +func NewToFromSet(name string, assocExtType *AssocExtType, elemTypeType, elemTypeValue, elemFrom string) ToFromSet { + t := map[string]string{ + "from": SetFromTemplate, + "to": SetToTemplate, + } + + return ToFromSet{ + Name: FrameworkIdentifier(name), + AssocExtType: assocExtType, + ElementTypeType: elemTypeType, + ElementTypeValue: elemTypeValue, + ElementFrom: elemFrom, + templates: t, + } +} + +func (o ToFromSet) 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 ToFromSet) 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 ToFromSet) 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 + ElementTypeType string + ElementTypeValue string + ElementFrom string + }{ + Name: o.Name.ToPascalCase(), + AssocExtType: o.AssocExtType, + ElementTypeType: o.ElementTypeType, + ElementTypeValue: o.ElementTypeValue, + ElementFrom: o.ElementFrom, + }) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/schema/to_from_set_test.go b/internal/schema/to_from_set_test.go new file mode 100644 index 00000000..a4c359f9 --- /dev/null +++ b/internal/schema/to_from_set_test.go @@ -0,0 +1,169 @@ +// 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 TestToFromSet_renderFrom(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + elemTypeType string + elemTypeValue string + elemFrom string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + elemTypeType: "types.BoolType", + elemTypeValue: "types.Bool", + elemFrom: "types.BoolPointerValue", + 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.SetNull(types.BoolType), +}, diags +} + +var elems []types.Bool + +for _, e := range *apiObject { +elems = append(elems, types.BoolPointerValue(e)) +} + +l, d := basetypes.NewSetValueFrom(ctx, types.BoolType, elems) + +diags.Append(d...) + +if diags.HasError() { +return ExampleValue{ +types.SetUnknown(types.BoolType), +}, diags +} + +return ExampleValue{ +l, +}, diags +} +`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromSet := NewToFromSet(testCase.name, testCase.assocExtType, testCase.elemTypeType, testCase.elemTypeValue, testCase.elemFrom) + + got, err := toFromSet.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 TestToFromSet_renderTo(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + name string + assocExtType *AssocExtType + elemTypeType string + elemTypeValue string + elemFrom string + expected []byte + expectedError error + }{ + "default": { + name: "Example", + assocExtType: &AssocExtType{ + &schema.AssociatedExternalType{ + Import: &code.Import{ + Path: "example.com/apisdk", + }, + Type: "*apisdk.Type", + }, + }, + elemTypeType: "types.BoolType", + elemTypeValue: "types.Bool", + elemFrom: "types.BoolPointerValue", + 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 +} + +var apisdkType apisdk.Type + +d := v.ElementsAs(ctx, &apisdkType, false) + +diags.Append(d...) + +if diags.HasError() { +return nil, diags +} + +return &apisdkType, diags +}`), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + toFromSet := NewToFromSet(testCase.name, testCase.assocExtType, testCase.elemTypeType, testCase.elemTypeValue, testCase.elemFrom) + + got, err := toFromSet.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) + } + }) + } +}