MinLength/MaxLength measured len(value), so a string of 8 accented characters (9+ bytes) failed MaxLength(8) despite the error message promising "characters". Count with utf8.RuneCountInString so the check matches the wording and the common-sense intent. Still not grapheme-cluster aware (combining marks, emoji ZWJ sequences count as several runes), which is out of scope for a stdlib-only helper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,17 @@
|
|||||||
package validate
|
package validate
|
||||||
|
|
||||||
|
import "unicode/utf8"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMustBeLonger = NewError("must contain at least %d characters")
|
ErrMustBeLonger = NewError("must contain at least %d characters")
|
||||||
ErrMustBeShorter = NewError("must contain no more than %d characters")
|
ErrMustBeShorter = NewError("must contain no more than %d characters")
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxLength validates that a string is no longer than a given maximum.
|
// MaxLength validates that a string is no longer than a given maximum.
|
||||||
// Length is measured in bytes (len), not runes.
|
// Length is counted in runes, so multi-byte characters count as one.
|
||||||
func MaxLength(l int) func(string) error {
|
func MaxLength(l int) func(string) error {
|
||||||
return func(value string) error {
|
return func(value string) error {
|
||||||
if len(value) > l {
|
if utf8.RuneCountInString(value) > l {
|
||||||
return ErrMustBeShorter.With(l)
|
return ErrMustBeShorter.With(l)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -17,10 +19,10 @@ func MaxLength(l int) func(string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MinLength validates that a string is at least a given minimum length.
|
// MinLength validates that a string is at least a given minimum length.
|
||||||
// Length is measured in bytes (len), not runes.
|
// Length is counted in runes, so multi-byte characters count as one.
|
||||||
func MinLength(l int) func(string) error {
|
func MinLength(l int) func(string) error {
|
||||||
return func(value string) error {
|
return func(value string) error {
|
||||||
if len(value) < l {
|
if utf8.RuneCountInString(value) < l {
|
||||||
return ErrMustBeLonger.With(l)
|
return ErrMustBeLonger.With(l)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+16
-2
@@ -20,7 +20,14 @@ func ExampleMinLength() {
|
|||||||
|
|
||||||
func TestMaxLength(t *testing.T) {
|
func TestMaxLength(t *testing.T) {
|
||||||
testCases := map[int]map[string]error{
|
testCases := map[int]map[string]error{
|
||||||
8: {"abcd": nil, "abcdefgh": nil, "abcd efg": nil, "abcdefghi": ErrMustBeShorter.With(8)},
|
8: {
|
||||||
|
"abcd": nil,
|
||||||
|
"abcdefgh": nil,
|
||||||
|
"abcd efg": nil,
|
||||||
|
"abcdéfgh": nil, // 8 runes, 9 bytes
|
||||||
|
"abcdefghi": ErrMustBeShorter.With(8),
|
||||||
|
"abcdéfghi": ErrMustBeShorter.With(8), // 9 runes, 10 bytes
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for setup, values := range testCases {
|
for setup, values := range testCases {
|
||||||
@@ -41,7 +48,14 @@ func TestMaxLength(t *testing.T) {
|
|||||||
|
|
||||||
func TestMinLength(t *testing.T) {
|
func TestMinLength(t *testing.T) {
|
||||||
testCases := map[int]map[string]error{
|
testCases := map[int]map[string]error{
|
||||||
8: {"abcd": ErrMustBeLonger.With(8), "abcdefgh": nil, "abcd efg": nil, "abcdefghi": nil},
|
8: {
|
||||||
|
"abcd": ErrMustBeLonger.With(8),
|
||||||
|
"abcdéfg": ErrMustBeLonger.With(8), // 7 runes, 8 bytes
|
||||||
|
"abcdefgh": nil,
|
||||||
|
"abcdéfgh": nil, // 8 runes, 9 bytes
|
||||||
|
"abcd efg": nil,
|
||||||
|
"abcdefghi": nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for setup, values := range testCases {
|
for setup, values := range testCases {
|
||||||
|
|||||||
Reference in New Issue
Block a user