Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions schema/attribute.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package schema

import (
"context"
"errors"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

Expand Down Expand Up @@ -64,6 +66,9 @@ type Attribute struct {
// using this attribute, warning them that it is deprecated and
// instructing them on what upgrade steps to take.
DeprecationMessage string

// Validators defines validation functionality for the attribute.
Validators []AttributeValidator
}

// ApplyTerraform5AttributePathStep transparently calls
Expand Down Expand Up @@ -119,3 +124,62 @@ func (a Attribute) Equal(o Attribute) bool {
}
return true
}

func (a Attribute) Validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) {
if (a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0) && a.Type == nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Invalid Attribute Definition",
Detail: "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.",
Attribute: req.AttributePath,
})

return
}

if a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 && a.Type != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Invalid Attribute Definition",
Detail: "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.",
Attribute: req.AttributePath,
})

return
}

attributeConfig, err := req.Config.GetAttribute(ctx, req.AttributePath)

if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Attribute Value Error",
Detail: "Attribute validation cannot read configuration value. Report this to the provider developer:\n\n" + err.Error(),
Attribute: req.AttributePath,
})

return
}

req.AttributeConfig = attributeConfig

for _, validator := range a.Validators {
validator.Validate(ctx, req, resp)
}

if a.Attributes != nil {
for nestedName, nestedAttr := range a.Attributes.GetAttributes() {
nestedAttrReq := ValidateAttributeRequest{
AttributePath: req.AttributePath.WithAttributeName(nestedName),
Config: req.Config,
}
nestedAttrResp := &ValidateAttributeResponse{}

nestedAttr.Validate(ctx, nestedAttrReq, nestedAttrResp)

resp.Diagnostics = append(resp.Diagnostics, nestedAttrResp.Diagnostics...)
}
}

return
}
44 changes: 44 additions & 0 deletions schema/attribute_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package schema

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// AttributeValidator describes reusable Attribute validation functionality.
type AttributeValidator interface {
// Description describes the validation in plain text formatting.
Description(context.Context) string

// MarkdownDescription describes the validation in Markdown formatting.
MarkdownDescription(context.Context) string

// Validate performs the validation.
Validate(context.Context, ValidateAttributeRequest, *ValidateAttributeResponse)
}

// ValidateAttributeRequest repesents a request for
type ValidateAttributeRequest struct {
// AttributePath contains the path of the attribute.
AttributePath *tftypes.AttributePath

// AttributeConfig contains the value of the attribute in the configuration.
AttributeConfig attr.Value

// Config contains the entire configuration of the data source, provider, or resource.
Config tfsdk.Config
}

// ValidateAttributeResponse represents a response to a
// ValidateAttributeRequest. An instance of this response struct is
// automatically passed through to each AttributeValidator.
type ValidateAttributeResponse struct {
// Diagnostics report errors or warnings related to validating the data
// source configuration. An empty slice indicates success, with no warnings
// or errors generated.
Diagnostics []*tfprotov6.Diagnostic
}
16 changes: 16 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,19 @@ func (s Schema) AttributeAtPath(path *tftypes.AttributePath) (Attribute, error)
}
return a, nil
}

func (s Schema) Validate(ctx context.Context, req ValidateSchemaRequest, resp *ValidateSchemaResponse) {
for name, attribute := range s.Attributes {
attributeReq := ValidateAttributeRequest{
AttributePath: tftypes.NewAttributePath().WithAttributeName(name),
Config: req.Config,
}
attributeResp := &ValidateAttributeResponse{}

attribute.Validate(ctx, attributeReq, attributeResp)

resp.Diagnostics = append(resp.Diagnostics, attributeResp.Diagnostics...)
}

return
}
21 changes: 21 additions & 0 deletions schema/schema_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package schema

import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)

// ValidateSchemaRequest repesents a request for validating a Schema.
type ValidateSchemaRequest struct {
// Config contains the entire configuration of the data source, provider, or resource.
Config tfsdk.Config
}

// ValidateSchemaResponse represents a response to a
// ValidateSchemaRequest.
type ValidateSchemaResponse struct {
// Diagnostics report errors or warnings related to validating the data
// source configuration. An empty slice indicates success, with no warnings
// or errors generated.
Diagnostics []*tfprotov6.Diagnostic
}