Skip to content

Commit b0eaeea

Browse files
authored
Adding Set element validation for ValuesAre (#36)
* Adding Set element validation for ValuesAre (#12) * Renaming var (#12) * Rename test (#12) * Fix tests (#12) * Updates following code review (#12) * Adding doc.go and changelog entry (#12) * Updating CHANGELOG.md (#12) * Rename .changelog file to match PR number and remove updates to CHANGELOG.md (#12)
1 parent f80bdba commit b0eaeea

File tree

6 files changed

+310
-0
lines changed

6 files changed

+310
-0
lines changed

.changelog/36.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:feature
2+
Introduced `setvalidator` package with `ValuesAre()` validation function
3+
```

setvalidator/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package setvalidator provides validators for types.Set attributes.
2+
package setvalidator

setvalidator/type_validation.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package setvalidator
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-framework/types"
9+
)
10+
11+
// validateSet ensures that the request contains a Set value.
12+
func validateSet(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) ([]attr.Value, bool) {
13+
var s types.Set
14+
15+
diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &s)
16+
17+
if diags.HasError() {
18+
response.Diagnostics = append(response.Diagnostics, diags...)
19+
20+
return nil, false
21+
}
22+
23+
if s.Unknown || s.Null {
24+
return nil, false
25+
}
26+
27+
return s.Elems, true
28+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package setvalidator
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
10+
"github.com/hashicorp/terraform-plugin-framework/types"
11+
"github.com/hashicorp/terraform-plugin-go/tftypes"
12+
)
13+
14+
func TestValidateSet(t *testing.T) {
15+
t.Parallel()
16+
17+
testCases := map[string]struct {
18+
request tfsdk.ValidateAttributeRequest
19+
expectedSetElems []attr.Value
20+
expectedOk bool
21+
}{
22+
"invalid-type": {
23+
request: tfsdk.ValidateAttributeRequest{
24+
AttributeConfig: types.Bool{Value: true},
25+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
26+
},
27+
expectedSetElems: nil,
28+
expectedOk: false,
29+
},
30+
"set-null": {
31+
request: tfsdk.ValidateAttributeRequest{
32+
AttributeConfig: types.Set{Null: true},
33+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
34+
},
35+
expectedSetElems: nil,
36+
expectedOk: false,
37+
},
38+
"set-unknown": {
39+
request: tfsdk.ValidateAttributeRequest{
40+
AttributeConfig: types.Set{Unknown: true},
41+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
42+
},
43+
expectedSetElems: nil,
44+
expectedOk: false,
45+
},
46+
"set-value": {
47+
request: tfsdk.ValidateAttributeRequest{
48+
AttributeConfig: types.Set{
49+
ElemType: types.StringType,
50+
Elems: []attr.Value{
51+
types.String{Value: "first"},
52+
types.String{Value: "second"},
53+
},
54+
},
55+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
56+
},
57+
expectedSetElems: []attr.Value{
58+
types.String{Value: "first"},
59+
types.String{Value: "second"},
60+
},
61+
expectedOk: true,
62+
},
63+
}
64+
65+
for name, testCase := range testCases {
66+
name, testCase := name, testCase
67+
68+
t.Run(name, func(t *testing.T) {
69+
t.Parallel()
70+
71+
gotSetElems, gotOk := validateSet(context.Background(), testCase.request, &tfsdk.ValidateAttributeResponse{})
72+
73+
if diff := cmp.Diff(gotSetElems, testCase.expectedSetElems); diff != "" {
74+
t.Errorf("unexpected set difference: %s", diff)
75+
}
76+
77+
if diff := cmp.Diff(gotOk, testCase.expectedOk); diff != "" {
78+
t.Errorf("unexpected ok difference: %s", diff)
79+
}
80+
})
81+
}
82+
}

setvalidator/values_are.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package setvalidator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
)
10+
11+
var _ tfsdk.AttributeValidator = valuesAreValidator{}
12+
13+
// valuesAreValidator validates that each set member validates against each of the value validators.
14+
type valuesAreValidator struct {
15+
valueValidators []tfsdk.AttributeValidator
16+
}
17+
18+
// Description describes the validation in plain text formatting.
19+
func (v valuesAreValidator) Description(ctx context.Context) string {
20+
var descriptions []string
21+
for _, validator := range v.valueValidators {
22+
descriptions = append(descriptions, validator.Description(ctx))
23+
}
24+
25+
return fmt.Sprintf("value must satisfy all validations: %s", strings.Join(descriptions, " + "))
26+
}
27+
28+
// MarkdownDescription describes the validation in Markdown formatting.
29+
func (v valuesAreValidator) MarkdownDescription(ctx context.Context) string {
30+
return v.Description(ctx)
31+
}
32+
33+
// Validate performs the validation.
34+
func (v valuesAreValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
35+
elems, ok := validateSet(ctx, req, resp)
36+
if !ok {
37+
return
38+
}
39+
40+
for _, elem := range elems {
41+
value, err := elem.ToTerraformValue(ctx)
42+
if err != nil {
43+
resp.Diagnostics.AddError(
44+
"Attribute Conversion Error During Set Element Validation",
45+
"An unexpected error was encountered when handling the a Set element. "+
46+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
47+
"Please report this to the provider developer:\n\n"+
48+
"Attribute Conversion Error During Set Element Validation.",
49+
)
50+
return
51+
}
52+
53+
request := tfsdk.ValidateAttributeRequest{
54+
AttributePath: req.AttributePath.WithElementKeyValue(value),
55+
AttributeConfig: elem,
56+
Config: req.Config,
57+
}
58+
59+
for _, validator := range v.valueValidators {
60+
validator.Validate(ctx, request, resp)
61+
}
62+
}
63+
}
64+
65+
// ValuesAre returns an AttributeValidator which ensures that any configured
66+
// attribute value:
67+
//
68+
// - Is a Set.
69+
// - Contains Set elements, each of which validate against each value validator.
70+
//
71+
// Null (unconfigured) and unknown (known after apply) values are skipped.
72+
func ValuesAre(valueValidators ...tfsdk.AttributeValidator) tfsdk.AttributeValidator {
73+
return valuesAreValidator{
74+
valueValidators: valueValidators,
75+
}
76+
}

setvalidator/values_are_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package setvalidator
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
12+
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
13+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
14+
)
15+
16+
func TestValuesAreValidator(t *testing.T) {
17+
t.Parallel()
18+
19+
type testCase struct {
20+
val attr.Value
21+
valuesAreValidators []tfsdk.AttributeValidator
22+
expectError bool
23+
}
24+
tests := map[string]testCase{
25+
"not Set": {
26+
val: types.Map{
27+
ElemType: types.StringType,
28+
},
29+
expectError: true,
30+
},
31+
"Set unknown": {
32+
val: types.Set{
33+
Unknown: true,
34+
ElemType: types.StringType,
35+
},
36+
expectError: false,
37+
},
38+
"Set null": {
39+
val: types.Set{
40+
Null: true,
41+
ElemType: types.StringType,
42+
},
43+
expectError: false,
44+
},
45+
"Set elems invalid": {
46+
val: types.Set{
47+
ElemType: types.StringType,
48+
Elems: []attr.Value{
49+
types.String{Value: "first"},
50+
types.String{Value: "second"},
51+
},
52+
},
53+
valuesAreValidators: []tfsdk.AttributeValidator{
54+
stringvalidator.LengthAtLeast(6),
55+
},
56+
expectError: true,
57+
},
58+
"Set elems invalid for second validator": {
59+
val: types.Set{
60+
ElemType: types.StringType,
61+
Elems: []attr.Value{
62+
types.String{Value: "first"},
63+
types.String{Value: "second"},
64+
},
65+
},
66+
valuesAreValidators: []tfsdk.AttributeValidator{
67+
stringvalidator.LengthAtLeast(2),
68+
stringvalidator.LengthAtLeast(6),
69+
},
70+
expectError: true,
71+
},
72+
"Set elems wrong type for validator": {
73+
val: types.Set{
74+
ElemType: types.StringType,
75+
Elems: []attr.Value{
76+
types.String{Value: "first"},
77+
types.String{Value: "second"},
78+
},
79+
},
80+
valuesAreValidators: []tfsdk.AttributeValidator{
81+
int64validator.AtLeast(6),
82+
},
83+
expectError: true,
84+
},
85+
"Set elems valid": {
86+
val: types.Set{
87+
ElemType: types.StringType,
88+
Elems: []attr.Value{
89+
types.String{Value: "first"},
90+
types.String{Value: "second"},
91+
},
92+
},
93+
valuesAreValidators: []tfsdk.AttributeValidator{
94+
stringvalidator.LengthAtLeast(5),
95+
},
96+
expectError: false,
97+
},
98+
}
99+
100+
for name, test := range tests {
101+
name, test := name, test
102+
t.Run(name, func(t *testing.T) {
103+
request := tfsdk.ValidateAttributeRequest{
104+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
105+
AttributeConfig: test.val,
106+
}
107+
response := tfsdk.ValidateAttributeResponse{}
108+
ValuesAre(test.valuesAreValidators...).Validate(context.TODO(), request, &response)
109+
110+
if !response.Diagnostics.HasError() && test.expectError {
111+
t.Fatal("expected error, got no error")
112+
}
113+
114+
if response.Diagnostics.HasError() && !test.expectError {
115+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
116+
}
117+
})
118+
}
119+
}

0 commit comments

Comments
 (0)