yav

package module
v0.16.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2024 License: MIT Imports: 4 Imported by: 0

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

View Source
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 IsError

func IsError(err error) bool

func Join added in v0.10.0

func Join(errs ...error) error

Join returns combined Errors or nil. It is useful to combine Chain results, while validating multiple values.

func Join2 added in v0.13.6

func Join2(err0, err1 error) error

Join2 exactly equals to Join with two arguments, but works faster.

func Join3 added in v0.13.6

func Join3(err0, err1, err2 error) error

Join3 exactly equals to Join with three arguments, but works faster.

func NamedCheck added in v0.13.7

func NamedCheck(checkName string, err error) error

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

func Nested(name string, err error) error

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 Next added in v0.9.4

func Next[T any](string, T) (stop bool, err error)

Next is a no-op ValidateFunc.

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

type Error struct {
	CheckName string
	Parameter string

	ValueName string
	Value     any
}

func ErrRequired added in v0.13.8

func ErrRequired(name string) Error

func ErrUnique added in v0.15.0

func ErrUnique(name string, value any) Error

func (Error) As added in v0.11.2

func (err Error) As(target any) bool

func (Error) Error

func (err Error) Error() string

func (Error) Is

func (err Error) Is(target error) bool

func (Error) ValueAsString

func (err Error) ValueAsString() string

func (Error) WithNamedValue

func (err Error) WithNamedValue(name string, value any) Error

func (Error) WithParameter

func (err Error) WithParameter(parameter string) Error

func (Error) WithValue

func (err Error) WithValue(value any) Error

func (Error) WithValueName

func (err Error) WithValueName(name string) Error

type Errors added in v0.10.0

type Errors struct {
	Unknown    []error
	Validation []Error
}

func (*Errors) Append added in v0.10.0

func (errs *Errors) Append(err error)

func (Errors) As added in v0.11.2

func (errs Errors) As(target any) bool

func (Errors) AsError added in v0.10.0

func (errs Errors) AsError() error

AsError returns non-zero Errors or nil.

func (Errors) Error added in v0.10.0

func (errs Errors) Error() string

func (Errors) Is added in v0.10.1

func (errs Errors) Is(target error) bool

func (Errors) IsZero added in v0.10.0

func (errs Errors) IsZero() bool

type Validatable added in v0.8.0

type Validatable interface {
	Validate() error
}

type ValidateFunc

type ValidateFunc[T any] func(name string, value T) (stop bool, err error)

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.

type Zeroer added in v0.8.2

type Zeroer interface {
	IsZero() bool
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL