validator

package module
v0.0.0-...-d7cdf38 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2024 License: BSD-3-Clause Imports: 11 Imported by: 0

README

go-validator

Yet another validator written in Go.

GoDev Actions Status Coverage Status

Features

  • strongly-typed validators by type parameters
  • i18n support in the standard way
  • handling multiple validation errors

Built-in validators

  • Required: validates comparable types if the value is not zero-value.
  • Length: validates strings if the length of the value is within the range.
  • MinLength: see Length.
  • MaxLength: see Length.
  • InRange: validates ordered types if the value is within the range.
  • Min: see InRange.
  • Max: see InRange.
  • In: validates comparable types if the value is in valid values.
  • Pattern: validates strings if it matches the regular expression.

Supported languages

  • English
  • Japanese

Example

import (
	"context"
	"fmt"

	"github.com/lufia/go-validator"
)

type OIDCProvider int

const (
	Google OIDCProvider = iota + 1
	Apple
	GitHub
)

type CreateUserRequest struct {
	Name     string
	Provider OIDCProvider
	Theme    string
}

var createUserRequestValidator = validator.Struct(func(s validator.StructRule, r *CreateUserRequest) {
	validator.AddField(s, &r.Name, "name", validator.Length[string](5, 20))
	validator.AddField(s, &r.Provider, "provider", validator.In(Google, Apple, GitHub))
	validator.AddField(s, &r.Theme, "theme", validator.In("light", "dark"))
})

func main() {
	var r CreateUserRequest
	err := createUserRequestValidator.Validate(context.Background(), &r)
	fmt.Println(err)
}

For more details, see the documentation.

Documentation

Overview

Package validator enables strongly-typed Validate methods to validate any types.

Builtin Validators

There are builtin validators.

  • In
  • InRange
  • Length
  • Max
  • MaxLength
  • Min
  • MinLength
  • Pattern
  • Required

When these builtin validators detects the value is invalid, they returns just an error corresponding to the validator. In other words, they don't return multiple errors wrapped by errors.Join.

Also there are few composition validators.

  • Join
  • Slice
  • Struct

These validators wraps multiple validators (including composition validators itself), so it could be contained multiple errors to a returned error from them.

To get the details:

// Join validator
err := validator.Join(validator.Min(3)).Validate(context.Background(), 2)
if e, ok := err.(interface{ Unwrap() []error }); ok {
	fmt.Println(e.Unwrap())
}

// Slice validator
v := validator.Slice(validator.Min(3))
err := v.Validate(context.Background(), []int{3, 2, 1})
if e, ok := err.(*validator.SliceError[[]int, int]); ok {
	fmt.Println(e.Errors)
}

// Struct validator
v := validator.Struct(func(s validator.StructRule, r *Data) {
	// ...
})
err := v.Validate(context.Background(), &Data{})
if e, ok := err.(*validator.StructError[*Data, Data]); ok {
	fmt.Println(e.Errors)
}

Custom validator

The New utility function makes it easy to implement custom validators.

We highly recommend to set custom error message with WithFormat to that validator. It also has default error message but it might be a unsufficient to your users.

Error message

The builtin- and compositon-validators has default error messages. Additionally these validators provides to modify its each default message to appropriate message on the situation.

For example:

v := validator.Min(3).WithFormat("%[1]v is not valid", validator.ByName("value"))

It is different for each the validator to be available argument names with ByName. See each the validator documentation.

Internationalization

The validators error messages are available in multiple languages.

The validator package assumes English is default language. To switch default language to another one, it is set Printer provided by golang.org/x/text/message to ctx that will be passed to the first argument of Validate[T] method.

Example
type (
	User struct {
		ID   string
		Name string
	}
	Request struct {
		User    *User
		Options []string
	}
)

var requestValidator = validator.Struct(func(s validator.StructRule, r *Request) {
	validator.AddField(s, &r.User, "user", validator.Struct(func(s validator.StructRule, u *User) {
		validator.AddField(s, &u.ID, "id", validator.Length[string](5, 10))
		validator.AddField(s, &u.Name, "name", validator.Required[string]())
	}))
	validator.AddField(s, &r.Options, "options",
		validator.Slice(validator.In("option1", "option2")))
})

var r Request
r.Options = []string{"option3"}
err := requestValidator.Validate(context.Background(), &r)
fmt.Println(err)
Output:

user: name: cannot be the zero value
user: id: the length must be in range(5 ... 10)
options: must be a valid value in [option1 option2]
Example (CustomMessage)
package main

import (
	"context"
	"fmt"

	"github.com/lufia/go-validator"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
)

func init() {
	message.SetString(language.English, "must be of length %[1]d to %[2]d", "must be of length %[1]d to %[2]d")
	message.SetString(language.Japanese, "must be of length %[1]d to %[2]d", "%[1]d文字以上%[2]d文字以内で入力してください")
}

func main() {
	type Data struct {
		Num  int
		Name string
	}
	v := validator.Struct(func(s validator.StructRule, r *Data) {
		validator.AddField(s, &r.Name, "name",
			validator.Length[string](3, 100).WithFormat("must be of length %[1]d to %[2]d", validator.ByName("min"), validator.ByName("max")),
		)
	})
	p := message.NewPrinter(language.English)
	ctx := validator.WithPrinter(context.Background(), p)
	err := v.Validate(ctx, &Data{
		Num:  10,
		Name: "xx",
	})
	fmt.Println(err)
}
Output:

name: must be of length 3 to 100
Example (CustomValidator)
package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/lufia/go-validator"
)

type CreateUserRequest struct {
	Name                 string
	Password             string
	ConfirmationPassword string
}

var (
	usernameValidator = validator.New(func(v string) bool {
		// find non-alnum or non-ascii character
		i := strings.IndexFunc(v, func(c rune) bool {
			switch {
			default:
				return true
			case c >= 'a' && c <= 'z':
				return false
			case c >= 'A' && c <= 'Z':
				return false
			case c >= '0' && c <= '9':
				return false
			}
		})
		return i < 0
	}).WithFormat("does not allow not-alphabets or not-digits")

	passwordValidator = validator.New(func(r *CreateUserRequest) bool {
		return r.Password == r.ConfirmationPassword
	}).WithFormat("passwords does not match")
)

var createUserRequestValidator = validator.Join(
	validator.Struct(func(s validator.StructRule, r *CreateUserRequest) {
		validator.AddField(s, &r.Name, "name",
			validator.Length[string](5, 20),
			usernameValidator)
		validator.AddField(s, &r.Password, "password",
			validator.MinLength[string](8))
		validator.AddField(s, &r.ConfirmationPassword, "confirmation-password",
			validator.MinLength[string](8))
	}),
	passwordValidator,
)

func main() {
	ctx := context.Background()
	err := createUserRequestValidator.Validate(ctx, &CreateUserRequest{
		Name:                 ".adm",
		Password:             "1234",
		ConfirmationPassword: "abcd",
	})
	fmt.Println(err)
}
Output:

name: the length must be in range(5 ... 20)
name: does not allow not-alphabets or not-digits
password: the length must be no less than 8
confirmation-password: the length must be no less than 8
passwords does not match
Example (Localized)
type (
	User struct {
		ID   string
		Name string
	}
	Request struct {
		User    *User
		Options []string
	}
)

var requestValidator = validator.Struct(func(s validator.StructRule, r *Request) {
	validator.AddField(s, &r.User, "user", validator.Struct(func(s validator.StructRule, u *User) {
		validator.AddField(s, &u.ID, "id", validator.Length[string](5, 10))
		validator.AddField(s, &u.Name, "name", validator.Required[string]())
	}))
	validator.AddField(s, &r.Options, "options",
		validator.Slice(validator.In("option1", "option2")))
})

p := message.NewPrinter(language.Japanese, message.Catalog(validator.DefaultCatalog))
ctx := validator.WithPrinter(context.Background(), p)

var r Request
r.Options = []string{"option3"}
err := requestValidator.Validate(ctx, &r)
fmt.Println(err)
Output:

user: name: 必須です
user: id: 長さは5以上10以内の制限があります
options: [option1 option2]のいずれかでなければなりません
Example (Separated)
type (
	User struct {
		ID   string
		Name string
	}
	Request struct {
		User    *User
		Options []string
	}
)

var (
	userValidator = validator.Struct(func(s validator.StructRule, u *User) {
		validator.AddField(s, &u.ID, "id", validator.Length[string](5, 10))
		validator.AddField(s, &u.Name, "name", validator.Required[string]())
	})
	requestValidator = validator.Struct(func(s validator.StructRule, r *Request) {
		validator.AddField(s, &r.User, "user", userValidator)
		validator.AddField(s, &r.Options, "options", validator.Slice(
			validator.In("option1", "option2"),
		))
		validator.AddField(s, &r.Options, "options", validator.Slice(
			validator.In("option1", "option2"),
		))
	})
)

var r Request
err := requestValidator.Validate(context.Background(), &r)
fmt.Println(err)
Output:

user: name: cannot be the zero value
user: id: the length must be in range(5 ... 10)

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	DefaultCatalog = catalog.NewBuilder(catalog.Fallback(defaultLanguage))
)

Functions

func AddField

func AddField[T any](s StructRule, p *T, name string, vs ...Validator[T])

AddField adds the p's field of the struct T.

func WithPrinter

func WithPrinter(ctx context.Context, p Printer) context.Context

Types

type Arg

type Arg interface {
	ValueOf(v any) any
}

func ByName

func ByName(name string) Arg

type Error

type Error interface {
	error
}

Error is the interface that wraps Error method.

type OrderedMap

type OrderedMap[K comparable, V any] struct {
	// contains filtered or unexported fields
}

OrderedMap is a map that guarantee that the iteration order of entries will be the order in which they were set.

func (*OrderedMap[K, V]) Get

func (m *OrderedMap[K, V]) Get(key K) (V, bool)

Get returns a value associated to key.

func (*OrderedMap[K, V]) Keys

func (m *OrderedMap[K, V]) Keys() []K

Keys returns keys of m. These keys preserves the order in which they were set.

func (*OrderedMap[K, V]) Len

func (m *OrderedMap[K, V]) Len() int

Len returns the length of m.

type Printer

type Printer interface {
	Fprintf(w io.Writer, key message.Reference, a ...any) (int, error)
}

Printer is the interface that wraps Fprintf method.

type SliceError

type SliceError[S ~[]T, T any] struct {
	Value  S
	Errors *OrderedMap[int, error]
}

SliceError reports an error is caused in Slice validator.

func (SliceError[S, T]) Error

func (e SliceError[S, T]) Error() string

Error implements the error interface.

func (SliceError[S, T]) Unwrap

func (e SliceError[S, T]) Unwrap() []error

Unwrap returns each errors of err.

type StructError

type StructError[P ~*T, T any] struct {
	Value  P
	Errors map[string]error
}

StructError reports an error is caused in Struct validator.

func (StructError[P, T]) Error

func (e StructError[P, T]) Error() string

Error implements the error interface.

func (StructError[P, T]) Unwrap

func (e StructError[P, T]) Unwrap() []error

Unwrap returns each errors of err.

type StructRule

type StructRule interface {
	// contains filtered or unexported methods
}

StructRule is the interface to add its fields.

type Validator

type Validator[T any] interface {
	Validate(ctx context.Context, v T) error
	WithFormat(key message.Reference, a ...Arg) Validator[T]
}

Validator is the interface that wraps the basic Validate method.

func In

func In[T comparable](a ...T) Validator[T]

In returns the validator to verify the value is in a.

Two named args are available in its error format.

  • validValues: specified valid values (type []T)
  • value: user input (type T)

func InRange

func InRange[T ordered](min, max T) Validator[T]

InRange returns the validator to verify the value is within min and max.

Three named args are available in its error format.

  • min: specified min value (type T)
  • max: specified max value (type T)
  • value: user input (type T)

func Join

func Join[T any](vs ...Validator[T]) Validator[T]

Join bundles vs to a validator.

func Length

func Length[T ~string](min, max int) Validator[T]

Length returns the validator to verify the length of the value is within min and max.

Three named args are available in its error format.

  • min: specified min value (type int)
  • max: specified max value (type int)
  • value: user input (type T)

func Max

func Max[T ordered](n T) Validator[T]

Max returns the validator to verify the value is less or equal than n.

Two named args are available in its error format.

  • max: specified max value (type T)
  • value: user input (type T)

func MaxLength

func MaxLength[T ~string](n int) Validator[T]

MaxLength returns the validator to verify the length of the value is less or equal than n.

Two named args are available in its error format.

  • max: specified max value (type int)
  • value: user input (type T)

func Min

func Min[T ordered](n T) Validator[T]

Min returns the validator to verify the value is greater or equal than n.

Two named args are available in its error format.

  • min: specified min value (type T)
  • value: user input (type T)

func MinLength

func MinLength[T ~string](n int) Validator[T]

MinLength returns the validator to verify the length of the value is greater or equal than n.

Two named args are available in its error format.

  • min: specified min value (type int)
  • value: user input (type T)

func New

func New[T any](fn func(v T) bool) Validator[T]

New returns the validator to verify the value with fn. If fn returns false, the validator results as error.

A named args is available in its error format.

  • value: user input (type T)

func Pattern

func Pattern[T ~string](re *regexp.Regexp) Validator[T]

Pattern returns the validator to verify the value matches re.

Two named args are available in its error format.

  • pattern: specific regular expression (type *regexp.Regexp)
  • value: user input (type T)

func PatternString

func PatternString[T ~string](pattern string) Validator[T]

PatternString returns the validator to verify the value matches pattern.

When pattern is invalid, PatternString panics.

func Required

func Required[T comparable]() Validator[T]

Required returns the validator to verify the value is not zero value.

A named arg is available in its error format.

  • value: user input (type T)

func Slice

func Slice[T any](vs ...Validator[T]) Validator[[]T]

func Struct

func Struct[P ~*T, T any](build func(s StructRule, p P)) Validator[P]

Struct returns the validator to verify that the struct satisfies rules constrated with build.

Three named args are available in its error format.

  • name: the registered field name (type string)
  • value: user input
  • error: occurred validation error(s) (type error)

Jump to

Keyboard shortcuts

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