Skip to content
InseiPublic

About

Valigo is a powerfull, zero allocations validation engine with localizations, conditions and custom validation functions support.

Topics

Resources

Stars

4 stars

Watchers

1 watching

Forks

Repository files navigation

codecov build Goreport GoDoc

valigo

valigo validates Go structs by rules written in Go, next to the struct, by pointers to its fields. The compiler checks every rule, a valid object is validated without allocations about 4 times faster than by go-playground/validator, and the errors come translated to the language of the user, with stable codes for the clients that translate themselves.

b.Field(&u.Email).As(str.Of).Required().Email()
b.Field(&u.Age).As(num.Of).AnyOfInterval(18, 120) // the arguments are int: Max("120") doesn't compile
b.Field(&u.Name).As(num.Of)                       // doesn't compile: string does not satisfy num.Number

Documentation: insei.github.io/valigo — get started, why valigo, the rules of every type, performance.

For AI agents: the skill with the current API, and the documentation as one file, llms-full.txt.

Installation

Go 1.27 or newer is required, the configurator uses generic methods.

go get github.com/insei/valigo@latest

Upgrading from v0.0.x: see Upgrading.

Example

type User struct {
	Name     string
	Email    string
	Age      int
	Phone    *string
	Tags     []string
	StartsAt time.Time
	EndsAt   *time.Time
}

v := valigo.New()
valigo.Configure[User](v, func(b valigo.Configurator[User], u *User) {
	b.Field(&u.Name).As(str.Of).Trim().Required().MaxRunes(64)
	b.Field(&u.Email).As(str.Of).Required().Email()
	b.Field(&u.Age).As(num.Of).AnyOfInterval(18, 120)
	b.Field(&u.Phone).As(str.Of).E164() // a nil phone is skipped
	b.Slice(&u.Tags).As(str.Slice).MaxLen(5).Each(func(tag *str.BaseConfigurator) {
		tag.Trim().Required().MaxLen(16)
	})
	b.Field(&u.EndsAt).As(timex.Of).AfterField(&u.StartsAt) // a rule on another field
})

errs := v.ValidateTyped(ctx, &User{Name: "  Ann  ", Email: "ann.example.com", Age: 16, Tags: []string{"go", " "}})
// Should be email address (Email: ann.example.com)   code string.email
// Only interval[18 - 120] is allowed (Age: 16)       code number.interval, params {"min": 18, "max": 120}
// Should be fulfilled (Tags[1]: )                    code string.required

Rules

The configurator has Field, Slice and Map for the fields, As converts them to the configurators of their types, and a conversion that doesn't fit the field doesn't compile:

Conversion Fields Rules
str.Of string, *string, named strings Required, lengths in bytes and runes, AnyOf, Prefix, Suffix, Contains, Excludes, Alpha, Digits, Numeric, Hexadecimal, Regexp, Email, URL, IP, CIDR, MAC, Hostname, UUID, E164, Base64, JSON, Semver, HexColor, Datetime, CountryCode, CurrencyCode, CreditCard, Trim...
num.Of, num.Ptr integers, floats, named numbers, pointers to them Required, Min, Max, GreaterThan, LessThan, AnyOf, AnyOfInterval, Latitude, Longitude
timex.Of time.Time, *time.Time Required, Before, After, Between, InPast, InFuture
eq.Of, eq.Ptr any comparable: enums, bools Required, OneOf, NotOneOf, Equal, NotEqual
vuuid.Of uuid.UUID, *uuid.UUID, the separate module github.com/insei/valigo/rules/uuid Required, AnyOf
str.Slice, num.Slice, timex.Slice, eq.Slice, vuuid.Slice slices, also of pointers Required, MinLen, MaxLen, Each for the elements, Unique
b.Map(&m.X) maps Required, MinLen, MaxLen, EachKey, EachValue
  • Rules on other fields by their pointers: AfterField, MinField, MaxField, EqualField.
  • Conditions and custom rules: When and Custom on fields and on the struct, closures with the context of the request — the limits can come from a database or the settings of a tenant.
  • Absent values: nil pointers and the fields of nil nested structs are skipped by every rule except Required.
  • Your types: a conversion function of a few lines, the core has no dependencies on uuid or other packages of types.
  • Tags of go-playground/validator: b.Tags("validate") configures the rules of validate:"required,max=64" with the messages and the speed of valigo, a migration moves them to code one by one.

Errors and translations

  • Every error has the translated message, the location (Tags[1], address.city with JSON paths), the value, a stable Code and named Params
  • Messages in English, German, Spanish, French and Russian with plural forms, the language of every request from the context, an Accept-Language middleware
  • Translations from a chain of sources: your catalogs of go-i18n by github.com/insei/valigo/integrations/goi18n, the built-in YAML for the rest, or your own source
  • problem: the errors as application/problem+json of RFC 9457 with JSON pointers
  • The rules as the keywords of JSON Schema, vhuma.OpenAPI puts them into the OpenAPI document of huma: the clients see maxLength, format, enum of the rules
  • github.com/insei/valigo/integrations/huma, github.com/insei/valigo/integrations/gin, github.com/insei/valigo/integrations/echo: the rules of valigo in huma, gin and echo

Performance

The same struct and rules, Intel Core i9-14900HX, Go 1.27.0, see Performance:

valid object invalid object, 6 errors
hand-written code 286 ns, 0 allocs 590 ns, without messages
valigo 407 ns, 0 allocs 2 205 ns, 14 allocs, translated messages
go-playground/validator 1 785 ns, 16 allocs 4 150 ns, 69 allocs, with Error()
ozzo-validation 2 950 ns, 38 allocs 12 665 ns, 140 allocs, with Error()

About

Valigo is a powerfull, zero allocations validation engine with localizations, conditions and custom validation functions support.

Topics

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages