Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add validation function for string length in bytes with min and max constraints #1350

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions helper/validation/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"regexp"
"strings"
"unicode/utf8"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure"
Expand Down Expand Up @@ -238,3 +239,16 @@ func StringIsValidRegExp(i interface{}, k string) (warnings []string, errors []e

return warnings, errors
}

// StringLenCharactersBetween returns a SchemaValidateFunc which tests if the provided value
// is of type string and has a character length between min and max (inclusive).
func StringLenBytesBetween(min, max int) schema.SchemaValidateFunc {
return func(val interface{}, key string) (warns []string, errs []error) {
v := val.(string)
length := utf8.RuneCountInString(v)
if length < min || length > max {
errs = append(errs, fmt.Errorf("%q must be between %d and %d bytes, got %d", key, min, max, length))
}
return
}
}
102 changes: 102 additions & 0 deletions helper/validation/strings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,105 @@ func TestStringIsValidRegExp(t *testing.T) {
},
})
}

func TestStringLenBytesBetween(t *testing.T) {
cases := []struct {
name string
input string
min int
max int
expectError bool
}{
// 1-byte character test cases
{
name: "valid single byte character (1 byte)",
input: "a",
min: 1,
max: 1,
expectError: false,
},
{
name: "invalid single byte character (2 characters min)",
input: "a",
min: 2,
max: 2,
expectError: true,
},
// 2-byte character test cases
{
name: "valid double byte character (1 character)",
input: "漢",
min: 1,
max: 1,
expectError: false,
},
{
name: "invalid double byte character (2 characters min)",
input: "漢",
min: 2,
max: 2,
expectError: true,
},
// 3-byte character test cases
{
name: "valid triple byte character (1 character)",
input: "日",
min: 1,
max: 1,
expectError: false,
},
{
name: "invalid triple byte character (2 characters min)",
input: "日",
min: 2,
max: 2,
expectError: true,
},
// 4-byte character test cases
{
name: "valid quadruple byte character (1 character)",
input: "😀",
min: 1,
max: 1,
expectError: false,
},
{
name: "invalid quadruple byte character (2 characters min)",
input: "😀",
min: 2,
max: 2,
expectError: true,
},
// Mixed characters test cases
{
name: "valid mixed characters",
input: "a漢日😀",
min: 4,
max: 4,
expectError: false,
},
{
name: "invalid mixed characters (5 characters min)",
input: "a漢日😀",
min: 5,
max: 5,
expectError: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
v := StringLenBytesBetween(tc.min, tc.max)
_, errs := v(tc.input, "test")
if tc.expectError {
if len(errs) == 0 {
t.Fatalf("expected errors but got none")
}
} else {
if len(errs) != 0 {
t.Fatalf("expected no errors but got: %v", errs)
}
}
})
}
}