diff --git a/.changes/unreleased/BUG FIXES-20250903-155819.yaml b/.changes/unreleased/BUG FIXES-20250903-155819.yaml new file mode 100644 index 00000000000..f6f14ebbc13 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20250903-155819.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: ' tfsdk: Fixed `StringLenBetween` function to correctly count multibyte characters' +time: 2025-09-03T15:58:19.587736995Z +custom: + Issue: "1516" diff --git a/helper/validation/strings.go b/helper/validation/strings.go index d8c2243937b..83bdbfeabaa 100644 --- a/helper/validation/strings.go +++ b/helper/validation/strings.go @@ -8,9 +8,11 @@ import ( "fmt" "regexp" "strings" + "unicode/utf8" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "golang.org/x/text/unicode/norm" ) // StringIsNotEmpty is a ValidateFunc that ensures a string is not empty @@ -78,8 +80,9 @@ func StringLenBetween(minVal, maxVal int) schema.SchemaValidateFunc { errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) return warnings, errors } - - if len(v) < minVal || len(v) > maxVal { + v = norm.NFC.String(v) + l := utf8.RuneCountInString(v) + if l < minVal || l > maxVal { errors = append(errors, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s", k, minVal, maxVal, v)) } diff --git a/helper/validation/strings_test.go b/helper/validation/strings_test.go index 1bb78630a0a..03c336d6d43 100644 --- a/helper/validation/strings_test.go +++ b/helper/validation/strings_test.go @@ -259,6 +259,27 @@ func TestValidationStringLenBetween(t *testing.T) { f: StringLenBetween(1, 5), expectedErr: regexp.MustCompile(`expected type of [\w]+ to be string`), }, + { + val: "ๆ—ฅ", // 3 bytes char, length must be 1 + f: StringLenBetween(1, 1), + }, + { + val: "๐Ÿ˜Š", // 4 bytes char, length must be 1 + f: StringLenBetween(1, 1), + }, + { + val: "๐Ÿ˜Š๐Ÿ˜ญ", // 4 bytes chars, length must be 2 + f: StringLenBetween(1, 2), + }, + { + val: "ใ‹ใ‚™", // 2 code points + f: StringLenBetween(1, 1), + }, + { + val: "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", // multi code points but can't be normalized + f: StringLenBetween(1, 1), + expectedErr: regexp.MustCompile(`expected length of [\w]+ to be in the range \(1 \- 1\), got ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ`), + }, }) }