MinLength/MaxLength now count runes, leaving no way to bound a string by
its byte size -- still wanted for fixed-width columns and wire-format
fields. Add the byte-counting pair alongside them, with their own "%d
bytes" sentinels (ErrMustHaveMoreBytes / ErrMustHaveFewerBytes).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Closes#3.
- doc.go: package overview -- what a validator is, All vs Collect, errors
are sentinels, how this compares to validator/v10 (tags+reflection),
ozzo-validation (Rule interface) and govalidator (IsX helpers), and when
to pick something else.
- README.md: rewritten from three lines to a positioning intro, a
"why this and not X" table, a building-blocks table, and a
"not a fit if" note.
- example_test.go: runnable Example (compose built-ins + a closure),
Example_collectAll (errors.Is over a joined error), Example_errorsIs
(sentinel matching, Err as match-anything).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Audit ahead of the README/doc.go work (issue #3):
- number.go: Min, MinFloat32, MinFloat64 all said "less than or equal to a
given maximum" -- copy-pasted from the Max family. They check the
minimum. Fixed.
- chars_test.go: ExampleExceptChars called Chars, not ExceptChars, so it
never exercised the function it documents.
- chars.go: "does not contain disallowed characters" was circular.
- in.go: NotIn's parameter was named "allow"; renamed to "disallow".
- length.go: note that length is bytes (len), not runes -- the "%d
characters" messages imply otherwise.
- url.go: spell out that ParseRequestURI wants an absolute URL or path.
- uuid.go: note lowercase-hex only, version/variant not checked.
- error.go: explain Err (the match-anything sentinel), how Error() formats
with Data, and what With does. all.go: point at Collect.
Comments only; no behaviour change. go vet + go test ./... pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds .gitea/workflows/ci.yml: on push to main and on pull requests, run
`go vet ./...` then `go test ./...` in a golang:1.21-alpine container.
Also removes .github/workflows/test.yml, which only triggered on a
`develop` branch that no longer exists and depended on an external
Discord notification action and an unset DISCORD_WEBHOOK secret.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the RFC 5322 mega-regexp with net/mail.ParseAddress, which is the
standard library's address parser and far easier to audit. A bare address
is required: inputs with a display name, angle brackets, a comment, or
trailing content are rejected, as is an address list.
Behaviour change: ParseAddress does not require the domain to contain a
dot, so "alice@localhost" now validates. Layer Match or a DNS lookup on
top if a stricter domain is needed.
Also rewrites FuzzEmail, which previously asserted that *every* input is
invalid, into checks that Email never returns an unexpected error type,
never panics, and gives a stable verdict.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Broadens the built-in set with the string/presence checks that came up
most often when reaching for this package:
- Required[T comparable] non-zero value
- Prefix / Suffix / Contains strings.HasPrefix/HasSuffix/Contains
- Match(*regexp.Regexp) arbitrary pattern
All follow the existing shape: a constructor returning func(T) error, a
sentinel error matched with errors.Is, table-driven tests and an Example.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collect is the all-errors-at-once counterpart to All. All still stops at
the first failure (unchanged); Collect runs every function and returns the
failures joined with errors.Join, which errors.Is still matches element by
element. Kept as a separate function rather than a flag on All so All's
signature and behaviour don't change.
Closes#5
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>