Skip to content

Commit 40680ac

Browse files
author
Ivan De Marino
authored
Splitting OneOf and NoneOf by "case sensitivity" (#45)
* Splitting `OneOf` and `NoneOf` by "case sensitivity" * `OneOf` and `NoneOf` are case sensitive * `OneOfCaseInsensitive` and `NoneOfCaseInsensitive` are ... case insensitive instead * Changelog entries
1 parent 668b4ce commit 40680ac

File tree

7 files changed

+366
-52
lines changed

7 files changed

+366
-52
lines changed

.changelog/42.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,3 @@ int64validator: 2 new validation functions, `OneOf()` and `NoneOf()`
99
```release-note:feature
1010
numbervalidator: New package that starts with 2 validation functions, `OneOf()` and `NoneOf()`
1111
```
12-
13-
```release-note:enhancement
14-
stringvalidator: 2 new validation functions, `OneOf()` and `NoneOf()`, that offer case-sensitivity control
15-
```

.changelog/45.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
stringvalidator: 4 new validation functions, `OneOf()` and `NoneOf()` (case sensitive), and `OneOfCaseInsensitive()` and `NoneOfCaseInsensitive()` (case insensitive)
3+
```

stringvalidator/acceptable_strings_validator.go

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,27 @@ import (
99
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1010
)
1111

12-
// acceptableStringsAttributeValidator is the underlying struct implementing OneOf and NoneOf.
13-
type acceptableStringsAttributeValidator struct {
12+
// acceptableStringsCaseInsensitiveAttributeValidator is the underlying struct implementing OneOf and NoneOf.
13+
type acceptableStringsCaseInsensitiveAttributeValidator struct {
1414
acceptableStrings []string
15-
caseSensitive bool
1615
shouldMatch bool
1716
}
1817

19-
var _ tfsdk.AttributeValidator = (*acceptableStringsAttributeValidator)(nil)
18+
var _ tfsdk.AttributeValidator = (*acceptableStringsCaseInsensitiveAttributeValidator)(nil)
2019

21-
func (av *acceptableStringsAttributeValidator) Description(ctx context.Context) string {
20+
func (av *acceptableStringsCaseInsensitiveAttributeValidator) Description(ctx context.Context) string {
2221
return av.MarkdownDescription(ctx)
2322
}
2423

25-
func (av *acceptableStringsAttributeValidator) MarkdownDescription(_ context.Context) string {
24+
func (av *acceptableStringsCaseInsensitiveAttributeValidator) MarkdownDescription(_ context.Context) string {
2625
if av.shouldMatch {
2726
return fmt.Sprintf("String must match one of: %q", av.acceptableStrings)
2827
} else {
2928
return fmt.Sprintf("String must match none of: %q", av.acceptableStrings)
3029
}
3130
}
3231

33-
func (av *acceptableStringsAttributeValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, res *tfsdk.ValidateAttributeResponse) {
32+
func (av *acceptableStringsCaseInsensitiveAttributeValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, res *tfsdk.ValidateAttributeResponse) {
3433
value, ok := validateString(ctx, req, res)
3534
if !ok {
3635
return
@@ -46,16 +45,10 @@ func (av *acceptableStringsAttributeValidator) Validate(ctx context.Context, req
4645
}
4746
}
4847

49-
func (av *acceptableStringsAttributeValidator) isAcceptableValue(v string) bool {
48+
func (av *acceptableStringsCaseInsensitiveAttributeValidator) isAcceptableValue(v string) bool {
5049
for _, acceptableV := range av.acceptableStrings {
51-
if av.caseSensitive {
52-
if v == acceptableV {
53-
return true
54-
}
55-
} else {
56-
if strings.EqualFold(v, acceptableV) {
57-
return true
58-
}
50+
if strings.EqualFold(v, acceptableV) {
51+
return true
5952
}
6053
}
6154

stringvalidator/none_of.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
package stringvalidator
22

33
import (
4+
"github.com/hashicorp/terraform-plugin-framework-validators/internal/primitivevalidator"
5+
"github.com/hashicorp/terraform-plugin-framework/attr"
46
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
7+
"github.com/hashicorp/terraform-plugin-framework/types"
58
)
69

710
// NoneOf checks that the string held in the attribute
811
// is none of the given `unacceptableStrings`.
9-
//
10-
// String comparison case sensitiveness is controlled by the `caseSensitive` argument.
11-
func NoneOf(caseSensitive bool, unacceptableStrings ...string) tfsdk.AttributeValidator {
12-
return &acceptableStringsAttributeValidator{
12+
func NoneOf(unacceptableStrings ...string) tfsdk.AttributeValidator {
13+
unacceptableStringValues := make([]attr.Value, 0, len(unacceptableStrings))
14+
for _, s := range unacceptableStrings {
15+
unacceptableStringValues = append(unacceptableStringValues, types.String{Value: s})
16+
}
17+
18+
return primitivevalidator.NoneOf(unacceptableStringValues...)
19+
}
20+
21+
// NoneOfCaseInsensitive checks that the string held in the attribute
22+
// is none of the given `unacceptableStrings`, irrespective of case sensitivity.
23+
func NoneOfCaseInsensitive(unacceptableStrings ...string) tfsdk.AttributeValidator {
24+
return &acceptableStringsCaseInsensitiveAttributeValidator{
1325
unacceptableStrings,
14-
caseSensitive,
1526
false,
1627
}
1728
}

stringvalidator/none_of_test.go

Lines changed: 161 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,24 @@ func TestNoneOfValidator(t *testing.T) {
3030
"simple-match": {
3131
in: types.String{Value: "foo"},
3232
validator: stringvalidator.NoneOf(
33-
true,
3433
"foo",
3534
"bar",
3635
"baz",
3736
),
3837
expErrors: 1,
3938
},
40-
"simple-match-case-insensitive": {
39+
"simple-mismatch-case-insensitive": {
4140
in: types.String{Value: "foo"},
4241
validator: stringvalidator.NoneOf(
43-
false,
4442
"FOO",
4543
"bar",
4644
"baz",
4745
),
48-
expErrors: 1,
46+
expErrors: 0,
4947
},
5048
"simple-mismatch": {
5149
in: types.String{Value: "foz"},
5250
validator: stringvalidator.NoneOf(
53-
true,
5451
"foo",
5552
"bar",
5653
"baz",
@@ -67,7 +64,6 @@ func TestNoneOfValidator(t *testing.T) {
6764
},
6865
},
6966
validator: stringvalidator.NoneOf(
70-
true,
7167
"10",
7268
"20",
7369
"30",
@@ -86,7 +82,6 @@ func TestNoneOfValidator(t *testing.T) {
8682
},
8783
},
8884
validator: stringvalidator.NoneOf(
89-
true,
9085
"bob",
9186
"alice",
9287
"john",
@@ -106,7 +101,6 @@ func TestNoneOfValidator(t *testing.T) {
106101
},
107102
},
108103
validator: stringvalidator.NoneOf(
109-
true,
110104
"1.1",
111105
"10.20",
112106
"5.4",
@@ -125,7 +119,6 @@ func TestNoneOfValidator(t *testing.T) {
125119
},
126120
},
127121
validator: stringvalidator.NoneOf(
128-
true,
129122
"Bob Parr",
130123
"40",
131124
"1200 Park Avenue Emeryville",
@@ -136,7 +129,6 @@ func TestNoneOfValidator(t *testing.T) {
136129
"skip-validation-on-null": {
137130
in: types.String{Null: true},
138131
validator: stringvalidator.NoneOf(
139-
true,
140132
"foo",
141133
"bar",
142134
"baz",
@@ -146,7 +138,165 @@ func TestNoneOfValidator(t *testing.T) {
146138
"skip-validation-on-unknown": {
147139
in: types.String{Unknown: true},
148140
validator: stringvalidator.NoneOf(
149-
true,
141+
"foo",
142+
"bar",
143+
"baz",
144+
),
145+
expErrors: 0,
146+
},
147+
}
148+
149+
for name, test := range testCases {
150+
name, test := name, test
151+
t.Run(name, func(t *testing.T) {
152+
req := tfsdk.ValidateAttributeRequest{
153+
AttributeConfig: test.in,
154+
}
155+
res := tfsdk.ValidateAttributeResponse{}
156+
test.validator.Validate(context.TODO(), req, &res)
157+
158+
if test.expErrors > 0 && !res.Diagnostics.HasError() {
159+
t.Fatalf("expected %d error(s), got none", test.expErrors)
160+
}
161+
162+
if test.expErrors > 0 && test.expErrors != validatordiag.ErrorsCount(res.Diagnostics) {
163+
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
164+
}
165+
166+
if test.expErrors == 0 && res.Diagnostics.HasError() {
167+
t.Fatalf("expected no error(s), got %d: %v", validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
168+
}
169+
})
170+
}
171+
}
172+
173+
func TestNoneOfCaseInsensitiveValidator(t *testing.T) {
174+
t.Parallel()
175+
176+
type testCase struct {
177+
in attr.Value
178+
validator tfsdk.AttributeValidator
179+
expErrors int
180+
}
181+
182+
objAttrTypes := map[string]attr.Type{
183+
"Name": types.StringType,
184+
"Age": types.StringType,
185+
"Address": types.StringType,
186+
}
187+
188+
testCases := map[string]testCase{
189+
"simple-match": {
190+
in: types.String{Value: "foo"},
191+
validator: stringvalidator.NoneOfCaseInsensitive(
192+
"foo",
193+
"bar",
194+
"baz",
195+
),
196+
expErrors: 1,
197+
},
198+
"simple-match-case-insensitive": {
199+
in: types.String{Value: "foo"},
200+
validator: stringvalidator.NoneOfCaseInsensitive(
201+
"FOO",
202+
"bar",
203+
"baz",
204+
),
205+
expErrors: 1,
206+
},
207+
"simple-mismatch": {
208+
in: types.String{Value: "foz"},
209+
validator: stringvalidator.NoneOfCaseInsensitive(
210+
"foo",
211+
"bar",
212+
"baz",
213+
),
214+
expErrors: 0,
215+
},
216+
"list-not-allowed": {
217+
in: types.List{
218+
ElemType: types.StringType,
219+
Elems: []attr.Value{
220+
types.String{Value: "10"},
221+
types.String{Value: "20"},
222+
types.String{Value: "30"},
223+
},
224+
},
225+
validator: stringvalidator.NoneOfCaseInsensitive(
226+
"10",
227+
"20",
228+
"30",
229+
"40",
230+
"50",
231+
),
232+
expErrors: 1,
233+
},
234+
"set-not-allowed": {
235+
in: types.Set{
236+
ElemType: types.StringType,
237+
Elems: []attr.Value{
238+
types.String{Value: "foo"},
239+
types.String{Value: "bar"},
240+
types.String{Value: "baz"},
241+
},
242+
},
243+
validator: stringvalidator.NoneOfCaseInsensitive(
244+
"bob",
245+
"alice",
246+
"john",
247+
"foo",
248+
"bar",
249+
"baz",
250+
),
251+
expErrors: 1,
252+
},
253+
"map-not-allowed": {
254+
in: types.Map{
255+
ElemType: types.StringType,
256+
Elems: map[string]attr.Value{
257+
"one.one": types.String{Value: "1.1"},
258+
"ten.twenty": types.String{Value: "10.20"},
259+
"five.four": types.String{Value: "5.4"},
260+
},
261+
},
262+
validator: stringvalidator.NoneOfCaseInsensitive(
263+
"1.1",
264+
"10.20",
265+
"5.4",
266+
"geronimo",
267+
"bob",
268+
),
269+
expErrors: 1,
270+
},
271+
"object-not-allowed": {
272+
in: types.Object{
273+
AttrTypes: objAttrTypes,
274+
Attrs: map[string]attr.Value{
275+
"Name": types.String{Value: "Bob Parr"},
276+
"Age": types.String{Value: "40"},
277+
"Address": types.String{Value: "1200 Park Avenue Emeryville"},
278+
},
279+
},
280+
validator: stringvalidator.NoneOfCaseInsensitive(
281+
"Bob Parr",
282+
"40",
283+
"1200 Park Avenue Emeryville",
284+
"123",
285+
),
286+
expErrors: 1,
287+
},
288+
"skip-validation-on-null": {
289+
in: types.String{Null: true},
290+
validator: stringvalidator.NoneOfCaseInsensitive(
291+
"foo",
292+
"bar",
293+
"baz",
294+
),
295+
expErrors: 0,
296+
},
297+
"skip-validation-on-unknown": {
298+
in: types.String{Unknown: true},
299+
validator: stringvalidator.NoneOfCaseInsensitive(
150300
"foo",
151301
"bar",
152302
"baz",

stringvalidator/one_of.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
package stringvalidator
22

33
import (
4+
"github.com/hashicorp/terraform-plugin-framework-validators/internal/primitivevalidator"
5+
"github.com/hashicorp/terraform-plugin-framework/attr"
46
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
7+
"github.com/hashicorp/terraform-plugin-framework/types"
58
)
69

710
// OneOf checks that the string held in the attribute
811
// is one of the given `acceptableStrings`.
9-
//
10-
// String comparison case sensitiveness is controlled by the `caseSensitive` argument.
11-
func OneOf(caseSensitive bool, acceptableStrings ...string) tfsdk.AttributeValidator {
12-
return &acceptableStringsAttributeValidator{
12+
func OneOf(acceptableStrings ...string) tfsdk.AttributeValidator {
13+
acceptableStringValues := make([]attr.Value, 0, len(acceptableStrings))
14+
for _, s := range acceptableStrings {
15+
acceptableStringValues = append(acceptableStringValues, types.String{Value: s})
16+
}
17+
18+
return primitivevalidator.OneOf(acceptableStringValues...)
19+
}
20+
21+
// OneOfCaseInsensitive checks that the string held in the attribute
22+
// is one of the given `acceptableStrings`, irrespective of case sensitivity.
23+
func OneOfCaseInsensitive(acceptableStrings ...string) tfsdk.AttributeValidator {
24+
return &acceptableStringsCaseInsensitiveAttributeValidator{
1325
acceptableStrings,
14-
caseSensitive,
1526
true,
1627
}
1728
}

0 commit comments

Comments
 (0)