Skip to content

Commit a5fdeb7

Browse files
author
Ivan De Marino
committed
Update all schemavalidators to use path.Expressions
1 parent d0f4597 commit a5fdeb7

8 files changed

Lines changed: 367 additions & 200 deletions

schemavalidator/at_least_one_of.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,23 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/attributepath"
7+
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/pathutils"
88
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
99
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-framework/path"
1011
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
11-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1212
)
1313

1414
// atLeastOneOfAttributeValidator is the underlying struct implementing AtLeastOneOf.
1515
type atLeastOneOfAttributeValidator struct {
16-
attrPaths []*tftypes.AttributePath
16+
pathExpressions path.Expressions
1717
}
1818

19-
// AtLeastOneOf checks that of a set of *tftypes.AttributePath,
19+
// AtLeastOneOf checks that of a set of path.Expression,
2020
// including the attribute it's applied to, at least one attribute out of all specified is configured.
2121
//
22-
// The provided tftypes.AttributePath must be "absolute",
23-
// and starting with top level attribute names.
24-
func AtLeastOneOf(attributePaths ...*tftypes.AttributePath) tfsdk.AttributeValidator {
22+
// Relative path.Expression will be resolved against the validated attribute.
23+
func AtLeastOneOf(attributePaths ...path.Expression) tfsdk.AttributeValidator {
2524
return &atLeastOneOfAttributeValidator{attributePaths}
2625
}
2726

@@ -31,22 +30,21 @@ func (av atLeastOneOfAttributeValidator) Description(ctx context.Context) string
3130
return av.MarkdownDescription(ctx)
3231
}
3332

34-
func (av atLeastOneOfAttributeValidator) MarkdownDescription(ctx context.Context) string {
35-
return fmt.Sprintf("Ensure that at least one attribute from this collection is set: %q", av.attrPaths)
33+
func (av atLeastOneOfAttributeValidator) MarkdownDescription(_ context.Context) string {
34+
return fmt.Sprintf("Ensure that at least one attribute from this collection is set: %q", av.pathExpressions)
3635
}
3736

3837
func (av atLeastOneOfAttributeValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, res *tfsdk.ValidateAttributeResponse) {
39-
// Assemble a slice of paths, ensuring we don't repeat the attribute this validator is applied to
40-
var paths []*tftypes.AttributePath
41-
if attributepath.Contains(req.AttributePath, av.attrPaths...) {
42-
paths = av.attrPaths
43-
} else {
44-
paths = append(av.attrPaths, req.AttributePath)
38+
matchingPaths, diags := pathutils.PathMatchExpressionsAgainstAttributeConfig(ctx, av.pathExpressions, req.AttributePathExpression, req.Config)
39+
res.Diagnostics.Append(diags...)
40+
if diags.HasError() {
41+
return
4542
}
4643

47-
for _, path := range paths {
44+
// Validate values at the matching paths
45+
for _, p := range matchingPaths {
4846
var v attr.Value
49-
diags := req.Config.GetAttribute(ctx, path, &v)
47+
diags := req.Config.GetAttribute(ctx, p, &v)
5048
res.Diagnostics.Append(diags...)
5149
if diags.HasError() {
5250
return
@@ -57,8 +55,8 @@ func (av atLeastOneOfAttributeValidator) Validate(ctx context.Context, req tfsdk
5755
}
5856
}
5957

60-
res.Diagnostics.Append(validatordiag.InvalidAttributeSchemaDiagnostic(
58+
res.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic(
6159
req.AttributePath,
62-
fmt.Sprintf("At least one attribute out of %q must be specified", attributepath.JoinToString(paths...)),
60+
fmt.Sprintf("At least one attribute out of %q must be specified", matchingPaths),
6361
))
6462
}

schemavalidator/at_least_one_of_test.go

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"context"
55
"testing"
66

7-
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
87
"github.com/hashicorp/terraform-plugin-framework-validators/schemavalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/path"
99
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1010
"github.com/hashicorp/terraform-plugin-framework/types"
1111
"github.com/hashicorp/terraform-plugin-go/tftypes"
@@ -16,15 +16,16 @@ func TestAtLeastOneOfValidator(t *testing.T) {
1616

1717
type testCase struct {
1818
req tfsdk.ValidateAttributeRequest
19-
in []*tftypes.AttributePath
19+
in path.Expressions
2020
expErrors int
2121
}
2222

2323
testCases := map[string]testCase{
2424
"base": {
2525
req: tfsdk.ValidateAttributeRequest{
26-
AttributeConfig: types.String{Value: "bar value"},
27-
AttributePath: tftypes.NewAttributePath().WithAttributeName("bar"),
26+
AttributeConfig: types.String{Value: "bar value"},
27+
AttributePath: path.Root("bar"),
28+
AttributePathExpression: path.MatchRoot("bar"),
2829
Config: tfsdk.Config{
2930
Schema: tfsdk.Schema{
3031
Attributes: map[string]tfsdk.Attribute{
@@ -47,14 +48,15 @@ func TestAtLeastOneOfValidator(t *testing.T) {
4748
}),
4849
},
4950
},
50-
in: []*tftypes.AttributePath{
51-
tftypes.NewAttributePath().WithAttributeName("foo"),
51+
in: path.Expressions{
52+
path.MatchRoot("foo"),
5253
},
5354
},
5455
"self-is-null": {
5556
req: tfsdk.ValidateAttributeRequest{
56-
AttributeConfig: types.String{Null: true},
57-
AttributePath: tftypes.NewAttributePath().WithAttributeName("bar"),
57+
AttributeConfig: types.String{Null: true},
58+
AttributePath: path.Root("bar"),
59+
AttributePathExpression: path.MatchRoot("bar"),
5860
Config: tfsdk.Config{
5961
Schema: tfsdk.Schema{
6062
Attributes: map[string]tfsdk.Attribute{
@@ -77,14 +79,15 @@ func TestAtLeastOneOfValidator(t *testing.T) {
7779
}),
7880
},
7981
},
80-
in: []*tftypes.AttributePath{
81-
tftypes.NewAttributePath().WithAttributeName("foo"),
82+
in: path.Expressions{
83+
path.MatchRoot("foo"),
8284
},
8385
},
8486
"error_none-set": {
8587
req: tfsdk.ValidateAttributeRequest{
86-
AttributeConfig: types.String{Value: "bar value"},
87-
AttributePath: tftypes.NewAttributePath().WithAttributeName("bar"),
88+
AttributeConfig: types.String{Value: "bar value"},
89+
AttributePath: path.Root("bar"),
90+
AttributePathExpression: path.MatchRoot("bar"),
8891
Config: tfsdk.Config{
8992
Schema: tfsdk.Schema{
9093
Attributes: map[string]tfsdk.Attribute{
@@ -112,16 +115,17 @@ func TestAtLeastOneOfValidator(t *testing.T) {
112115
}),
113116
},
114117
},
115-
in: []*tftypes.AttributePath{
116-
tftypes.NewAttributePath().WithAttributeName("foo"),
117-
tftypes.NewAttributePath().WithAttributeName("baz"),
118+
in: path.Expressions{
119+
path.MatchRoot("foo"),
120+
path.MatchRoot("baz"),
118121
},
119122
expErrors: 1,
120123
},
121124
"multiple-set": {
122125
req: tfsdk.ValidateAttributeRequest{
123-
AttributeConfig: types.String{Value: "bar value"},
124-
AttributePath: tftypes.NewAttributePath().WithAttributeName("bar"),
126+
AttributeConfig: types.String{Value: "bar value"},
127+
AttributePath: path.Root("bar"),
128+
AttributePathExpression: path.MatchRoot("bar"),
125129
Config: tfsdk.Config{
126130
Schema: tfsdk.Schema{
127131
Attributes: map[string]tfsdk.Attribute{
@@ -149,15 +153,16 @@ func TestAtLeastOneOfValidator(t *testing.T) {
149153
}),
150154
},
151155
},
152-
in: []*tftypes.AttributePath{
153-
tftypes.NewAttributePath().WithAttributeName("foo"),
154-
tftypes.NewAttributePath().WithAttributeName("baz"),
156+
in: path.Expressions{
157+
path.MatchRoot("foo"),
158+
path.MatchRoot("baz"),
155159
},
156160
},
157161
"allow-duplicate-input": {
158162
req: tfsdk.ValidateAttributeRequest{
159-
AttributeConfig: types.String{Value: "bar value"},
160-
AttributePath: tftypes.NewAttributePath().WithAttributeName("bar"),
163+
AttributeConfig: types.String{Value: "bar value"},
164+
AttributePath: path.Root("bar"),
165+
AttributePathExpression: path.MatchRoot("bar"),
161166
Config: tfsdk.Config{
162167
Schema: tfsdk.Schema{
163168
Attributes: map[string]tfsdk.Attribute{
@@ -185,16 +190,17 @@ func TestAtLeastOneOfValidator(t *testing.T) {
185190
}),
186191
},
187192
},
188-
in: []*tftypes.AttributePath{
189-
tftypes.NewAttributePath().WithAttributeName("foo"),
190-
tftypes.NewAttributePath().WithAttributeName("bar"),
191-
tftypes.NewAttributePath().WithAttributeName("baz"),
193+
in: path.Expressions{
194+
path.MatchRoot("foo"),
195+
path.MatchRoot("bar"),
196+
path.MatchRoot("baz"),
192197
},
193198
},
194199
"unknowns": {
195200
req: tfsdk.ValidateAttributeRequest{
196-
AttributeConfig: types.String{Value: "bar value"},
197-
AttributePath: tftypes.NewAttributePath().WithAttributeName("bar"),
201+
AttributeConfig: types.String{Value: "bar value"},
202+
AttributePath: path.Root("bar"),
203+
AttributePathExpression: path.MatchRoot("bar"),
198204
Config: tfsdk.Config{
199205
Schema: tfsdk.Schema{
200206
Attributes: map[string]tfsdk.Attribute{
@@ -222,11 +228,43 @@ func TestAtLeastOneOfValidator(t *testing.T) {
222228
}),
223229
},
224230
},
225-
in: []*tftypes.AttributePath{
226-
tftypes.NewAttributePath().WithAttributeName("foo"),
227-
tftypes.NewAttributePath().WithAttributeName("baz"),
231+
in: path.Expressions{
232+
path.MatchRoot("foo"),
233+
path.MatchRoot("baz"),
228234
},
229235
},
236+
"matches-no-attribute-in-schema": {
237+
req: tfsdk.ValidateAttributeRequest{
238+
AttributeConfig: types.String{Value: "bar value"},
239+
AttributePath: path.Root("bar"),
240+
AttributePathExpression: path.MatchRoot("bar"),
241+
Config: tfsdk.Config{
242+
Schema: tfsdk.Schema{
243+
Attributes: map[string]tfsdk.Attribute{
244+
"foo": {
245+
Type: types.Int64Type,
246+
},
247+
"bar": {
248+
Type: types.StringType,
249+
},
250+
},
251+
},
252+
Raw: tftypes.NewValue(tftypes.Object{
253+
AttributeTypes: map[string]tftypes.Type{
254+
"foo": tftypes.Number,
255+
"bar": tftypes.String,
256+
},
257+
}, map[string]tftypes.Value{
258+
"foo": tftypes.NewValue(tftypes.Number, 42),
259+
"bar": tftypes.NewValue(tftypes.String, nil),
260+
}),
261+
},
262+
},
263+
in: path.Expressions{
264+
path.MatchRoot("fooz"),
265+
},
266+
expErrors: 1,
267+
},
230268
}
231269

232270
for name, test := range testCases {
@@ -239,12 +277,12 @@ func TestAtLeastOneOfValidator(t *testing.T) {
239277
t.Fatal("expected error(s), got none")
240278
}
241279

242-
if test.expErrors > 0 && test.expErrors != validatordiag.ErrorsCount(res.Diagnostics) {
243-
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
280+
if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() {
281+
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics)
244282
}
245283

246284
if test.expErrors == 0 && res.Diagnostics.HasError() {
247-
t.Fatalf("expected no error(s), got %d: %v", validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
285+
t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics)
248286
}
249287
})
250288
}

schemavalidator/conflicts_with.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,24 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/attributepath"
7+
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/pathutils"
88
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
99
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-framework/path"
1011
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
11-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1212
)
1313

1414
// conflictsWithAttributeValidator is the underlying struct implementing ConflictsWith.
1515
type conflictsWithAttributeValidator struct {
16-
attrPaths []*tftypes.AttributePath
16+
pathExpressions path.Expressions
1717
}
1818

19-
// ConflictsWith checks that a set of *tftypes.AttributePath,
19+
// ConflictsWith checks that a set of path.Expression,
2020
// including the attribute it's applied to, are not set simultaneously.
2121
// This implements the validation logic declaratively within the tfsdk.Schema.
2222
//
23-
// The provided tftypes.AttributePath must be "absolute",
24-
// and starting with top level attribute names.
25-
func ConflictsWith(attributePaths ...*tftypes.AttributePath) tfsdk.AttributeValidator {
23+
// Relative path.Expression will be resolved against the validated attribute.
24+
func ConflictsWith(attributePaths ...path.Expression) tfsdk.AttributeValidator {
2625
return &conflictsWithAttributeValidator{attributePaths}
2726
}
2827

@@ -33,7 +32,7 @@ func (av conflictsWithAttributeValidator) Description(ctx context.Context) strin
3332
}
3433

3534
func (av conflictsWithAttributeValidator) MarkdownDescription(_ context.Context) string {
36-
return fmt.Sprintf("Ensure that if an attribute is set, these are not set: %q", av.attrPaths)
35+
return fmt.Sprintf("Ensure that if an attribute is set, these are not set: %q", av.pathExpressions)
3736
}
3837

3938
func (av conflictsWithAttributeValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, res *tfsdk.ValidateAttributeResponse) {
@@ -43,24 +42,31 @@ func (av conflictsWithAttributeValidator) Validate(ctx context.Context, req tfsd
4342
return
4443
}
4544

46-
for _, path := range av.attrPaths {
45+
matchingPaths, diags := pathutils.PathMatchExpressionsAgainstAttributeConfig(ctx, av.pathExpressions, req.AttributePathExpression, req.Config)
46+
res.Diagnostics.Append(diags...)
47+
if diags.HasError() {
48+
return
49+
}
50+
51+
// Validate values at the matching paths
52+
for _, p := range matchingPaths {
4753
// If the user specifies the same attribute this validator is applied to,
4854
// also as part of the input, skip it.
49-
if req.AttributePath.Equal(path) {
55+
if p.Equal(req.AttributePath) {
5056
continue
5157
}
5258

5359
var o attr.Value
54-
diags := req.Config.GetAttribute(ctx, path, &o)
60+
diags := req.Config.GetAttribute(ctx, p, &o)
5561
res.Diagnostics.Append(diags...)
5662
if diags.HasError() {
5763
return
5864
}
5965

6066
if !v.IsNull() && !o.IsNull() {
61-
res.Diagnostics.Append(validatordiag.InvalidAttributeSchemaDiagnostic(
67+
res.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic(
6268
req.AttributePath,
63-
fmt.Sprintf("Attribute %q cannot be specified when %q is specified", attributepath.ToString(path), attributepath.ToString(req.AttributePath)),
69+
fmt.Sprintf("Attribute %q cannot be specified when %q is specified", p, req.AttributePath),
6470
))
6571
}
6672
}

0 commit comments

Comments
 (0)