README ¶
Yet Another Validator
Go struct and field validation.
The project is inspired by go-playground/validator and uses some of its codebase.
YAV aims to provide Playground-like validator configuration and produce compatible errors whenever possible.
At the same time, the introduced chained validator improves validation speed dramatically.
The Playground validator's performance is quite poor due to heavy reflection usage, which YAV strives not to use.
YAV's key principles:
- mimic Playground validator whenever possible;
- make fewer to zero allocations;
- work fast.
The main drawback of other builder-like validators (e.g. ozzo) is that
they are interface{}
-based and allocate a lot of memory upon each run.
Sometimes, it even makes them slower than the Playground validator.
Unlike in earlier versions, the repo no longer includes Playground validator wrapper in order to reduce the number of 3rd-party dependencies. The removed code still can be found in yav-tests.
Examples
Single field struct validation
The field name passed to yav.Chain
doesn't affect the validation process and is only necessary
for building a validation error, so that you may use whatever naming style you like, i.e. id
, ID
, Id
.
type AccountID struct {
ID string
}
func (id AccountID) Validate() error {
return yav.Chain(
"id", id.ID,
vstring.Required,
vstring.UUID,
)
}
Combine validation errors
Use yav.Join
to combine multiple validation errors.
type Password struct {
Salt, Hash []byte
}
func (p Password) Validate() error {
return yav.Join(
yav.Chain(
"salt", p.Salt,
vbytes.Required,
vbytes.Max(200),
),
yav.Chain(
"hash", p.Hash,
vbytes.Required,
vbytes.Max(200),
),
)
}
Validate nested structs
Use yav.Nested
to add value namespace, i.e. to get password.salt
error instead of just salt
.
Contrary, any possible id
error is returned as if the field were in the Account
struct directly.
type Account struct {
AccountID
Login string
Password Password
}
func (a Account) Validate() error {
return yav.Join(
a.AccountID.Validate(),
yav.Chain(
"login", a.Login,
vstring.Required,
vstring.Between(4, 20),
vstring.Alphanumeric,
vstring.Lowercase,
),
yav.Nested("password", a.Password.Validate()),
)
}
Compare YAV and Playground validator
Here we pass to YAV Go-like field names in order to produce Playground-compatible errors.
YAV doesn't anyhow use it, except while building validation errors.
If compatibility is not required, pass the field names in whatever style you prefer.
type Account struct {
ID string `validate:"required,uuid"`
Login string `validate:"required,min=4,max=20,alphanum,lowercase"`
Password string `validate:"required_with=Login,omitempty,min=8,max=32,text"`
Email string `validate:"required,min=6,max=100,email"`
Phone string `validate:"required,min=8,max=16,e164"`
}
func (a Account) Validate() error {
return yav.Join(
yav.Chain(
"ID", a.ID,
vstring.Required,
vstring.UUID,
),
yav.Chain(
"Login", a.Login,
vstring.Required,
vstring.Min(4),
vstring.Max(20),
vstring.Alphanumeric,
vstring.Lowercase,
),
yav.Chain(
"Password", a.Password,
vstring.RequiredWithAny().String(a.Login).Names("Login"),
vstring.Between(8, 32),
vstring.Text,
),
yav.Chain(
"Email", a.Email,
vstring.Required,
vstring.Between(6, 100),
vstring.Email,
),
yav.Chain(
"Phone", a.Phone,
vstring.Required,
vstring.Between(8, 16),
vstring.E164,
),
)
}
Available validations
Common
OmitEmpty
Required
RequiredIf
RequiredUnless
RequiredWithAny
RequiredWithoutAny
RequiredWithAll
RequiredWithoutAll
ExcludedIf
ExcludedUnless
ExcludedWithAny
ExcludedWithoutAny
ExcludedWithAll
ExcludedWithoutAll
Bool
Equal
NotEqual
Bytes
Min
Max
Between
Duration
Min
Max
Between
LessThan
LessThanOrEqual
GreaterThan
GreaterThanOrEqual
LessThanNamed
LessThanOrEqualNamed
GreaterThanNamed
GreaterThanOrEqualNamed
Map
Min
Max
Between
Unique
Keys
Values
Number
Min
Max
Between
LessThan
LessThanOrEqual
GreaterThan
GreaterThanOrEqual
Equal
NotEqual
OneOf
Slice
Min
Max
Between
Unique
Items
String
Min
Max
Between
Equal
NotEqual
OneOf
Alpha
Alphanumeric
Lowercase
Uppercase
ContainsAlpha
ContainsLowerAlpha
ContainsUpperAlpha
ContainsDigit
ContainsSpecialCharacter
ExcludesWhitespace
StartsWithAlpha
StartsWithLowerAlpha
StartsWithUpperAlpha
StartsWithDigit
StartsWithSpecialCharacter
EndsWithAlpha
EndsWithLowerAlpha
EndsWithUpperAlpha
EndsWithDigit
EndsWithSpecialCharacter
Text
Title
E164
Email
Hostname
HostnameRFC1123
HostnamePort
FQDN
URI
URL
UUID
Regexp
Time
Min
Max
Between
LessThan
LessThanOrEqual
GreaterThan
GreaterThanOrEqual
LessThanNamed
LessThanOrEqualNamed
GreaterThanNamed
GreaterThanOrEqualNamed
Benchmarks
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i9-10850K CPU @ 3.60GHz
Tiny struct validation
BenchmarkYAV 12907930 92.15 ns/op 0 B/op 0 allocs/op
BenchmarkOzzo 1334562 890.1 ns/op 1248 B/op 20 allocs/op
BenchmarkPlayground 1324868 911.8 ns/op 40 B/op 2 allocs/op
Account struct validation
BenchmarkYAV 729123 1658 ns/op 123 B/op 4 allocs/op
BenchmarkPreAllocatedYAV 802777 1488 ns/op 0 B/op 0 allocs/op
BenchmarkOzzo* 54954 21684 ns/op 19215 B/op 317 allocs/op
BenchmarkPlayground 172633 6789 ns/op 653 B/op 23 allocs/op
Notes
- The Account in the Examples section is a reduced version of the benchmarked structure.
- Ozzo validator lacks some features available in both YAV and Playground validator. Therefore, those validation steps were not enabled for ozzo.
- The YAV is still slower, than a manually written validation boilerplate, but the amount of code differs dramatically.
Documentation ¶
Index ¶
- Constants
- func Chain[T any](name string, value T, validateFuncs ...ValidateFunc[T]) error
- func IsError(err error) bool
- func Join(errs ...error) error
- func Join2(err0, err1 error) error
- func Join3(err0, err1, err2 error) error
- func NamedCheck(checkName string, err error) error
- func Nested(name string, err error) error
- func NestedValidate[T Validatable](name string, value T) (stop bool, err error)
- func Next[T any](string, T) (stop bool, err error)
- func OmitEmpty[T comparable](_ string, value T) (stop bool, err error)
- func Required[T comparable](name string, value T) (stop bool, err error)
- func UnnamedValidate[T Validatable](_ string, value T) (stop bool, err error)
- type Error
- func (err Error) As(target any) bool
- func (err Error) Error() string
- func (err Error) Is(target error) bool
- func (err Error) ValueAsString() string
- func (err Error) WithNamedValue(name string, value any) Error
- func (err Error) WithParameter(parameter string) Error
- func (err Error) WithValue(value any) Error
- func (err Error) WithValueName(name string) Error
- type Errors
- type Validatable
- type ValidateFunc
- func NamedCheckFunc[T any](checkName string, validateFuncs ...ValidateFunc[T]) ValidateFunc[T]
- func Or[T any](validateFuncs ...ValidateFunc[T]) ValidateFunc[T]
- func Or2[T any](validateFunc0, validateFunc1 ValidateFunc[T]) ValidateFunc[T]
- func Or3[T any](validateFunc0, validateFunc1, validateFunc2 ValidateFunc[T]) ValidateFunc[T]
- type Zeroer
Constants ¶
const ( CheckNameRequired = "required" CheckNameRequiredIf = "required_if" CheckNameRequiredUnless = "required_unless" CheckNameRequiredWithAny = "required_with" CheckNameRequiredWithoutAny = "required_without" CheckNameRequiredWithAll = "required_with_all" CheckNameRequiredWithoutAll = "required_without_all" CheckNameExcludedIf = "excluded_if" CheckNameExcludedUnless = "excluded_unless" CheckNameExcludedWithAny = "excluded_with" CheckNameExcludedWithoutAny = "excluded_without" CheckNameExcludedWithAll = "excluded_with_all" CheckNameExcludedWithoutAll = "excluded_without_all" CheckNameMin = "min" CheckNameMax = "max" CheckNameGreaterThan = "gt" CheckNameGreaterThanOrEqual = "gte" CheckNameLessThan = "lt" CheckNameLessThanOrEqual = "lte" CheckNameGreaterThanNamed = "gtfield" CheckNameGreaterThanOrEqualNamed = "gtefield" CheckNameLessThanNamed = "ltfield" CheckNameLessThanOrEqualNamed = "ltefield" CheckNameEqual = "eq" CheckNameNotEqual = "ne" CheckNameOneOf = "oneof" CheckNameUnique = "unique" CheckNameEmail = "email" CheckNameE164 = "e164" CheckNameUUID = "uuid" CheckNameURI = "uri" CheckNameURL = "url" CheckNameHostname = "hostname" // RFC 952 CheckNameHostnameRFC1123 = "hostname_rfc1123" // RFC 1123, DNS name CheckNameHostnamePort = "hostname_port" // [RFC 1123]:<port> CheckNameFQDN = "fqdn" // RFC 1123, but must contain a non-numerical TLD CheckNameRegexp = "regexp" CheckNameBase64 = "base64" CheckNameBase64Raw = "base64raw" CheckNameBase64URL = "base64url" CheckNameBase64RawURL = "base64rawurl" CheckNameAlpha = "alpha" CheckNameAlphanumeric = "alphanum" CheckNameNumeric = "numeric" CheckNameLowercase = "lowercase" CheckNameUppercase = "uppercase" CheckNameContainsAlpha = "contains_alpha" CheckNameContainsLowerAlpha = "contains_lower_alpha" CheckNameContainsUpperAlpha = "contains_upper_alpha" CheckNameContainsDigit = "contains_digit" CheckNameContainsSpecialCharacter = "contains_special_character" CheckNameExcludesWhitespace = "excludes_whitespace" CheckNameStartsWithAlpha = "starts_with_alpha" CheckNameStartsWithLowerAlpha = "starts_with_lower_alpha" CheckNameStartsWithUpperAlpha = "starts_with_upper_alpha" CheckNameStartsWithDigit = "starts_with_digit" CheckNameStartsWithSpecialCharacter = "starts_with_special_character" CheckNameEndsWithAlpha = "ends_with_alpha" CheckNameEndsWithLowerAlpha = "ends_with_lower_alpha" CheckNameEndsWithUpperAlpha = "ends_with_upper_alpha" CheckNameEndsWithDigit = "ends_with_digit" CheckNameEndsWithSpecialCharacter = "ends_with_special_character" CheckNameText = "text" CheckNameTitle = "title" )
Variables ¶
This section is empty.
Functions ¶
func Chain ¶
func Chain[T any](name string, value T, validateFuncs ...ValidateFunc[T]) error
Chain allows chaining validation funcs against a single struct field or value. If not nil, the result is always of Errors type.
func Join ¶ added in v0.10.0
Join returns combined Errors or nil. It is useful to combine Chain results, while validating multiple values.
func NamedCheck ¶ added in v0.13.7
NamedCheck processes errors of either Error or Errors type, clearing Error.Parameter and replacing Error.CheckName with the given name. Unsupported and nil errors are returned as is.
func Nested ¶ added in v0.1.1
Nested processes errors of either Error or Errors type, prepending Error.ValueName with name argument. It returns unsupported and nil errors as is.
func NestedValidate ¶ added in v0.8.5
func NestedValidate[T Validatable](name string, value T) (stop bool, err error)
NestedValidate basically equals to UnnamedValidate, but additionally calls Nested before returning the error.
func OmitEmpty ¶ added in v0.11.4
func OmitEmpty[T comparable](_ string, value T) (stop bool, err error)
OmitEmpty skips further validation, when a generic comparable value is default for its type. Most of the validation packages contain specialized versions of this function, e.g. vstring.OmitEmpty, etc.
func Required ¶ added in v0.16.3
func Required[T comparable](name string, value T) (stop bool, err error)
Required checks a generic comparable value against the default for its type. Most of the validation packages contain specialized versions of this function, e.g. vstring.Required, etc.
func UnnamedValidate ¶ added in v0.8.5
func UnnamedValidate[T Validatable](_ string, value T) (stop bool, err error)
UnnamedValidate is a ValidateFunc that simply calls Validatable.Validate of the given value. It may be useful, while validating slice items or map entries.
Types ¶
type Error ¶
func ErrRequired ¶ added in v0.13.8
func (Error) ValueAsString ¶
func (Error) WithParameter ¶
func (Error) WithValueName ¶
type Errors ¶ added in v0.10.0
type Validatable ¶ added in v0.8.0
type Validatable interface {
Validate() error
}
type ValidateFunc ¶
ValidateFunc usually represents a single validation check against the given named value.
func NamedCheckFunc ¶ added in v0.14.2
func NamedCheckFunc[T any](checkName string, validateFuncs ...ValidateFunc[T]) ValidateFunc[T]
NamedCheckFunc combines the given validation funcs into a new named one. Those functions are invoked similarly to Chain, then NamedCheck is applied to the result.
func Or ¶
func Or[T any](validateFuncs ...ValidateFunc[T]) ValidateFunc[T]
Or combines the given validation funcs into a new one, which iterates over and sequentially invokes the arguments. When any of the functions returns a nil error, its result is immediately returned. Otherwise, a non-nil error and stop flag of the last function are returned.
Constructing and dropping intermediate errors is somewhat expensive, so that use Or with care. If performance is crucial, write your own validation func returning an error if and only if all the checks fail.
func Or2 ¶ added in v0.8.1
func Or2[T any](validateFunc0, validateFunc1 ValidateFunc[T]) ValidateFunc[T]
Or2 exactly equals to Or with two arguments, but makes one less memory allocation.
func Or3 ¶ added in v0.8.1
func Or3[T any](validateFunc0, validateFunc1, validateFunc2 ValidateFunc[T]) ValidateFunc[T]
Or3 exactly equals to Or with three arguments, but makes one less memory allocation.