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_generate/embed.go b/internal/datasource_generate/embed.go index 9a160b78..e8a7b61b 100644 --- a/internal/datasource_generate/embed.go +++ b/internal/datasource_generate/embed.go @@ -25,7 +25,7 @@ var listAttributeTemplate string 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 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/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/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_generate/embed.go b/internal/provider_generate/embed.go index 1ba31363..19e2b7e9 100644 --- a/internal/provider_generate/embed.go +++ b/internal/provider_generate/embed.go @@ -25,7 +25,7 @@ var listAttributeTemplate string 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 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/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/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_generate/embed.go b/internal/resource_generate/embed.go index 0d818c21..44a0ec33 100644 --- a/internal/resource_generate/embed.go +++ b/internal/resource_generate/embed.go @@ -25,7 +25,7 @@ var listAttributeTemplate string 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 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/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/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/embed.go b/internal/schema/embed.go index 6c7ea03b..5934034b 100644 --- a/internal/schema/embed.go +++ b/internal/schema/embed.go @@ -187,6 +187,51 @@ 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 diff --git a/internal/schema/templates/list_from.gotmpl b/internal/schema/templates/list_from.gotmpl index 1dc5133a..c4051bae 100644 --- a/internal/schema/templates/list_from.gotmpl +++ b/internal/schema/templates/list_from.gotmpl @@ -8,7 +8,7 @@ types.ListNull({{.ElementTypeType}}), }, diags } - var elems []{{.ElementTypeValue}} +var elems []{{.ElementTypeValue}} for _, e := range *apiObject { elems = append(elems, {{.ElementFrom}}(e)) diff --git a/internal/schema/templates/map_from.gotmpl b/internal/schema/templates/map_from.gotmpl new file mode 100644 index 00000000..9f56a55a --- /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 MapAttributeValue{ +types.MapNull({{.ElementTypeType}}), +}, diags +} + +return MapAttributeValue{ +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/set_from.gotmpl b/internal/schema/templates/set_from.gotmpl index dfb7644b..f7be55a6 100644 --- a/internal/schema/templates/set_from.gotmpl +++ b/internal/schema/templates/set_from.gotmpl @@ -8,7 +8,7 @@ types.SetNull({{.ElementTypeType}}), }, diags } - var elems []{{.ElementTypeValue}} +var elems []{{.ElementTypeValue}} for _, e := range *apiObject { elems = append(elems, {{.ElementFrom}}(e)) diff --git a/internal/schema/to_from_list_test.go b/internal/schema/to_from_list_test.go index 075c6c5e..e5575e49 100644 --- a/internal/schema/to_from_list_test.go +++ b/internal/schema/to_from_list_test.go @@ -46,7 +46,7 @@ types.ListNull(types.BoolType), }, diags } - var elems []types.Bool +var elems []types.Bool for _, e := range *apiObject { elems = append(elems, types.BoolPointerValue(e)) 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..d4a5664f --- /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 MapAttributeValue{ +types.MapNull(types.BoolType), +}, diags +} + +return MapAttributeValue{ +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_set_test.go b/internal/schema/to_from_set_test.go index 773ab45f..ef529d2a 100644 --- a/internal/schema/to_from_set_test.go +++ b/internal/schema/to_from_set_test.go @@ -46,7 +46,7 @@ types.SetNull(types.BoolType), }, diags } - var elems []types.Bool +var elems []types.Bool for _, e := range *apiObject { elems = append(elems, types.BoolPointerValue(e))