A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://developer.hashicorp.com/terraform/plugin/framework/handling-data/types/custom below:

Custom types | Terraform | HashiCorp Developer

Use existing custom types or develop custom types to consistently define behaviors for a kind of value across schemas. Custom types are supported on top of any framework-defined type.

Supported behaviors for custom types include:

The following Go modules contain custom type implementations covering common use cases with validation and semantic equality logic (where appropriate).

Individual data value handling in the framework is performed by a pair of associated Go types:

The framework defines a standard set these associated Go types referred to by the "base type" terminology. Extending these base types is referred to by the "custom type" terminology.

Use a custom type by switching the schema definition and data handling from a framework-defined type to the custom type.

Schema Definition

The framework schema types accept a CustomType field where applicable, such as the resource/schema.StringAttribute type. When the CustomType is omitted, the framework defaults to the associated base type.

Implement the CustomType field in a schema type to switch from the base type to a custom type.

In this example, a string attribute implements a custom type.

schema.StringAttribute{
    CustomType: CustomStringType{},
    // ... other fields ...
}
Data Handling

Each custom type will also include a value type, which must be used anywhere the value is referenced in data source, provider, or resource logic.

Switch any usage of a base value type to the custom value type. Any logic will need to be updated to match the custom value type implementation.

In this example, a custom value type is used in a data model approach:

type ThingModel struct {
    // Instead of types.String
    Timestamp CustomStringValue `tfsdk:"timestamp"`
    // ... other fields ...
}

Create a custom type by extending an existing framework schema type and its associated value type. Once created, define semantic equality and/or validation logic for the custom type.

Schema Type

Extend a framework schema type by creating a Go type that implements one of the github.com/hashicorp/terraform-plugin-framework/types/basetypes package *Typable interfaces.

Tip

The commonly used types package types are aliases to the basetypes package types mentioned in this table.

It is recommended to use Go type embedding of the base type to simplify the implementation and ensure it is up to date with the latest data handling features of the framework. With type embedding, the following attr.Type methods must be overridden by the custom type to prevent confusing errors:

In this example, the basetypes.StringTypable interface is implemented to create a custom string type with an associated value type:

import (
    "context"
    "fmt"

    "github.com/hashicorp/terraform-plugin-framework/attr"
    "github.com/hashicorp/terraform-plugin-framework/diag"
    "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
    "github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringTypable = CustomStringType{}

type CustomStringType struct {
    basetypes.StringType
    // ... potentially other fields ...
}

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

    if !ok {
        return false
    }

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

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

func (t CustomStringType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
    // CustomStringValue defined in the value type section
    value := CustomStringValue{
        StringValue: in,
    }

    return value, nil
}

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

    if err != nil {
        return nil, err
    }

    stringValue, ok := attrValue.(basetypes.StringValue)

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

    stringValuable, diags := t.ValueFromString(ctx, stringValue)

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

    return stringValuable, nil
}

func (t CustomStringType) ValueType(ctx context.Context) attr.Value {
    // CustomStringValue defined in the value type section
    return CustomStringValue{}
}
Value Type

Extend a framework value type by creating a Go type that implements one of the github.com/hashicorp/terraform-plugin-framework/types/basetypes package *Valuable interfaces.

Tip

The commonly used types package types are aliases to the basetypes package types mentioned in this table.

It is recommended to use Go type embedding of the base type to simplify the implementation and ensure it is up to date with the latest data handling features of the framework. With type embedding, the following attr.Value methods must be overridden by the custom type to prevent confusing errors:

Note

The overridden Equal(attr.Value) bool method should not contain Semantic Equality logic. Equal should only check the type of attr.Value and the underlying base value.

An example of this can be found below with the CustomStringValue implementation.

In this example, the basetypes.StringValuable interface is implemented to create a custom string value type with an associated schema type:

import (
    "context"
    "fmt"

    "github.com/hashicorp/terraform-plugin-framework/attr"
    "github.com/hashicorp/terraform-plugin-framework/diag"
    "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
    "github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringValuable = CustomStringValue{}

type CustomStringValue struct {
    basetypes.StringValue
    // ... potentially other fields ...
}

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

    if !ok {
        return false
    }

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

func (v CustomStringValue) Type(ctx context.Context) attr.Type {
    // CustomStringType defined in the schema type section
    return CustomStringType{}
}

From this point, the custom type can be extended with other behaviors.

Semantic Equality

Semantic equality handling enables the value type to automatically keep a prior value when a new value is determined to be inconsequentially different. This handling can prevent unexpected drift detection for values and in some cases prevent Terraform data handling errors.

This value type functionality is checked in the following scenarios:

The framework will only call semantic equality logic if both the prior and new values are known. Null or unknown values are unnecessary to check. When working with collection types, the framework automatically calls semantic equality logic of element types. When working with object types, the framework automatically calls semantic equality of underlying attribute types.

Implement the associated github.com/hashicorp/terraform-plugin-framework/types/basetypes package *ValuableWithSemanticEquals interface on the value type to define and enable this behavior.

In this example, the custom string value type will preserve the prior value if the expected RFC3339 timestamps are considered equivalent:

// CustomStringValue defined in the value type section
// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringValuableWithSemanticEquals = CustomStringValue{}

func (v CustomStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
    var diags diag.Diagnostics

    // The framework should always pass the correct value type, but always check
    newValue, ok := newValuable.(CustomStringValue)

    if !ok {
        diags.AddError(
            "Semantic Equality Check Error",
            "An unexpected value type was received while performing semantic equality checks. "+
            "Please report this to the provider developers.\n\n"+
            "Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+
            "Got Value Type: "+fmt.Sprintf("%T", newValuable),
        )

        return false, diags
    }

    // Skipping error checking if CustomStringValue already implemented RFC3339 validation
    priorTime, _ := time.Parse(time.RFC3339, v.StringValue.ValueString())

    // Skipping error checking if CustomStringValue already implemented RFC3339 validation
    newTime, _ := time.Parse(time.RFC3339, newValue.ValueString())

    // If the times are equivalent, keep the prior value
    return priorTime.Equal(newTime), diags
}
Validation Value Validation

Validation handling in custom value types can be enabled for schema attribute values, or provider-defined function parameters.

Implement the xattr.ValidateableAttribute interface on the custom value type to define and enable validation handling for a schema attribute, which will automatically raise warning and/or error diagnostics when a value is determined to be invalid.

Implement the function.ValidateableParameter interface on the custom value type to define and enable validation handling for a provider-defined function parameter, which will automatically raise an error when a value is determined to be invalid.

If the custom value type is to be used for both schema attribute values and provider-defined function parameters, implement both interfaces.

// Implementation of the xattr.ValidateableAttribute interface
func (v CustomStringValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) {
    if v.IsNull() || v.IsUnknown() {
        return
    }

    err := v.validate(v.ValueString())

    if err != nil {
        resp.Diagnostics.AddAttributeError(
            req.Path,
            "Invalid RFC 3339 String Value",
            "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+
                "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+
                "Path: "+req.Path.String()+"\n"+
                "Given Value: "+v.ValueString()+"\n"+
                "Error: "+err.Error(),
        )

        return
    }
}

// Implementation of the function.ValidateableParameter interface
func (v CustomStringValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) {
    if v.IsNull() || v.IsUnknown() {
        return
    }

    err := v.validate(v.ValueString())

    if err != nil {
        resp.Error = function.NewArgumentFuncError(
            req.Position,
            "Invalid RFC 3339 String Value: "+
                "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+
                "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+
                fmt.Sprintf("Position: %d", req.Position)+"\n"+
                "Given Value: "+v.ValueString()+"\n"+
                "Error: "+err.Error(),
        )
    }
}

func (v CustomStringValue) validate(in string) error {
    _, err := time.Parse(time.RFC3339, in)

    return err
}
Type Validation

Implement the xattr.TypeWithValidate interface on the value type to define and enable this behavior.

Note

This functionality uses the lower level tftypes type system compared to other framework logic.

In this example, the custom string value type will ensure the string is a valid RFC3339 timestamp:

// CustomStringType defined in the schema type section
func (t CustomStringType) Validate(ctx context.Context, value tftypes.Value, valuePath path.Path) diag.Diagnostics {
    if value.IsNull() || !value.IsKnown() {
        return nil
    }

    var diags diag.Diagnostics
    var valueString string

    if err := value.As(&valueString); err != nil {
        diags.AddAttributeError(
            valuePath,
            "Invalid Terraform Value",
            "An unexpected error occurred while attempting to convert a Terraform value to a string. "+
                "This generally is an issue with the provider schema implementation. "+
                "Please contact the provider developers.\n\n"+
                "Path: "+valuePath.String()+"\n"+
                "Error: "+err.Error(),
        )

        return diags
    }

    if _, err := time.Parse(time.RFC3339, valueString); err != nil {
        diags.AddAttributeError(
            valuePath,
            "Invalid RFC 3339 String Value",
            "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+
                "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+
                "Path: "+valuePath.String()+"\n"+
                "Given Value: "+valueString+"\n"+
                "Error: "+err.Error(),
        )

        return diags
    }

    return diags
}

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4