vali

package module
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Jan 20, 2026 License: MIT Imports: 15 Imported by: 0

README

Vali, Yet Another Validator

License Test Coverage Go Report Card Go Reference Socket.dev

Vali, a purposefully tiny validator. Some 🔋🔋🔋 included, plus recipes on how to make more of them ☺️.

Description

Vali aims for the most things you could do with a minimal set of checks, therefore it has a small set of checks and an easy way to add your own checks, see the example and vali_test.go files.

You can also change the struct tag name being used (by creating a new Validator) and a few other bits, see Validator type definition.

It is pointer-insensitive, will always validate the value behind the pointer, that is, given:

Foo *string `validate:"required"`

passes if *Foo != "" NOT if Foo != nil.

It validates both public and private fields, as long as they have the validation tags. To skip a field entirely (including nested structs), use validate:"-".

Non-goals:

  • slice/map dive;
  • cross field checks;
  • anything that needs a 3rd party dep.

Why? Complex validation reads better when is expressed as Go code, rather than in struct tags "perlisms".

Available Checks

Check Description Domain
- skip field validation any
required must NOT be IsZero() any
regex:<rx> must match <rx> string, Stringer
eq:<number> must == number CanInt, CanUint, CanFloat, CanLen
ne:<number> must != number same as eq
min:<number> must be >= number same as eq
max:<number> must be <= number same as eq
one_of:a|b|c must be one of {a,b,c} same as regex
uuid 32 (dash separated) hexdigits same as regex
email valid email address string, Stringer
url valid URL with scheme and host string, Stringer
ipv4 valid IPv4 address string, Stringer
ipv6 valid IPv6 address string, Stringer
ip valid IP address (v4 or v6) string, Stringer
mac valid MAC address string, Stringer
domain valid domain name same as regex
isbn valid ISBN-10 or ISBN-13 string, Stringer
alpha letters only same as regex
alphanum letters and numbers only same as regex
numeric numbers only same as regex
boolean valid boolean representation string, Stringer, numeric
creditcard valid credit card number string, Stringer, numeric
json valid JSON format string, Stringer, numeric
ascii ASCII characters only string, Stringer
lowercase lowercase characters only string, Stringer
uppercase uppercase characters only string, Stringer
hexadecimal valid hexadecimal string same as regex
base64 valid base64 string same as regex
mongoid valid MongoDB ObjectID same as regex
rgb valid RGB color same as regex
rgba valid RGBA color same as regex
luhn valid luhn string or number string, Stringer, numeric
ssn valid Social Security Number same as regex
npi valid NPI number string, Stringer, numeric
<your_own> you can easily add your own... ...

Multiple checks must be combined with a comma (,) extra space is forgiven, and empty checks are ignored i.e.: validate:"required,,,, uuid , one_of:foo|bar|baz" is fine, albeit unclean.

Both separators (between checks and between a check and its arguments) are configurable, whereas the separator between a check's arguments (the pipe symbol in the a|b|c example above) are up the each individual checker, the library doesn't care, it will just pass all the arguments as a string to the Checker func.

Sample Usage

s := struct {
	Foo struct {
		Bar string `validate:"required, one_of:foo|bar|baz"`
	}
	email string `validate:"required,email"`
}{}

if err := vali.Validate(s); err != nil {
    fmt.Println("oh noes!...")
}

Documentation

Documentation

Overview

Package vali is a tiny validation library.

It is pointer-insensitive, will always validate the value behind the pointer (i.e. *string required passes if string != "" not if *string != nil).

You can pass it a struct, a *struct, a *****struct, doesn't matter, it will always fast-forward to the value and ignore any pointers.

It is very small, but extensible, you can easily add your own checkers or "checker makers" (basically, checkers that can take arguments).

Index

Examples

Constants

View Source
const DefaultValidatorTagName = "validate"

DefaultValidatorTagName holds the default struct tag name.

Variables

View Source
var (
	ErrCheckFailed    = errors.New("check failed")
	ErrRequired       = errors.New("value missing")
	ErrInvalidChecker = errors.New("invalid checker")
	ErrInvalidCmp     = errors.New("invalid comparison")
)

Possible errors.

View Source
var DefaultDontSkipZero = []string{"required", "eq", "ne", "min", "max"}

DefaultDontSkipZero holds the default list of checks that do NOT skip the zero value. By default, checks are skipping it, unless they are in this list.

This allows checks to be used for optional fields as well, i.e.: `validate:"uuid"` will allow an empty string and only validate it as uuid if not empty. To BOTH require it to be present and be an uuid, you would combine `validate:"required,uuid"`.

In short, checks should be kept small, focused and composable and avoid overlapping their responsibilities.

View Source
var DefaultValidator = New()

DefaultValidator allows using the library directly, without creating a validator, similar to how flags and net/http packages work.

Functions

func Interface added in v1.5.0

func Interface(v reflect.Value) any

Interface returns the value as an interface{}, working around the limitation that unexported fields cannot use reflect.Value.Interface().

This is useful when implementing custom checkers that need to work with both exported and unexported fields.

Returns nil if the value cannot be extracted (e.g., complex unexported types).

func RegisterChecker

func RegisterChecker(name string, fn Checker)

RegisterChecker registers a new Checker to the DefaultValidator.

func RegisterCheckerMaker

func RegisterCheckerMaker(name string, fn CheckerMaker)

RegisterCheckerMaker registers a new CheckerMaker to the DefaultValidator.

func Validate

func Validate(val any, tags ...string) error

Validate validates v against DefaultValidator. See Validator.Validate for details.

Types

type Checker

type Checker func(reflect.Value) error

Checker repesents a basic checker (one that takes no arguments, i.e. "required").

func Eq added in v1.2.0

func Eq(arg string) (c Checker, err error)

Eq checks numbers for being == `arg` and things with a `len()` (`array`, `chan`, `map`, `slice`, `string`) for having len == `arg`.

func Max added in v1.2.0

func Max(arg string) (c Checker, err error)

Max checks numbers for being at most `arg` and things with a `len()` (`array`, `chan`, `map`, `slice`, `string`) for having len at most `arg`.

func Min added in v1.2.0

func Min(arg string) (c Checker, err error)

Min checks numbers for being at least `arg` and things with a `len()` (`array`, `chan`, `map`, `slice`, `string`) for having len at least `arg`.

func Ne added in v1.2.0

func Ne(arg string) (c Checker, err error)

Ne checks numbers for being != `arg` and things with a `len()` (`array`, `chan`, `map`, `slice`, `string`) for having len != `arg`.

func Regex

func Regex(arg string) (c Checker, err error)

Regex allows you to easily create regex-based checkers.

type CheckerMaker

type CheckerMaker func(args string) (Checker, error)

CheckerMaker is a way to construct checkers with arguments (i.e. "regex:^[A-Z]$").

type Validator added in v1.2.0

type Validator struct {

	// Separator between checks (a), cheks and their arguments (b). The check between
	// arguments themselves is not configurable (c), as that is ultimately up to each
	// individual checker (how to parse the arguments). The only builtin check that uses
	// it is `one_of` and that one requires it to be the pipe symbol.
	//
	//     `validate:"required(a)uuid(a)one_of(b)foo|bar|baz"` which defaults to:
	//     `validate:"required,uuid,one_of:foo|bar|baz"`
	CheckSep,
	CheckArgSep string

	// Checks in this list WILL be checked against the zero value.
	// By default, checks are not run against the zero value, unless they
	// are part of this list.
	DontSkipZeroChecks []string

	sync.RWMutex //nolint:embeddedstructfieldcheck // ok
	// contains filtered or unexported fields
}

Validator holds the validation context. You can create your own or use the default one provided by this library.

func New added in v1.2.0

func New(opts ...string) (v *Validator)

New creates a new Validator, initialized with the default checkers and ready to be used. You can optionally pass a struct tag name or use the DefaultValidatorTagName.

By default, it errors out if it encounters validation tags on private fields, but you can change that by setting the [Validator.ErrorOnPrivate] to false. The error will be of type [ErrPrivateField].

func (*Validator) RegisterChecker added in v1.2.0

func (v *Validator) RegisterChecker(name string, fn Checker)

RegisterChecker registers a new Checker to the Validator.

func (*Validator) RegisterCheckerMaker added in v1.2.0

func (v *Validator) RegisterCheckerMaker(name string, fn CheckerMaker)

RegisterCheckerMaker registers a new CheckerMaker to the Validator.

func (*Validator) Validate added in v1.2.0

func (v *Validator) Validate(val any, tags ...string) (err error)

Validate validates a struct. The passed value v can be a value or a pointer (or pointer to a pointer, although there's no point to do that in Go). It will validate all the fields that have the `s.tag` present, recursively.

Example
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

func main() {
	s := struct {
		Foo struct {
			Bar string `validate:"required"`
		}
	}{}
	err := vali.Validate(s)
	fmt.Println(err)
}
Output:

Foo.Bar: required check failed: value missing
Example (Custom_checker)
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

func main() {
	var phone string

	s := struct {
		Foo struct {
			Bar *string `validate:"phone"`
		}
	}{}
	s.Foo.Bar = &phone

	p, err := vali.Regex(`^\d{3}-?\d{3}-?\d{4}$`)
	if err != nil {
		fmt.Println(err)
	}

	vali.RegisterChecker("phone", p)

	phone = "123"
	err = vali.Validate(s)
	fmt.Println(err) // This should err.

	phone = "123-456-7890"
	err = vali.Validate(s)
	fmt.Println(err) // This should not.

}
Output:

Foo.Bar: phone check failed: "123" does not match ^\d{3}-?\d{3}-?\d{4}$
<nil>
Example (Custom_min)
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

func main() {
	s := struct {
		Foo struct {
			Bar int8 `validate:"min10"`
		}
	}{}

	min10, err := vali.Min("10")
	if err != nil {
		fmt.Println(err)
	}

	vali.RegisterChecker("min10", min10)

	s.Foo.Bar = 9
	err = vali.Validate(s)
	fmt.Println(err) // This should err.

	s.Foo.Bar = 10
	err = vali.Validate(s)
	fmt.Println(err) // This should not.

}
Output:

Foo.Bar: min10 check failed: 9 is less than 10
<nil>
Example (Interface)
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

type foo struct{}

func (foo) Foo() string {
	return "hello world"
}

func main() {
	type fooer interface {
		Foo() string
	}

	s := struct {
		F fooer `validate:"required"`
	}{}

	err := vali.Validate(s)
	fmt.Println(err) // This should err.

	s.F = foo{}
	err = vali.Validate(s)
	fmt.Println(err) // This should not.

}
Output:

F: required check failed: value missing
<nil>
Example (Luhn)
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

func main() {
	s := struct {
		CreditCard string `validate:"luhn"`
	}{}

	// Valid credit card number (passing Luhn algorithm).
	s.CreditCard = "4111 1111 1111 1111"
	err := vali.Validate(s)
	fmt.Println(err)

	// Invalid credit card number (failing Luhn algorithm).
	s.CreditCard = "4111 1111 1111 1112"
	if err = vali.Validate(s); err != nil {
		fmt.Println("Invalid")
	}

}
Output:

<nil>
Invalid
Example (Ssn_npi)
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

func main() {
	s := struct {
		SSN string `validate:"ssn"`
		NPI string `validate:"npi"`
	}{}

	// Valid SSN.
	s.SSN = "123-45-6789"
	err := vali.Validate(s)
	fmt.Println("Valid SSN:", err)

	// Invalid SSN format.
	s.SSN = "12345-6789"
	err = vali.Validate(s)
	fmt.Println("Invalid SSN:", err != nil)

	// Valid NPI (with 80840 prefix for Luhn check).
	s.SSN = ""
	s.NPI = "1234567893"
	err = vali.Validate(s)
	fmt.Println("Valid NPI:", err)

	// Invalid NPI.
	s.NPI = "12345"
	err = vali.Validate(s)
	fmt.Println("Invalid NPI:", err != nil)

}
Output:

Valid SSN: <nil>
Invalid SSN: true
Valid NPI: <nil>
Invalid NPI: true
Example (Unexported)
package main

import (
	"fmt"

	"github.com/alexaandru/vali"
)

func main() {
	s := struct {
		Foo struct {
			bar string `validate:"required,uuid"`
		}
	}{}

	s.Foo.bar = "550e8400-e29b-41d4-a716-446655440000"

	err := vali.Validate(s)
	fmt.Println(err) // Private fields with tags are validated!

	s.Foo.bar = "invalid"
	err = vali.Validate(s)
	fmt.Println(err) // Validation fails for invalid UUID.

}
Output:

<nil>
Foo.bar: uuid check failed: "invalid" does not match (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$

Jump to

Keyboard shortcuts

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