Skip to content

Commit 19aa7ab

Browse files
authored
Introduce stringvalidator package (#22)
Reference: #3
1 parent a772159 commit 19aa7ab

File tree

8 files changed

+423
-1
lines changed

8 files changed

+423
-1
lines changed

.changelog/22.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 `stringvalidator` package with `LengthAtLeast()`, `LengthAtMost()`, and `LengthBetween()` validation functions
3+
```

stringvalidator/length_at_least.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package stringvalidator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
)
10+
11+
var _ tfsdk.AttributeValidator = lengthAtLeastValidator{}
12+
13+
// stringLenAtLeastValidator validates that a string Attribute's length is at least a certain value.
14+
type lengthAtLeastValidator struct {
15+
minLength int
16+
}
17+
18+
// Description describes the validation in plain text formatting.
19+
func (validator lengthAtLeastValidator) Description(_ context.Context) string {
20+
return fmt.Sprintf("string length must be at least %d", validator.minLength)
21+
}
22+
23+
// MarkdownDescription describes the validation in Markdown formatting.
24+
func (validator lengthAtLeastValidator) MarkdownDescription(ctx context.Context) string {
25+
return validator.Description(ctx)
26+
}
27+
28+
// Validate performs the validation.
29+
func (validator lengthAtLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
30+
s, ok := validateString(ctx, request, response)
31+
32+
if !ok {
33+
return
34+
}
35+
36+
if l := len(s); l < validator.minLength {
37+
response.Diagnostics.Append(validatordiag.AttributeValueLengthDiagnostic(
38+
request.AttributePath,
39+
validator.Description(ctx),
40+
fmt.Sprintf("%d", l),
41+
))
42+
43+
return
44+
}
45+
}
46+
47+
// LengthAtLeast returns an AttributeValidator which ensures that any configured
48+
// attribute value:
49+
//
50+
// - Is a string.
51+
// - Is of length exclusively greater than the given minimum.
52+
//
53+
// Null (unconfigured) and unknown (known after apply) values are skipped.
54+
func LengthAtLeast(minLength int) tfsdk.AttributeValidator {
55+
if minLength < 0 {
56+
return nil
57+
}
58+
59+
return lengthAtLeastValidator{
60+
minLength: minLength,
61+
}
62+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package stringvalidator
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+
13+
func TestLengthAtLeastValidator(t *testing.T) {
14+
t.Parallel()
15+
16+
type testCase struct {
17+
val attr.Value
18+
minLength int
19+
expectError bool
20+
}
21+
tests := map[string]testCase{
22+
"not a String": {
23+
val: types.Bool{Value: true},
24+
expectError: true,
25+
},
26+
"unknown String": {
27+
val: types.String{Unknown: true},
28+
minLength: 1,
29+
},
30+
"null String": {
31+
val: types.String{Null: true},
32+
minLength: 1,
33+
},
34+
"valid String": {
35+
val: types.String{Value: "ok"},
36+
minLength: 1,
37+
},
38+
"too short String": {
39+
val: types.String{Value: ""},
40+
minLength: 1,
41+
expectError: true,
42+
},
43+
}
44+
45+
for name, test := range tests {
46+
name, test := name, test
47+
t.Run(name, func(t *testing.T) {
48+
request := tfsdk.ValidateAttributeRequest{
49+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
50+
AttributeConfig: test.val,
51+
}
52+
response := tfsdk.ValidateAttributeResponse{}
53+
LengthAtLeast(test.minLength).Validate(context.TODO(), request, &response)
54+
55+
if !response.Diagnostics.HasError() && test.expectError {
56+
t.Fatal("expected error, got no error")
57+
}
58+
59+
if response.Diagnostics.HasError() && !test.expectError {
60+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
61+
}
62+
})
63+
}
64+
}

stringvalidator/length_at_most.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package stringvalidator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
)
11+
12+
var _ tfsdk.AttributeValidator = lengthAtMostValidator{}
13+
14+
// lengthAtMostValidator validates that a string Attribute's length is at most a certain value.
15+
type lengthAtMostValidator struct {
16+
maxLength int
17+
}
18+
19+
// Description describes the validation in plain text formatting.
20+
func (validator lengthAtMostValidator) Description(_ context.Context) string {
21+
return fmt.Sprintf("string length must be at most %d", validator.maxLength)
22+
}
23+
24+
// MarkdownDescription describes the validation in Markdown formatting.
25+
func (validator lengthAtMostValidator) MarkdownDescription(ctx context.Context) string {
26+
return validator.Description(ctx)
27+
}
28+
29+
// Validate performs the validation.
30+
func (validator lengthAtMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
31+
s, ok := validateString(ctx, request, response)
32+
33+
if !ok {
34+
return
35+
}
36+
37+
if l := len(s); l > validator.maxLength {
38+
response.Diagnostics.Append(validatordiag.AttributeValueLengthDiagnostic(
39+
request.AttributePath,
40+
validator.Description(ctx),
41+
fmt.Sprintf("%d", l),
42+
))
43+
44+
return
45+
}
46+
}
47+
48+
// LengthAtMost returns an AttributeValidator which ensures that any configured
49+
// attribute value:
50+
//
51+
// - Is a string.
52+
// - Is of length exclusively less than the given maximum.
53+
//
54+
// Null (unconfigured) and unknown (known after apply) values are skipped.
55+
func LengthAtMost(maxLength int) tfsdk.AttributeValidator {
56+
if maxLength < 0 {
57+
return nil
58+
}
59+
60+
return lengthAtMostValidator{
61+
maxLength: maxLength,
62+
}
63+
}
64+
65+
// validateString ensures that the request contains a String value.
66+
func validateString(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (string, bool) {
67+
var s types.String
68+
69+
diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &s)
70+
71+
if diags.HasError() {
72+
response.Diagnostics = append(response.Diagnostics, diags...)
73+
74+
return "", false
75+
}
76+
77+
if s.Unknown || s.Null {
78+
return "", false
79+
}
80+
81+
return s.Value, true
82+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package stringvalidator
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+
13+
func TestLengthAtMostValidator(t *testing.T) {
14+
t.Parallel()
15+
16+
type testCase struct {
17+
val attr.Value
18+
maxLength int
19+
expectError bool
20+
}
21+
tests := map[string]testCase{
22+
"not a String": {
23+
val: types.Bool{Value: true},
24+
expectError: true,
25+
},
26+
"unknown String": {
27+
val: types.String{Unknown: true},
28+
maxLength: 1,
29+
},
30+
"null String": {
31+
val: types.String{Null: true},
32+
maxLength: 1,
33+
},
34+
"valid String": {
35+
val: types.String{Value: "ok"},
36+
maxLength: 2,
37+
},
38+
"too long String": {
39+
val: types.String{Value: "not ok"},
40+
maxLength: 5,
41+
expectError: true,
42+
},
43+
}
44+
45+
for name, test := range tests {
46+
name, test := name, test
47+
t.Run(name, func(t *testing.T) {
48+
request := tfsdk.ValidateAttributeRequest{
49+
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
50+
AttributeConfig: test.val,
51+
}
52+
response := tfsdk.ValidateAttributeResponse{}
53+
LengthAtMost(test.maxLength).Validate(context.TODO(), request, &response)
54+
55+
if !response.Diagnostics.HasError() && test.expectError {
56+
t.Fatal("expected error, got no error")
57+
}
58+
59+
if response.Diagnostics.HasError() && !test.expectError {
60+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
61+
}
62+
})
63+
}
64+
}

stringvalidator/length_between.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package stringvalidator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
)
10+
11+
var _ tfsdk.AttributeValidator = lengthBetweenValidator{}
12+
13+
// stringLenBetweenValidator validates that a string Attribute's length is in a range.
14+
type lengthBetweenValidator struct {
15+
minLength, maxLength int
16+
}
17+
18+
// Description describes the validation in plain text formatting.
19+
func (validator lengthBetweenValidator) Description(_ context.Context) string {
20+
return fmt.Sprintf("string length must be between %d and %d", validator.minLength, validator.maxLength)
21+
}
22+
23+
// MarkdownDescription describes the validation in Markdown formatting.
24+
func (validator lengthBetweenValidator) MarkdownDescription(ctx context.Context) string {
25+
return validator.Description(ctx)
26+
}
27+
28+
// Validate performs the validation.
29+
func (validator lengthBetweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
30+
s, ok := validateString(ctx, request, response)
31+
32+
if !ok {
33+
return
34+
}
35+
36+
if l := len(s); l < validator.minLength || l > validator.maxLength {
37+
response.Diagnostics.Append(validatordiag.AttributeValueLengthDiagnostic(
38+
request.AttributePath,
39+
validator.Description(ctx),
40+
fmt.Sprintf("%d", l),
41+
))
42+
43+
return
44+
}
45+
}
46+
47+
// LengthBetween returns an AttributeValidator which ensures that any configured
48+
// attribute value:
49+
//
50+
// - Is a string.
51+
// - Is of length greater than the given minimum and less than the given maximum.
52+
//
53+
// Null (unconfigured) and unknown (known after apply) values are skipped.
54+
func LengthBetween(minLength, maxLength int) tfsdk.AttributeValidator {
55+
if minLength < 0 || maxLength < 0 || minLength > maxLength {
56+
return nil
57+
}
58+
59+
return lengthBetweenValidator{
60+
minLength: minLength,
61+
maxLength: maxLength,
62+
}
63+
}

0 commit comments

Comments
 (0)