Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate custom type and value types and to/from methods for primitives #59

Merged
merged 23 commits into from
Oct 19, 2023

Conversation

bendbennett
Copy link
Contributor

@bendbennett bendbennett commented Oct 6, 2023

Closes: #70

This PR extends the generation of custom type and value types for primitives that have an associated external type, and generates the methods for converting to/from the associated external type.

At this point, the implementation only covers BoolAttribute for data sources, but will be extended to encompass all primitive attributes (Bool, Float64, Int64, Number & String), if we're agreed on the approach.

For example, given the following spec:

{
  "version": "0.1",
  "datasources": [
    {
      "name": "datasource",
      "schema": {
        "attributes": [
          {
            "name": "bool_attribute",
            "bool": {
              "associated_external_type": {
                "import": {
                  "path": "github.com/api"
                },
                "type": "*api.BoolAttribute"
              },
              "computed_optional_required": "required"
            }
          }
        ]
      }
    }
  ],
  "provider": {
    "name": "provider"
  }
}

The generated code is as follows:

var _ basetypes.BoolTypable = BoolAttributeType{}

type BoolAttributeType struct {
	basetypes.BoolType
}

func (t BoolAttributeType) Equal(o attr.Type) bool {
	other, ok := o.(BoolAttributeType)

	if !ok {
		return false
	}

	return t.BoolType.Equal(other.BoolType)
}

func (t BoolAttributeType) String() string {
	return "BoolAttributeType"
}

func (t BoolAttributeType) ValueFromBool(ctx context.Context, in basetypes.BoolValue) (basetypes.BoolValuable, diag.Diagnostics) {
	return BoolAttributeValue{
		BoolValue: in,
	}, nil
}

func (t BoolAttributeType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
	attrValue, err := t.BoolType.ValueFromTerraform(ctx, in)

	if err != nil {
		return nil, err
	}

	boolValue, ok := attrValue.(basetypes.BoolValue)

	if !ok {
		return nil, fmt.Errorf("unexpected value type of %T", attrValue)
	}

	boolValuable, diags := t.ValueFromBool(ctx, boolValue)

	if diags.HasError() {
		return nil, fmt.Errorf("unexpected error converting BoolValue to BoolValuable: %v", diags)
	}

	return boolValuable, nil
}

func (t BoolAttributeType) ValueType(ctx context.Context) attr.Value {
	return BoolAttributeValue{}
}

var _ basetypes.BoolValuable = BoolAttributeValue{}

type BoolAttributeValue struct {
	basetypes.BoolValue
}

func (v BoolAttributeValue) Equal(o attr.Value) bool {
	other, ok := o.(BoolAttributeValue)

	if !ok {
		return false
	}

	return v.BoolValue.Equal(other.BoolValue)
}

func (v BoolAttributeValue) Type(ctx context.Context) attr.Type {
	return BoolAttributeType{}
}

func (v BoolAttributeValue) ToApiBoolAttribute(ctx context.Context) (*api.BoolAttribute, diag.Diagnostics) {
	var diags diag.Diagnostics

	if v.IsNull() {
		return nil, diags
	}

	if v.IsUnknown() {
		diags.Append(diag.NewErrorDiagnostic(
			"BoolAttributeValue Value Is Unknown",
			`"BoolAttributeValue" is unknown.`,
		))

		return nil, diags
	}

	a := api.BoolAttribute(v.ValueBoolPointer())

	return &a, diags
}

func (v BoolAttributeValue) FromApiBoolAttribute(ctx context.Context, apiObject *api.BoolAttribute) (BoolAttributeValue, diag.Diagnostics) {
	var diags diag.Diagnostics

	if apiObject == nil {
		return BoolAttributeValue{
			types.BoolNull(),
		}, diags
	}

	return BoolAttributeValue{
		types.BoolPointerValue(*apiObject),
	}, diags
}

func DatasourceDataSourceSchema(ctx context.Context) schema.Schema {
	return schema.Schema{
		Attributes: map[string]schema.Attribute{
			"bool_attribute": schema.BoolAttribute{
				CustomType: BoolAttributeType{},
				Required: true,
			},
		},
	}
}

type DatasourceModel struct {
	BoolAttribute BoolAttributeValue `tfsdk:"bool_attribute"`
}

Outstanding Tasks

  • The generation of the schema needs to be modified to use the generated custom type.
  • The generation of the data model needs to be modified to use the generated custom value.
  • Primitive attributes, with associated external types, that are nested within list, map, set, single nested attributes, or list, set single nested blocks need to be handled so that the generated custom type or value are used appropriately in this context. This can be managed by replacing the placeholder methods that are currently on GeneratorAttributes and GeneratorBlocks (i.e., AttributeTypes() / BlockTypes(), AttrTypes(), AttrValues(), FromFuncs(), and ToFuncs()), with calls that delegate to the individual attribute or block.
    • This will be deferred to the next PR.

Dependency

@bendbennett bendbennett added the enhancement New feature or request label Oct 17, 2023
@bendbennett bendbennett marked this pull request as ready for review October 17, 2023 16:50
go.mod Outdated
@@ -4,7 +4,7 @@ go 1.20

require (
github.com/google/go-cmp v0.6.0
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.0
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1-0.20231017164028-264c2bd37f5f
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be updated to v0.2.0 once hashicorp/terraform-plugin-codegen-spec#55 is merged and the v0.2.0 release is cut.

Copy link
Member

@austinvalle austinvalle Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted about this earlier, but maybe we can wait until codegen-framework needs a v0.2.0 release and then re-assess how we want to bump codegen-spec. SHAs are fine I'm sure :shipit: 🧨

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, we'll stick with using SHAs until we want to release 👍

Copy link
Member

@austinvalle austinvalle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, would love to start looking at getting a more comprehensive e2e test once we get the rest of external types implemented 🚀 🚀

func (c CustomBoolType) renderEqual() ([]byte, error) {
var buf bytes.Buffer

t, err := template.New("").Parse(c.templates["equal"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

template.New("")

curious q: is there a reason to keep the template name empty? I don't fully grasp what it does, but never kept it empty lol

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a detailed explanation of template naming on an SO post. TLDR: If you don't want/need to refer to the template, the name doesn't really matter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! thanks for sharing 👍🏻

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the custom strings have tests as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted. Added

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, does string get tests for this too? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted. Added

@bendbennett bendbennett merged commit 99e04d2 into main Oct 19, 2023
4 checks passed
@bendbennett bendbennett deleted the assoc-ext-type-primitives branch October 19, 2023 13:01
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add code generation for primitive attributes with associated external type
2 participants