docs: explain what distinguishes validate (+ comment audit) #11

Merged
aneurin merged 4 commits from docs-positioning into main 2026-09-07 16:35:55 +00:00
2 changed files with 22 additions and 6 deletions
Showing only changes of commit 4e2e05c1b9 - Show all commits
+6 -4
View File
@@ -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")
) )
Outdated
Review

The original intention was to measure text length. Does this make it more correct to measure runes rather than bytes?

The original intention was to measure text length. Does this make it more correct to measure runes rather than bytes?
// 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
View File
@@ -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 {