Skip to content

Commit fd9f0f4

Browse files
authored
listresourcevalidator: add new package with common validators for list resource configurations (#298)
* add ValidateListResourceConfig method to ConfigValidator * add ValidateListResourceConfig to WarningValidator * add validators for list resource configs * fix import ordering * update dependencies
1 parent 439e846 commit fd9f0f4

29 files changed

+1505
-25
lines changed

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ toolchain go1.23.7
66

77
require (
88
github.com/google/go-cmp v0.7.0
9-
github.com/hashicorp/terraform-plugin-framework v1.15.0
10-
github.com/hashicorp/terraform-plugin-go v0.28.0
9+
github.com/hashicorp/terraform-plugin-framework v1.15.1-0.20250721151353-59a937e815ac
10+
github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806
1111
)
1212

1313
require (
@@ -19,5 +19,5 @@ require (
1919
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
2020
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
2121
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
22-
golang.org/x/sys v0.32.0 // indirect
22+
golang.org/x/sys v0.33.0 // indirect
2323
)

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
77
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
88
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
99
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
10-
github.com/hashicorp/terraform-plugin-framework v1.15.0 h1:LQ2rsOfmDLxcn5EeIwdXFtr03FVsNktbbBci8cOKdb4=
11-
github.com/hashicorp/terraform-plugin-framework v1.15.0/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI=
12-
github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA=
13-
github.com/hashicorp/terraform-plugin-go v0.28.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
10+
github.com/hashicorp/terraform-plugin-framework v1.15.1-0.20250721151353-59a937e815ac h1:yJgGua9+kWvU3p/QeUShF5BV5L7YW+fEdhuWk90VdJw=
11+
github.com/hashicorp/terraform-plugin-framework v1.15.1-0.20250721151353-59a937e815ac/go.mod h1:1aeefX7ICeY56E8o1t9V0RSPa1DKkiwpPTihj8RfVRs=
12+
github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806 h1:i3kA1sT/Fk8Ex+VVKdjf9sFOPwS7w3Q73pfbnxKwdjg=
13+
github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY=
1414
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
1515
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
1616
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -37,8 +37,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
3737
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3838
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3939
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
40-
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
41-
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
40+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
41+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
4242
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4343
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4444
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/configvalidator/at_least_one_of.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/terraform-plugin-framework/datasource"
1212
"github.com/hashicorp/terraform-plugin-framework/diag"
1313
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
14+
"github.com/hashicorp/terraform-plugin-framework/list"
1415
"github.com/hashicorp/terraform-plugin-framework/path"
1516
"github.com/hashicorp/terraform-plugin-framework/provider"
1617
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -38,15 +39,19 @@ func (v AtLeastOneOfValidator) ValidateDataSource(ctx context.Context, req datas
3839
resp.Diagnostics = v.Validate(ctx, req.Config)
3940
}
4041

41-
func (v AtLeastOneOfValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
42+
func (v AtLeastOneOfValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
4243
resp.Diagnostics = v.Validate(ctx, req.Config)
4344
}
4445

45-
func (v AtLeastOneOfValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
46+
func (v AtLeastOneOfValidator) ValidateListResourceConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
4647
resp.Diagnostics = v.Validate(ctx, req.Config)
4748
}
4849

49-
func (v AtLeastOneOfValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
50+
func (v AtLeastOneOfValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
51+
resp.Diagnostics = v.Validate(ctx, req.Config)
52+
}
53+
54+
func (v AtLeastOneOfValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
5055
resp.Diagnostics = v.Validate(ctx, req.Config)
5156
}
5257

internal/configvalidator/conflicting.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform-plugin-framework/datasource"
1313
"github.com/hashicorp/terraform-plugin-framework/diag"
1414
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
15+
"github.com/hashicorp/terraform-plugin-framework/list"
1516
"github.com/hashicorp/terraform-plugin-framework/path"
1617
"github.com/hashicorp/terraform-plugin-framework/provider"
1718
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -39,15 +40,19 @@ func (v ConflictingValidator) ValidateDataSource(ctx context.Context, req dataso
3940
resp.Diagnostics = v.Validate(ctx, req.Config)
4041
}
4142

42-
func (v ConflictingValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
43+
func (v ConflictingValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
4344
resp.Diagnostics = v.Validate(ctx, req.Config)
4445
}
4546

46-
func (v ConflictingValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
47+
func (v ConflictingValidator) ValidateListResourceConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
4748
resp.Diagnostics = v.Validate(ctx, req.Config)
4849
}
4950

50-
func (v ConflictingValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
51+
func (v ConflictingValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
52+
resp.Diagnostics = v.Validate(ctx, req.Config)
53+
}
54+
55+
func (v ConflictingValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
5156
resp.Diagnostics = v.Validate(ctx, req.Config)
5257
}
5358

internal/configvalidator/exactly_one_of.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform-plugin-framework/datasource"
1313
"github.com/hashicorp/terraform-plugin-framework/diag"
1414
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
15+
"github.com/hashicorp/terraform-plugin-framework/list"
1516
"github.com/hashicorp/terraform-plugin-framework/path"
1617
"github.com/hashicorp/terraform-plugin-framework/provider"
1718
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -39,15 +40,19 @@ func (v ExactlyOneOfValidator) ValidateDataSource(ctx context.Context, req datas
3940
resp.Diagnostics = v.Validate(ctx, req.Config)
4041
}
4142

42-
func (v ExactlyOneOfValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
43+
func (v ExactlyOneOfValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
4344
resp.Diagnostics = v.Validate(ctx, req.Config)
4445
}
4546

46-
func (v ExactlyOneOfValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
47+
func (v ExactlyOneOfValidator) ValidateListResourceConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
4748
resp.Diagnostics = v.Validate(ctx, req.Config)
4849
}
4950

50-
func (v ExactlyOneOfValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
51+
func (v ExactlyOneOfValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
52+
resp.Diagnostics = v.Validate(ctx, req.Config)
53+
}
54+
55+
func (v ExactlyOneOfValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
5156
resp.Diagnostics = v.Validate(ctx, req.Config)
5257
}
5358

internal/configvalidator/required_together.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform-plugin-framework/datasource"
1313
"github.com/hashicorp/terraform-plugin-framework/diag"
1414
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
15+
"github.com/hashicorp/terraform-plugin-framework/list"
1516
"github.com/hashicorp/terraform-plugin-framework/path"
1617
"github.com/hashicorp/terraform-plugin-framework/provider"
1718
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -39,15 +40,19 @@ func (v RequiredTogetherValidator) ValidateDataSource(ctx context.Context, req d
3940
resp.Diagnostics = v.Validate(ctx, req.Config)
4041
}
4142

42-
func (v RequiredTogetherValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
43+
func (v RequiredTogetherValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
4344
resp.Diagnostics = v.Validate(ctx, req.Config)
4445
}
4546

46-
func (v RequiredTogetherValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
47+
func (v RequiredTogetherValidator) ValidateListResourceConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
4748
resp.Diagnostics = v.Validate(ctx, req.Config)
4849
}
4950

50-
func (v RequiredTogetherValidator) ValidateEphemeralResource(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) {
51+
func (v RequiredTogetherValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
52+
resp.Diagnostics = v.Validate(ctx, req.Config)
53+
}
54+
55+
func (v RequiredTogetherValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
5156
resp.Diagnostics = v.Validate(ctx, req.Config)
5257
}
5358

internal/testvalidator/warning.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-framework/datasource"
1010
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
11+
"github.com/hashicorp/terraform-plugin-framework/list"
1112
"github.com/hashicorp/terraform-plugin-framework/provider"
1213
"github.com/hashicorp/terraform-plugin-framework/resource"
1314
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@@ -69,6 +70,14 @@ func WarningList(summary string, detail string) validator.List {
6970
}
7071
}
7172

73+
// WarningListResourceConfig returns a validator which returns a warning diagnostic.
74+
func WarningListResourceConfig(summary string, detail string) list.ConfigValidator {
75+
return WarningValidator{
76+
Summary: summary,
77+
Detail: detail,
78+
}
79+
}
80+
7281
// WarningMap returns a validator which returns a warning diagnostic.
7382
func WarningMap(summary string, detail string) validator.Map {
7483
return WarningValidator{
@@ -171,6 +180,10 @@ func (v WarningValidator) ValidateDataSource(ctx context.Context, request dataso
171180
response.Diagnostics.AddWarning(v.Summary, v.Detail)
172181
}
173182

183+
func (v WarningValidator) ValidateEphemeralResource(ctx context.Context, request ephemeral.ValidateConfigRequest, response *ephemeral.ValidateConfigResponse) {
184+
response.Diagnostics.AddWarning(v.Summary, v.Detail)
185+
}
186+
174187
func (v WarningValidator) ValidateFloat32(ctx context.Context, request validator.Float32Request, response *validator.Float32Response) {
175188
response.Diagnostics.AddWarning(v.Summary, v.Detail)
176189
}
@@ -191,6 +204,10 @@ func (v WarningValidator) ValidateList(ctx context.Context, request validator.Li
191204
response.Diagnostics.AddWarning(v.Summary, v.Detail)
192205
}
193206

207+
func (v WarningValidator) ValidateListResourceConfig(ctx context.Context, request list.ValidateConfigRequest, response *list.ValidateConfigResponse) {
208+
response.Diagnostics.AddWarning(v.Summary, v.Detail)
209+
}
210+
194211
func (v WarningValidator) ValidateMap(ctx context.Context, request validator.MapRequest, response *validator.MapResponse) {
195212
response.Diagnostics.AddWarning(v.Summary, v.Detail)
196213
}
@@ -211,10 +228,6 @@ func (v WarningValidator) ValidateResource(ctx context.Context, request resource
211228
response.Diagnostics.AddWarning(v.Summary, v.Detail)
212229
}
213230

214-
func (v WarningValidator) ValidateEphemeralResource(ctx context.Context, request ephemeral.ValidateConfigRequest, response *ephemeral.ValidateConfigResponse) {
215-
response.Diagnostics.AddWarning(v.Summary, v.Detail)
216-
}
217-
218231
func (v WarningValidator) ValidateSet(ctx context.Context, request validator.SetRequest, response *validator.SetResponse) {
219232
response.Diagnostics.AddWarning(v.Summary, v.Detail)
220233
}

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+
}

0 commit comments

Comments
 (0)