Skip to content

Commit f5b03ab

Browse files
committed
schema: Proof of concept Schema and Attribute validation
Reference: #17 Reference: #65 Note that this code as written introduces an import cycle between tfsdk and schema packages as they need to cross-reference types. Proposal pull requests will be submitted to migrate certain package functionality between packages to alleviate the issue.
1 parent dacb915 commit f5b03ab

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

schema/attribute.go

+64
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package schema
22

33
import (
4+
"context"
45
"errors"
56

67
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
79
"github.com/hashicorp/terraform-plugin-go/tftypes"
810
)
911

@@ -64,6 +66,9 @@ type Attribute struct {
6466
// using this attribute, warning them that it is deprecated and
6567
// instructing them on what upgrade steps to take.
6668
DeprecationMessage string
69+
70+
// Validators defines validation functionality for the attribute.
71+
Validators []AttributeValidator
6772
}
6873

6974
// ApplyTerraform5AttributePathStep transparently calls
@@ -119,3 +124,62 @@ func (a Attribute) Equal(o Attribute) bool {
119124
}
120125
return true
121126
}
127+
128+
func (a Attribute) Validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) {
129+
if (a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0) && a.Type == nil {
130+
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
131+
Severity: tfprotov6.DiagnosticSeverityError,
132+
Summary: "Invalid Attribute Definition",
133+
Detail: "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.",
134+
Attribute: req.AttributePath,
135+
})
136+
137+
return
138+
}
139+
140+
if a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 && a.Type != nil {
141+
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
142+
Severity: tfprotov6.DiagnosticSeverityError,
143+
Summary: "Invalid Attribute Definition",
144+
Detail: "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.",
145+
Attribute: req.AttributePath,
146+
})
147+
148+
return
149+
}
150+
151+
attributeConfig, err := req.Config.GetAttribute(ctx, req.AttributePath)
152+
153+
if err != nil {
154+
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
155+
Severity: tfprotov6.DiagnosticSeverityError,
156+
Summary: "Attribute Value Error",
157+
Detail: "Attribute validation cannot read configuration value. Report this to the provider developer:\n\n" + err.Error(),
158+
Attribute: req.AttributePath,
159+
})
160+
161+
return
162+
}
163+
164+
req.AttributeConfig = attributeConfig
165+
166+
for _, validator := range a.Validators {
167+
validator.Validate(ctx, req, resp)
168+
}
169+
170+
if a.Attributes != nil {
171+
for nestedName, nestedAttr := range a.Attributes.GetAttributes() {
172+
nestedAttrReq := ValidateAttributeRequest{
173+
AttributePath: req.AttributePath.WithAttributeName(nestedName),
174+
Config: req.Config,
175+
}
176+
nestedAttrResp := &ValidateAttributeResponse{}
177+
178+
nestedAttr.Validate(ctx, nestedAttrReq, nestedAttrResp)
179+
180+
resp.Diagnostics = append(resp.Diagnostics, nestedAttrResp.Diagnostics...)
181+
}
182+
}
183+
184+
return
185+
}

schema/attribute_validation.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package schema
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/attr"
7+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
8+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
9+
"github.com/hashicorp/terraform-plugin-go/tftypes"
10+
)
11+
12+
// AttributeValidator describes reusable Attribute validation functionality.
13+
type AttributeValidator interface {
14+
// Description describes the validation in plain text formatting.
15+
Description(context.Context) string
16+
17+
// MarkdownDescription describes the validation in Markdown formatting.
18+
MarkdownDescription(context.Context) string
19+
20+
// Validate performs the validation.
21+
Validate(context.Context, ValidateAttributeRequest, *ValidateAttributeResponse)
22+
}
23+
24+
// ValidateAttributeRequest repesents a request for
25+
type ValidateAttributeRequest struct {
26+
// AttributePath contains the path of the attribute.
27+
AttributePath *tftypes.AttributePath
28+
29+
// AttributeConfig contains the value of the attribute in the configuration.
30+
AttributeConfig attr.Value
31+
32+
// Config contains the entire configuration of the data source, provider, or resource.
33+
Config tfsdk.Config
34+
}
35+
36+
// ValidateAttributeResponse represents a response to a
37+
// ValidateAttributeRequest. An instance of this response struct is
38+
// automatically passed through to each AttributeValidator.
39+
type ValidateAttributeResponse struct {
40+
// Diagnostics report errors or warnings related to validating the data
41+
// source configuration. An empty slice indicates success, with no warnings
42+
// or errors generated.
43+
Diagnostics []*tfprotov6.Diagnostic
44+
}

schema/schema.go

+16
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,19 @@ func (s Schema) AttributeAtPath(path *tftypes.AttributePath) (Attribute, error)
126126
}
127127
return a, nil
128128
}
129+
130+
func (s Schema) Validate(ctx context.Context, req ValidateSchemaRequest, resp *ValidateSchemaResponse) {
131+
for name, attribute := range s.Attributes {
132+
attributeReq := ValidateAttributeRequest{
133+
AttributePath: tftypes.NewAttributePath().WithAttributeName(name),
134+
Config: req.Config,
135+
}
136+
attributeResp := &ValidateAttributeResponse{}
137+
138+
attribute.Validate(ctx, attributeReq, attributeResp)
139+
140+
resp.Diagnostics = append(resp.Diagnostics, attributeResp.Diagnostics...)
141+
}
142+
143+
return
144+
}

schema/schema_validation.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package schema
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
5+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
6+
)
7+
8+
// ValidateSchemaRequest repesents a request for validating a Schema.
9+
type ValidateSchemaRequest struct {
10+
// Config contains the entire configuration of the data source, provider, or resource.
11+
Config tfsdk.Config
12+
}
13+
14+
// ValidateSchemaResponse represents a response to a
15+
// ValidateSchemaRequest.
16+
type ValidateSchemaResponse struct {
17+
// Diagnostics report errors or warnings related to validating the data
18+
// source configuration. An empty slice indicates success, with no warnings
19+
// or errors generated.
20+
Diagnostics []*tfprotov6.Diagnostic
21+
}

0 commit comments

Comments
 (0)