Skip to content

Commit aedbdc5

Browse files
committed
add validators for list resource configs
1 parent 3c750ab commit aedbdc5

22 files changed

+1447
-0
lines changed

listresourcevalidator/all.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listresourcevalidator
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"strings"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/list"
12+
)
13+
14+
// All returns a validator which ensures that any configured attribute value
15+
// validates against all the given validators.
16+
//
17+
// Use of All is only necessary when used in conjunction with Any or AnyWithAllWarnings
18+
// as the Validators field automatically applies a logical AND.
19+
func All(validators ...list.ConfigValidator) list.ConfigValidator {
20+
return allValidator{
21+
validators: validators,
22+
}
23+
}
24+
25+
var _ list.ConfigValidator = allValidator{}
26+
27+
// allValidator implements the validator.
28+
type allValidator struct {
29+
validators []list.ConfigValidator
30+
}
31+
32+
// Description describes the validation in plain text formatting.
33+
func (v allValidator) Description(ctx context.Context) string {
34+
var descriptions []string
35+
36+
for _, subValidator := range v.validators {
37+
descriptions = append(descriptions, subValidator.Description(ctx))
38+
}
39+
40+
return fmt.Sprintf("Value must satisfy all of the validations: %s", strings.Join(descriptions, " + "))
41+
}
42+
43+
// MarkdownDescription describes the validation in Markdown formatting.
44+
func (v allValidator) MarkdownDescription(ctx context.Context) string {
45+
return v.Description(ctx)
46+
}
47+
48+
// ValidateListResourceConfig performs the validation.
49+
func (v allValidator) ValidateListResourceConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
50+
for _, subValidator := range v.validators {
51+
validateResp := &list.ValidateConfigResponse{}
52+
53+
subValidator.ValidateListResourceConfig(ctx, req, validateResp)
54+
55+
resp.Diagnostics.Append(validateResp.Diagnostics...)
56+
}
57+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listresourcevalidator_test
5+
6+
import (
7+
"github.com/hashicorp/terraform-plugin-framework-validators/listresourcevalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/list"
9+
)
10+
11+
func ExampleAll() {
12+
// Used inside a list.ListResource type ConfigValidators method
13+
_ = []list.ConfigValidator{
14+
// The configuration must satisfy either All validator.
15+
listresourcevalidator.Any(
16+
listresourcevalidator.All( /* ... */ ),
17+
listresourcevalidator.All( /* ... */ ),
18+
),
19+
}
20+
}

listresourcevalidator/all_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listresourcevalidator_test
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-framework-validators/listresourcevalidator"
12+
"github.com/hashicorp/terraform-plugin-framework/diag"
13+
"github.com/hashicorp/terraform-plugin-framework/list"
14+
"github.com/hashicorp/terraform-plugin-framework/list/schema"
15+
"github.com/hashicorp/terraform-plugin-framework/path"
16+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
17+
"github.com/hashicorp/terraform-plugin-go/tftypes"
18+
)
19+
20+
func TestAllValidatorValidateListResource(t *testing.T) {
21+
t.Parallel()
22+
23+
testCases := map[string]struct {
24+
validators []list.ConfigValidator
25+
req list.ValidateConfigRequest
26+
expected *list.ValidateConfigResponse
27+
}{
28+
"no-diagnostics": {
29+
validators: []list.ConfigValidator{
30+
listresourcevalidator.ExactlyOneOf(
31+
path.MatchRoot("test1"),
32+
path.MatchRoot("test2"),
33+
),
34+
listresourcevalidator.All(
35+
listresourcevalidator.AtLeastOneOf(
36+
path.MatchRoot("test3"),
37+
path.MatchRoot("test4"),
38+
),
39+
listresourcevalidator.Conflicting(
40+
path.MatchRoot("test3"),
41+
path.MatchRoot("test5"),
42+
),
43+
),
44+
},
45+
req: list.ValidateConfigRequest{
46+
Config: tfsdk.Config{
47+
Schema: schema.Schema{
48+
Attributes: map[string]schema.Attribute{
49+
"test1": schema.StringAttribute{
50+
Optional: true,
51+
},
52+
"test2": schema.StringAttribute{
53+
Optional: true,
54+
},
55+
"test3": schema.StringAttribute{
56+
Optional: true,
57+
},
58+
"test4": schema.StringAttribute{
59+
Optional: true,
60+
},
61+
"test5": schema.StringAttribute{
62+
Optional: true,
63+
},
64+
},
65+
},
66+
Raw: tftypes.NewValue(
67+
tftypes.Object{
68+
AttributeTypes: map[string]tftypes.Type{
69+
"test1": tftypes.String,
70+
"test2": tftypes.String,
71+
"test3": tftypes.String,
72+
"test4": tftypes.String,
73+
"test5": tftypes.String,
74+
},
75+
},
76+
map[string]tftypes.Value{
77+
"test1": tftypes.NewValue(tftypes.String, nil),
78+
"test2": tftypes.NewValue(tftypes.String, nil),
79+
"test3": tftypes.NewValue(tftypes.String, "test-value"),
80+
"test4": tftypes.NewValue(tftypes.String, nil),
81+
"test5": tftypes.NewValue(tftypes.String, nil),
82+
},
83+
),
84+
},
85+
},
86+
expected: &list.ValidateConfigResponse{},
87+
},
88+
"diagnostics": {
89+
validators: []list.ConfigValidator{
90+
listresourcevalidator.ExactlyOneOf(
91+
path.MatchRoot("test1"),
92+
path.MatchRoot("test2"),
93+
),
94+
listresourcevalidator.All(
95+
listresourcevalidator.AtLeastOneOf(
96+
path.MatchRoot("test3"),
97+
path.MatchRoot("test4"),
98+
),
99+
listresourcevalidator.Conflicting(
100+
path.MatchRoot("test3"),
101+
path.MatchRoot("test5"),
102+
),
103+
),
104+
},
105+
req: list.ValidateConfigRequest{
106+
Config: tfsdk.Config{
107+
Schema: schema.Schema{
108+
Attributes: map[string]schema.Attribute{
109+
"test1": schema.StringAttribute{
110+
Optional: true,
111+
},
112+
"test2": schema.StringAttribute{
113+
Optional: true,
114+
},
115+
"test3": schema.StringAttribute{
116+
Optional: true,
117+
},
118+
"test4": schema.StringAttribute{
119+
Optional: true,
120+
},
121+
"test5": schema.StringAttribute{
122+
Optional: true,
123+
},
124+
},
125+
},
126+
Raw: tftypes.NewValue(
127+
tftypes.Object{
128+
AttributeTypes: map[string]tftypes.Type{
129+
"test1": tftypes.String,
130+
"test2": tftypes.String,
131+
"test3": tftypes.String,
132+
"test4": tftypes.String,
133+
"test5": tftypes.String,
134+
},
135+
},
136+
map[string]tftypes.Value{
137+
"test1": tftypes.NewValue(tftypes.String, nil),
138+
"test2": tftypes.NewValue(tftypes.String, nil),
139+
"test3": tftypes.NewValue(tftypes.String, "test-value"),
140+
"test4": tftypes.NewValue(tftypes.String, nil),
141+
"test5": tftypes.NewValue(tftypes.String, "test-value"),
142+
},
143+
),
144+
},
145+
},
146+
expected: &list.ValidateConfigResponse{
147+
Diagnostics: diag.Diagnostics{
148+
diag.NewErrorDiagnostic(
149+
"Missing Attribute Configuration",
150+
"Exactly one of these attributes must be configured: [test1,test2]",
151+
),
152+
diag.WithPath(path.Root("test3"),
153+
diag.NewErrorDiagnostic(
154+
"Invalid Attribute Combination",
155+
"These attributes cannot be configured together: [test3,test5]",
156+
)),
157+
},
158+
},
159+
},
160+
}
161+
162+
for name, testCase := range testCases {
163+
164+
t.Run(name, func(t *testing.T) {
165+
t.Parallel()
166+
167+
got := &list.ValidateConfigResponse{}
168+
169+
listresourcevalidator.Any(testCase.validators...).ValidateListResourceConfig(context.Background(), testCase.req, got)
170+
171+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
172+
t.Errorf("unexpected difference: %s", diff)
173+
}
174+
})
175+
}
176+
}

listresourcevalidator/any.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listresourcevalidator
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"github.com/hashicorp/terraform-plugin-framework/list"
10+
"strings"
11+
)
12+
13+
// Any returns a validator which ensures that any configured attribute value
14+
// passes at least one of the given validators.
15+
//
16+
// To prevent practitioner confusion should non-passing validators have
17+
// conflicting logic, only warnings from the passing validator are returned.
18+
// Use AnyWithAllWarnings() to return warnings from non-passing validators
19+
// as well.
20+
func Any(validators ...list.ConfigValidator) list.ConfigValidator {
21+
return anyValidator{
22+
validators: validators,
23+
}
24+
}
25+
26+
var _ list.ConfigValidator = anyValidator{}
27+
28+
// anyValidator implements the validator.
29+
type anyValidator struct {
30+
validators []list.ConfigValidator
31+
}
32+
33+
// Description describes the validation in plain text formatting.
34+
func (v anyValidator) Description(ctx context.Context) string {
35+
var descriptions []string
36+
37+
for _, subValidator := range v.validators {
38+
descriptions = append(descriptions, subValidator.Description(ctx))
39+
}
40+
41+
return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + "))
42+
}
43+
44+
// MarkdownDescription describes the validation in Markdown formatting.
45+
func (v anyValidator) MarkdownDescription(ctx context.Context) string {
46+
return v.Description(ctx)
47+
}
48+
49+
// ValidateListResourceConfig performs the validation.
50+
func (v anyValidator) ValidateListResourceConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
51+
for _, subValidator := range v.validators {
52+
validateResp := &list.ValidateConfigResponse{}
53+
54+
subValidator.ValidateListResourceConfig(ctx, req, validateResp)
55+
56+
if !validateResp.Diagnostics.HasError() {
57+
resp.Diagnostics = validateResp.Diagnostics
58+
59+
return
60+
}
61+
62+
resp.Diagnostics.Append(validateResp.Diagnostics...)
63+
}
64+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listresourcevalidator_test
5+
6+
import (
7+
"github.com/hashicorp/terraform-plugin-framework-validators/listresourcevalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/list"
9+
)
10+
11+
func ExampleAny() {
12+
// Used inside a list.ListResource type ConfigValidators method
13+
_ = []list.ConfigValidator{
14+
listresourcevalidator.Any( /* ... */ ),
15+
}
16+
}

0 commit comments

Comments
 (0)