From ef28172a4de655b902d120ea8646f51fb6f6d65a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 20:28:31 +0000 Subject: [PATCH] feat: add dynamic attribute support for self-referential schemas Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011drBEhNrtGfnXQNsedmJLa --- internal/convert/plan_modifiers.go | 1 + internal/convert/validators.go | 1 + internal/datasource/convert.go | 2 + internal/datasource/dynamic_attribute.go | 156 +++++++++++++++ internal/model/model.go | 1 + internal/provider/convert.go | 2 + internal/provider/dynamic_attribute.go | 156 +++++++++++++++ internal/resource/convert.go | 2 + internal/resource/dynamic_attribute.go | 167 ++++++++++++++++ internal/resource/dynamic_attribute_test.go | 207 ++++++++++++++++++++ internal/schema/types.go | 1 + 11 files changed, 696 insertions(+) create mode 100644 internal/datasource/dynamic_attribute.go create mode 100644 internal/provider/dynamic_attribute.go create mode 100644 internal/resource/dynamic_attribute.go create mode 100644 internal/resource/dynamic_attribute_test.go diff --git a/internal/convert/plan_modifiers.go b/internal/convert/plan_modifiers.go index a8cdf98c..9511babe 100644 --- a/internal/convert/plan_modifiers.go +++ b/internal/convert/plan_modifiers.go @@ -15,6 +15,7 @@ import ( const ( PlanModifierTypeBool PlanModifierType = "Bool" + PlanModifierTypeDynamic PlanModifierType = "Dynamic" PlanModifierTypeFloat64 PlanModifierType = "Float64" PlanModifierTypeInt64 PlanModifierType = "Int64" PlanModifierTypeList PlanModifierType = "List" diff --git a/internal/convert/validators.go b/internal/convert/validators.go index ac9c60aa..b5c6984a 100644 --- a/internal/convert/validators.go +++ b/internal/convert/validators.go @@ -15,6 +15,7 @@ import ( const ( ValidatorTypeBool ValidatorType = "Bool" + ValidatorTypeDynamic ValidatorType = "Dynamic" ValidatorTypeFloat64 ValidatorType = "Float64" ValidatorTypeInt64 ValidatorType = "Int64" ValidatorTypeList ValidatorType = "List" diff --git a/internal/datasource/convert.go b/internal/datasource/convert.go index 52fdf59a..ab60bbdb 100644 --- a/internal/datasource/convert.go +++ b/internal/datasource/convert.go @@ -86,6 +86,8 @@ func NewAttribute(a datasource.Attribute) (generatorschema.GeneratorAttribute, e switch { case a.Bool != nil: return NewGeneratorBoolAttribute(a.Name, a.Bool) + case a.Dynamic != nil: + return NewGeneratorDynamicAttribute(a.Name, a.Dynamic) case a.Float64 != nil: return NewGeneratorFloat64Attribute(a.Name, a.Float64) case a.Int64 != nil: diff --git a/internal/datasource/dynamic_attribute.go b/internal/datasource/dynamic_attribute.go new file mode 100644 index 00000000..5bc2524a --- /dev/null +++ b/internal/datasource/dynamic_attribute.go @@ -0,0 +1,156 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package datasource + +import ( + "bytes" + "fmt" + + "github.com/hashicorp/terraform-plugin-codegen-spec/datasource" + + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/convert" + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/model" + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/schema" +) + +type GeneratorDynamicAttribute struct { + AssociatedExternalType *schema.AssocExtType + ComputedOptionalRequired convert.ComputedOptionalRequired + CustomType convert.CustomTypePrimitive + DeprecationMessage convert.DeprecationMessage + Description convert.Description + Sensitive convert.Sensitive + Validators convert.Validators +} + +func NewGeneratorDynamicAttribute(name string, a *datasource.DynamicAttribute) (GeneratorDynamicAttribute, error) { + if a == nil { + return GeneratorDynamicAttribute{}, fmt.Errorf("*datasource.DynamicAttribute is nil") + } + + c := convert.NewComputedOptionalRequired(a.ComputedOptionalRequired) + + ctp := convert.NewCustomTypePrimitive(a.CustomType, a.AssociatedExternalType, name) + + d := convert.NewDescription(a.Description) + + dm := convert.NewDeprecationMessage(a.DeprecationMessage) + + s := convert.NewSensitive(a.Sensitive) + + v := convert.NewValidators(convert.ValidatorTypeDynamic, a.Validators.CustomValidators()) + + return GeneratorDynamicAttribute{ + AssociatedExternalType: schema.NewAssocExtType(a.AssociatedExternalType), + ComputedOptionalRequired: c, + CustomType: ctp, + DeprecationMessage: dm, + Description: d, + Sensitive: s, + Validators: v, + }, nil +} + +func (g GeneratorDynamicAttribute) GeneratorSchemaType() schema.Type { + return schema.GeneratorDynamicAttribute +} + +func (g GeneratorDynamicAttribute) Imports() *schema.Imports { + imports := schema.NewImports() + + imports.Append(g.CustomType.Imports()) + + imports.Append(g.Validators.Imports()) + + if g.AssociatedExternalType != nil { + imports.Append(schema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + + return imports +} + +func (g GeneratorDynamicAttribute) Equal(ga schema.GeneratorAttribute) bool { + h, ok := ga.(GeneratorDynamicAttribute) + + if !ok { + return false + } + + if !g.AssociatedExternalType.Equal(h.AssociatedExternalType) { + return false + } + + if !g.ComputedOptionalRequired.Equal(h.ComputedOptionalRequired) { + return false + } + + if !g.CustomType.Equal(h.CustomType) { + return false + } + + if !g.DeprecationMessage.Equal(h.DeprecationMessage) { + return false + } + + if !g.Description.Equal(h.Description) { + return false + } + + if !g.Sensitive.Equal(h.Sensitive) { + return false + } + + return g.Validators.Equal(h.Validators) +} + +func (g GeneratorDynamicAttribute) Schema(name schema.FrameworkIdentifier) (string, error) { + var b bytes.Buffer + + b.WriteString(fmt.Sprintf("%q: schema.DynamicAttribute{\n", name)) + b.Write(g.CustomType.Schema()) + b.Write(g.ComputedOptionalRequired.Schema()) + b.Write(g.Sensitive.Schema()) + b.Write(g.Description.Schema()) + b.Write(g.DeprecationMessage.Schema()) + b.Write(g.Validators.Schema()) + b.WriteString("},") + + return b.String(), nil +} + +func (g GeneratorDynamicAttribute) ModelField(name schema.FrameworkIdentifier) (model.Field, error) { + field := model.Field{ + Name: name.ToPascalCase(), + TfsdkName: name.ToString(), + ValueType: model.DynamicValueType, + } + + customValueType := g.CustomType.ValueType() + + if customValueType != "" { + field.ValueType = customValueType + } + + return field, nil +} + +// AttrType returns a string representation of a basetypes.DynamicTypable type. +func (g GeneratorDynamicAttribute) AttrType(name schema.FrameworkIdentifier) (string, error) { + if g.AssociatedExternalType != nil { + return fmt.Sprintf("%sType{}", name.ToPascalCase()), nil + } + + return "basetypes.DynamicType{}", nil +} + +// AttrValue returns a string representation of a basetypes.DynamicValuable type. +func (g GeneratorDynamicAttribute) AttrValue(name schema.FrameworkIdentifier) string { + if g.AssociatedExternalType != nil { + return fmt.Sprintf("%sValue", name.ToPascalCase()) + } + + return "basetypes.DynamicValue" +} diff --git a/internal/model/model.go b/internal/model/model.go index 4939e92e..1c0a7c5e 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -10,6 +10,7 @@ import ( const ( BoolValueType = "types.Bool" + DynamicValueType = "types.Dynamic" Float64ValueType = "types.Float64" Int64ValueType = "types.Int64" ListValueType = "types.List" diff --git a/internal/provider/convert.go b/internal/provider/convert.go index cfe4eb33..302504c5 100644 --- a/internal/provider/convert.go +++ b/internal/provider/convert.go @@ -89,6 +89,8 @@ func NewAttribute(a provider.Attribute) (generatorschema.GeneratorAttribute, err switch { case a.Bool != nil: return NewGeneratorBoolAttribute(a.Name, a.Bool) + case a.Dynamic != nil: + return NewGeneratorDynamicAttribute(a.Name, a.Dynamic) case a.Float64 != nil: return NewGeneratorFloat64Attribute(a.Name, a.Float64) case a.Int64 != nil: diff --git a/internal/provider/dynamic_attribute.go b/internal/provider/dynamic_attribute.go new file mode 100644 index 00000000..dea7f3d2 --- /dev/null +++ b/internal/provider/dynamic_attribute.go @@ -0,0 +1,156 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "bytes" + "fmt" + + "github.com/hashicorp/terraform-plugin-codegen-spec/provider" + + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/convert" + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/model" + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/schema" +) + +type GeneratorDynamicAttribute struct { + AssociatedExternalType *schema.AssocExtType + OptionalRequired convert.OptionalRequired + CustomType convert.CustomTypePrimitive + DeprecationMessage convert.DeprecationMessage + Description convert.Description + Sensitive convert.Sensitive + Validators convert.Validators +} + +func NewGeneratorDynamicAttribute(name string, a *provider.DynamicAttribute) (GeneratorDynamicAttribute, error) { + if a == nil { + return GeneratorDynamicAttribute{}, fmt.Errorf("*provider.DynamicAttribute is nil") + } + + c := convert.NewOptionalRequired(a.OptionalRequired) + + ctp := convert.NewCustomTypePrimitive(a.CustomType, a.AssociatedExternalType, name) + + d := convert.NewDescription(a.Description) + + dm := convert.NewDeprecationMessage(a.DeprecationMessage) + + s := convert.NewSensitive(a.Sensitive) + + v := convert.NewValidators(convert.ValidatorTypeDynamic, a.Validators.CustomValidators()) + + return GeneratorDynamicAttribute{ + AssociatedExternalType: schema.NewAssocExtType(a.AssociatedExternalType), + OptionalRequired: c, + CustomType: ctp, + DeprecationMessage: dm, + Description: d, + Sensitive: s, + Validators: v, + }, nil +} + +func (g GeneratorDynamicAttribute) GeneratorSchemaType() schema.Type { + return schema.GeneratorDynamicAttribute +} + +func (g GeneratorDynamicAttribute) Imports() *schema.Imports { + imports := schema.NewImports() + + imports.Append(g.CustomType.Imports()) + + imports.Append(g.Validators.Imports()) + + if g.AssociatedExternalType != nil { + imports.Append(schema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + + return imports +} + +func (g GeneratorDynamicAttribute) Equal(ga schema.GeneratorAttribute) bool { + h, ok := ga.(GeneratorDynamicAttribute) + + if !ok { + return false + } + + if !g.AssociatedExternalType.Equal(h.AssociatedExternalType) { + return false + } + + if !g.OptionalRequired.Equal(h.OptionalRequired) { + return false + } + + if !g.CustomType.Equal(h.CustomType) { + return false + } + + if !g.DeprecationMessage.Equal(h.DeprecationMessage) { + return false + } + + if !g.Description.Equal(h.Description) { + return false + } + + if !g.Sensitive.Equal(h.Sensitive) { + return false + } + + return g.Validators.Equal(h.Validators) +} + +func (g GeneratorDynamicAttribute) Schema(name schema.FrameworkIdentifier) (string, error) { + var b bytes.Buffer + + b.WriteString(fmt.Sprintf("%q: schema.DynamicAttribute{\n", name)) + b.Write(g.CustomType.Schema()) + b.Write(g.OptionalRequired.Schema()) + b.Write(g.Sensitive.Schema()) + b.Write(g.Description.Schema()) + b.Write(g.DeprecationMessage.Schema()) + b.Write(g.Validators.Schema()) + b.WriteString("},") + + return b.String(), nil +} + +func (g GeneratorDynamicAttribute) ModelField(name schema.FrameworkIdentifier) (model.Field, error) { + field := model.Field{ + Name: name.ToPascalCase(), + TfsdkName: name.ToString(), + ValueType: model.DynamicValueType, + } + + customValueType := g.CustomType.ValueType() + + if customValueType != "" { + field.ValueType = customValueType + } + + return field, nil +} + +// AttrType returns a string representation of a basetypes.DynamicTypable type. +func (g GeneratorDynamicAttribute) AttrType(name schema.FrameworkIdentifier) (string, error) { + if g.AssociatedExternalType != nil { + return fmt.Sprintf("%sType{}", name.ToPascalCase()), nil + } + + return "basetypes.DynamicType{}", nil +} + +// AttrValue returns a string representation of a basetypes.DynamicValuable type. +func (g GeneratorDynamicAttribute) AttrValue(name schema.FrameworkIdentifier) string { + if g.AssociatedExternalType != nil { + return fmt.Sprintf("%sValue", name.ToPascalCase()) + } + + return "basetypes.DynamicValue" +} diff --git a/internal/resource/convert.go b/internal/resource/convert.go index 41f69ce9..6ece5c86 100644 --- a/internal/resource/convert.go +++ b/internal/resource/convert.go @@ -86,6 +86,8 @@ func NewAttribute(a resource.Attribute) (generatorschema.GeneratorAttribute, err switch { case a.Bool != nil: return NewGeneratorBoolAttribute(a.Name, a.Bool) + case a.Dynamic != nil: + return NewGeneratorDynamicAttribute(a.Name, a.Dynamic) case a.Float64 != nil: return NewGeneratorFloat64Attribute(a.Name, a.Float64) case a.Int64 != nil: diff --git a/internal/resource/dynamic_attribute.go b/internal/resource/dynamic_attribute.go new file mode 100644 index 00000000..329656d9 --- /dev/null +++ b/internal/resource/dynamic_attribute.go @@ -0,0 +1,167 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "bytes" + "fmt" + + "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/convert" + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/model" + generatorschema "github.com/starburstdata/terraform-plugin-codegen-framework/internal/schema" +) + +type GeneratorDynamicAttribute struct { + AssociatedExternalType *generatorschema.AssocExtType + ComputedOptionalRequired convert.ComputedOptionalRequired + CustomType convert.CustomTypePrimitive + DeprecationMessage convert.DeprecationMessage + Description convert.Description + PlanModifiers convert.PlanModifiers + Sensitive convert.Sensitive + Validators convert.Validators +} + +func NewGeneratorDynamicAttribute(name string, a *resource.DynamicAttribute) (GeneratorDynamicAttribute, error) { + if a == nil { + return GeneratorDynamicAttribute{}, fmt.Errorf("*resource.DynamicAttribute is nil") + } + + c := convert.NewComputedOptionalRequired(a.ComputedOptionalRequired) + + ctp := convert.NewCustomTypePrimitive(a.CustomType, a.AssociatedExternalType, name) + + dm := convert.NewDeprecationMessage(a.DeprecationMessage) + + d := convert.NewDescription(a.Description) + + pm := convert.NewPlanModifiers(convert.PlanModifierTypeDynamic, a.PlanModifiers.CustomPlanModifiers()) + + s := convert.NewSensitive(a.Sensitive) + + v := convert.NewValidators(convert.ValidatorTypeDynamic, a.Validators.CustomValidators()) + + return GeneratorDynamicAttribute{ + AssociatedExternalType: generatorschema.NewAssocExtType(a.AssociatedExternalType), + ComputedOptionalRequired: c, + CustomType: ctp, + DeprecationMessage: dm, + Description: d, + PlanModifiers: pm, + Sensitive: s, + Validators: v, + }, nil +} + +func (g GeneratorDynamicAttribute) GeneratorSchemaType() generatorschema.Type { + return generatorschema.GeneratorDynamicAttribute +} + +func (g GeneratorDynamicAttribute) Imports() *generatorschema.Imports { + imports := generatorschema.NewImports() + + imports.Append(g.CustomType.Imports()) + + imports.Append(g.PlanModifiers.Imports()) + + imports.Append(g.Validators.Imports()) + + if g.AssociatedExternalType != nil { + imports.Append(generatorschema.AssociatedExternalTypeImports()) + } + + imports.Append(g.AssociatedExternalType.Imports()) + + return imports +} + +func (g GeneratorDynamicAttribute) Equal(ga generatorschema.GeneratorAttribute) bool { + h, ok := ga.(GeneratorDynamicAttribute) + + if !ok { + return false + } + + if !g.AssociatedExternalType.Equal(h.AssociatedExternalType) { + return false + } + + if !g.ComputedOptionalRequired.Equal(h.ComputedOptionalRequired) { + return false + } + + if !g.CustomType.Equal(h.CustomType) { + return false + } + + if !g.DeprecationMessage.Equal(h.DeprecationMessage) { + return false + } + + if !g.Description.Equal(h.Description) { + return false + } + + if !g.PlanModifiers.Equal(h.PlanModifiers) { + return false + } + + if !g.Sensitive.Equal(h.Sensitive) { + return false + } + + return g.Validators.Equal(h.Validators) +} + +func (g GeneratorDynamicAttribute) Schema(name generatorschema.FrameworkIdentifier) (string, error) { + var b bytes.Buffer + + b.WriteString(fmt.Sprintf("%q: schema.DynamicAttribute{\n", name)) + b.Write(g.CustomType.Schema()) + b.Write(g.ComputedOptionalRequired.Schema()) + b.Write(g.Sensitive.Schema()) + b.Write(g.Description.Schema()) + b.Write(g.DeprecationMessage.Schema()) + b.Write(g.PlanModifiers.Schema()) + b.Write(g.Validators.Schema()) + b.WriteString("},") + + return b.String(), nil +} + +func (g GeneratorDynamicAttribute) ModelField(name generatorschema.FrameworkIdentifier) (model.Field, error) { + field := model.Field{ + Name: name.ToPascalCase(), + TfsdkName: name.ToString(), + ValueType: model.DynamicValueType, + } + + customValueType := g.CustomType.ValueType() + + if customValueType != "" { + field.ValueType = customValueType + } + + return field, nil +} + +// AttrType returns a string representation of a basetypes.DynamicTypable type. +func (g GeneratorDynamicAttribute) AttrType(name generatorschema.FrameworkIdentifier) (string, error) { + if g.AssociatedExternalType != nil { + return fmt.Sprintf("%sType{}", name.ToPascalCase()), nil + } + + return "basetypes.DynamicType{}", nil +} + +// AttrValue returns a string representation of a basetypes.DynamicValuable type. +func (g GeneratorDynamicAttribute) AttrValue(name generatorschema.FrameworkIdentifier) string { + if g.AssociatedExternalType != nil { + return fmt.Sprintf("%sValue", name.ToPascalCase()) + } + + return "basetypes.DynamicValue" +} diff --git a/internal/resource/dynamic_attribute_test.go b/internal/resource/dynamic_attribute_test.go new file mode 100644 index 00000000..becff0e6 --- /dev/null +++ b/internal/resource/dynamic_attribute_test.go @@ -0,0 +1,207 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-codegen-spec/resource" + specschema "github.com/hashicorp/terraform-plugin-codegen-spec/schema" + + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/convert" + "github.com/starburstdata/terraform-plugin-codegen-framework/internal/model" +) + +func TestGeneratorDynamicAttribute_New(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input *resource.DynamicAttribute + expected GeneratorDynamicAttribute + expectedError error + }{ + "nil": { + input: nil, + expectedError: nil, + }, + "computed": { + input: &resource.DynamicAttribute{ + ComputedOptionalRequired: "computed", + }, + expected: GeneratorDynamicAttribute{ + ComputedOptionalRequired: convert.NewComputedOptionalRequired(specschema.Computed), + CustomType: convert.NewCustomTypePrimitive(nil, nil, "name"), + DeprecationMessage: convert.NewDeprecationMessage(nil), + Description: convert.NewDescription(nil), + PlanModifiers: convert.NewPlanModifiers(convert.PlanModifierTypeDynamic, nil), + Sensitive: convert.NewSensitive(nil), + Validators: convert.NewValidators(convert.ValidatorTypeDynamic, nil), + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := NewGeneratorDynamicAttribute("name", testCase.input) + + if testCase.input == nil { + if err == nil { + t.Fatalf("expected error for nil input, got nil") + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestGeneratorDynamicAttribute_Schema(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input GeneratorDynamicAttribute + expected string + expectedError error + }{ + "computed": { + input: GeneratorDynamicAttribute{ + ComputedOptionalRequired: convert.NewComputedOptionalRequired(specschema.Computed), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +Computed: true, +},`, + }, + "optional": { + input: GeneratorDynamicAttribute{ + ComputedOptionalRequired: convert.NewComputedOptionalRequired(specschema.Optional), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +Optional: true, +},`, + }, + "required": { + input: GeneratorDynamicAttribute{ + ComputedOptionalRequired: convert.NewComputedOptionalRequired(specschema.Required), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +Required: true, +},`, + }, + "sensitive": { + input: GeneratorDynamicAttribute{ + Sensitive: convert.NewSensitive(pointer(true)), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +Sensitive: true, +},`, + }, + "description": { + input: GeneratorDynamicAttribute{ + Description: convert.NewDescription(pointer("description")), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +Description: "description", +MarkdownDescription: "description", +},`, + }, + "deprecation-message": { + input: GeneratorDynamicAttribute{ + DeprecationMessage: convert.NewDeprecationMessage(pointer("deprecated")), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +DeprecationMessage: "deprecated", +},`, + }, + "custom-type": { + input: GeneratorDynamicAttribute{ + CustomType: convert.NewCustomTypePrimitive( + &specschema.CustomType{ + Type: "my_custom_type", + }, + nil, + "dynamic_attribute", + ), + }, + expected: `"dynamic_attribute": schema.DynamicAttribute{ +CustomType: my_custom_type, +},`, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.input.Schema("dynamic_attribute") + + 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 TestGeneratorDynamicAttribute_ModelField(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input GeneratorDynamicAttribute + expected model.Field + expectedError error + }{ + "default": { + expected: model.Field{ + Name: "DynamicAttribute", + ValueType: "types.Dynamic", + TfsdkName: "dynamic_attribute", + }, + }, + "custom-type": { + input: GeneratorDynamicAttribute{ + CustomType: convert.NewCustomTypePrimitive( + &specschema.CustomType{ + ValueType: "my_custom_value_type", + }, + nil, + "", + ), + }, + expected: model.Field{ + Name: "DynamicAttribute", + ValueType: "my_custom_value_type", + TfsdkName: "dynamic_attribute", + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.input.ModelField("dynamic_attribute") + + 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/types.go b/internal/schema/types.go index ea128255..cc2bf86c 100644 --- a/internal/schema/types.go +++ b/internal/schema/types.go @@ -95,6 +95,7 @@ type Type int64 const ( InvalidGeneratorSchemaType Type = iota GeneratorBoolAttribute + GeneratorDynamicAttribute GeneratorFloat64Attribute GeneratorInt64Attribute GeneratorListAttribute