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>
3.0 KiB
Go Validate
Small, composable value validators for Go. A validator is just a
func(T) error — it returns nil or an error. You compose them with
All or Collect and drop in plain closures wherever you need something
custom. No struct tags, no reflection, no dependencies outside the
standard library.
username := validate.All(
validate.MinLength(3),
validate.MaxLength(16),
validate.Chars("abcdefghijklmnopqrstuvwxyz0123456789_"),
func(s string) error {
if strings.HasPrefix(s, "_") {
return errors.New("must not start with an underscore")
}
return nil
},
)
if err := username(input); err != nil {
// errors.Is(err, validate.ErrDisallowedChars) still works
}
Why this and not one of the established packages
| Package | Style |
|---|---|
go-playground/validator |
struct tags (validate:"required,email") driven by reflection |
go-ozzo/ozzo-validation |
rules composed in code, through a Rule interface over interface{} |
asaskevich/govalidator |
a bag of IsEmail / IsURL string helpers |
This package is the code-composition style (closest to ozzo-validation) with two constraints held on purpose:
- Validators are ordinary generic functions.
In,Equaland the rest are type-checked by the compiler — pass the wrong type and it does not build. The tag/reflection libraries can't do this; they predate generics. - Errors are plain sentinels.
errors.Is(err, ErrInvalidEmail)composes with the standarderrorspackage. There is no bespokeValidationErrorstype to learn.Errmatches any error from the package.
Everything is value-level: there is no struct walker. You wire fields together yourself (a few lines) and decide how to present the result.
Fail fast or collect everything
// stops at the first failure
validate.All(rules...)
// runs every rule, joins the failures with errors.Join;
// errors.Is still matches each one
validate.Collect(rules...)
Building blocks
| Group | Functions |
|---|---|
| Compose | All, Collect |
| Presence / equality | Required, Equal, In, NotIn |
| String length & content | MinLength, MaxLength (runes), MinLengthBytes, MaxLengthBytes, Chars, ExceptChars, Prefix, Suffix, Contains, Match |
| Formats | Email, URL, UUID |
| Numbers | Min, Max, MinFloat32, MaxFloat32, MinFloat64, MaxFloat64 |
| Slices | MinSize, MaxSize |
| Errors | Error, NewError, Err |
Each returns (or is) a func(T) error, so anything you write with the
same shape composes with them.
Not a fit if…
You want struct-tag validation, translated / i18n messages, or a
large catalogue of built-in checks (credit cards, ISO codes, CIDRs, …).
Use go-playground/validator
for that. This package deliberately stays small.
License
MIT. See LICENSE.md.