valis

package module
v0.9.1 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2022 License: Apache-2.0 Imports: 11 Imported by: 1

README

valis

CircleCI Go Report Card PkgGoDev

Valis is a validation framework for Go.

Overviews

  • 👌 Validation in various ways
    • struct tag, Validate methods, and various other ways.
  • 🔧 Excellent customizability.
  • 🌎 Support translations.

Motivations

Validation in various ways

go-playground/validator is a great validation framework, but it only supports the way using struct tag.
go-ozzo/ozzo-validation supports the way using Validate methods, but it does not have enough features to use struct tag.
Valis supports to validate in various ways due to its excellent extensibility.

Create validation rules from multiple struct tags

When using various libraries, it is often the case that constraints with the same content are described by other struct tags.
For example, required:"true" validate:"required".

What this !?!?
Can you guarantee that both are always in agreement?

Valis solves this by supporting to read all tags.

Customizability is power

Performance is important, but it's faster not to use the library.
Therefore, Valis take emphasis on customizability.

So, requests for customizability are also welcomed.

Installation

To install it, run:

go get -u github.com/soranoba/valis

Usage

Basic
package main

import (
	"fmt"
	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

func main() {
	type User struct {
		Name string
		Age  int
	}

	u := &User{}
	if err := valis.Validate(
		&u,
		valis.Field(&u.Name, is.NonZero),
		valis.Field(&u.Age, is.Min(20)),
	); err != nil {
		fmt.Println(err)
	}
}
Struct tag
package main

import (
	"fmt"
	"github.com/soranoba/valis"
	"github.com/soranoba/valis/tagrule"
	"github.com/soranoba/valis/when"
)

func main() {
	type User struct {
		Name    *string `required:"true"`
		Age     int     `validate:"min=20"`
		Company struct {
			Location *string `required:"true"`
		}
	}

	v := valis.NewValidator()
	// Use the CommonRule if you want to automatically search and validate all hierarchies.
	v.SetCommonRules(
		when.IsStruct(valis.EachFields(tagrule.Required, tagrule.Validate)).
			ElseWhen(when.IsSliceOrArray(valis.Each( /* only common rules */ ))).
			ElseWhen(when.IsMap(valis.EachValues( /* only common rules */ ))),
	)

	user := User{}
	if err := v.Validate(&user); err != nil {
		fmt.Println(err)
	}
}

Please refer to documents for other usages.

Documentation

Overview

Example
package main

import (
	"fmt"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

func main() {
	type User struct {
		Name string
		Age  int
	}

	u := &User{}
	if err := valis.Validate(
		&u,
		valis.Field(&u.Name, is.NonZero),
		valis.Field(&u.Age, is.Min(20)),
	); err != nil {
		fmt.Println(err)
	}

	u.Name = "Alice"
	u.Age = 20
	if err := valis.Validate(
		&u,
		valis.Field(&u.Name, is.NonZero),
		valis.Field(&u.Age, is.Min(20)),
	); err != nil {
		fmt.Println(err)
	}

}
Output:

(non_zero) .Name can't be blank (or zero)
(gte) .Age must be greater than or equal to 20
Example (Convert)
package main

import (
	"fmt"
	"github.com/soranoba/valis/to"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

func main() {
	arr := []interface{}{0, 1, 2, 3, "1", "2", "3"}

	v := valis.NewValidator()
	if err := v.Validate(&arr, valis.Each(to.Int(is.Min(2)))); err != nil {
		fmt.Println(err)
	}

}
Output:

(gte) [0] must be greater than or equal to 2
(gte) [1] must be greater than or equal to 2
(gte) [4] must be greater than or equal to 2
Example (CustomizeError)
package main

import (
	"fmt"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

func main() {
	type User struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}

	v := valis.NewValidator()
	v.SetErrorCollectorFactoryFunc(func() valis.ErrorCollector {
		// You can create ErrorCollectors yourself that will generate your own error.
		// If you want to change only the attribute name, please change NameResolver.
		return valis.NewStandardErrorCollector(valis.JSONLocationNameResolver)
	})

	u := User{}
	if err := v.Validate(
		&u,
		valis.Field(&u.Name, is.NonZero),
		valis.Field(&u.Age, is.Min(20)),
	); err != nil {
		fmt.Println(err)
	}

}
Output:

(non_zero) .name can't be blank (or zero)
(gte) .age must be greater than or equal to 20
Example (Flow)
package main

import (
	"fmt"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
	"github.com/soranoba/valis/when"
)

func main() {
	arr := []interface{}{0, 1, 2, 3, "a", "b", "c", "A", "B", "C"}

	v := valis.NewValidator()

	if err := v.Validate(
		&arr,
		valis.Each(
			when.IsNumeric(is.In(1, 2, 3)).
				Else(is.In("a", "b", "c")),
		),
	); err != nil {
		fmt.Println(err)
	}

}
Output:

(inclusion) [0] is not included in [1 2 3]
(inclusion) [7] is not included in [a b c]
(inclusion) [8] is not included in [a b c]
(inclusion) [9] is not included in [a b c]
Example (NestedStruct)
package main

import (
	"fmt"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/tagrule"
	"github.com/soranoba/valis/when"
)

func main() {
	type User struct {
		Name    *string `required:"true"`
		Age     int     `validate:"min=20"`
		Company struct {
			Location *string `required:"true"`
		}
	}

	v := valis.NewValidator()
	// Use the CommonRule if you want to automatically search and validate all hierarchies.
	v.SetCommonRules(when.IsStruct(valis.EachFields(tagrule.Required, tagrule.Validate)))

	user := User{}
	if err := v.Validate(&user); err != nil {
		fmt.Println(err)
	}

}
Output:

(required) .Name is required
(gte) .Age must be greater than or equal to 20
(required) .Company.Location is required
Example (StructTag)
package main

import (
	"fmt"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/tagrule"
	"github.com/soranoba/valis/when"
)

func main() {
	type User struct {
		Name *string `required:"true"`
		Age  int     `validate:"min=20"`
	}

	v := valis.NewValidator()
	u := User{}
	if err := v.Validate(&u, when.IsStruct(valis.EachFields(tagrule.Required, tagrule.Validate))); err != nil {
		fmt.Println(err)
	}

}
Output:

(required) .Name is required
(gte) .Age must be greater than or equal to 20
Example (Translate)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
	"github.com/soranoba/valis/translations"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
	"golang.org/x/text/message/catalog"
)

func main() {
	type User struct {
		Name string
		Age  int
	}

	// It is recommended to have the catalog in a global variable etc. instead of creating it every time.
	// We welcome your contributions if it does not exist the language you use.
	c := translations.NewCatalog(catalog.Fallback(language.English))
	for _, f := range translations.AllPredefinedCatalogRegistrationFunc {
		c.Set(f)
	}

	u := User{}
	if err := valis.Validate(
		&u,
		valis.Field(&u.Name, is.NonZero),
		valis.Field(&u.Age, is.Min(20)),
	); err != nil {
		for _, lang := range []language.Tag{language.English, language.Japanese} {
			p := message.NewPrinter(lang, message.Catalog(c))
			// When you change the ErrorCollector and create errors other than ValidationError, you need an alternative.
			m := err.(*valis.ValidationError).Translate(p)
			b, _ := json.MarshalIndent(m, "", "  ")
			fmt.Printf("%s\n", b)
		}
	}

}
Output:

{
  ".Age": [
    "must be greater than or equal to 20"
  ],
  ".Name": [
    "can't be blank (or zero)"
  ]
}
{
  ".Age": [
    "は20より大きい値にする必要があります"
  ],
  ".Name": [
    "を空白にすることはできません"
  ]
}
Example (Validatable)
package main

import (
	"errors"
	"fmt"

	"github.com/soranoba/valis"
)

type ValidatableUser struct {
	Name string
}

func (u *ValidatableUser) Validate() error {
	if u.Name == "" {
		return errors.New("name is empty")
	}
	return nil
}

func main() {
	v := valis.NewValidator()

	// *ValidatableUser is implemented `Validate() error`
	// Validate returns nil, when Name is not empty. Otherwise, it returns an error.
	user := ValidatableUser{}
	if err := v.Validate(&user, valis.ValidatableRule); err != nil {
		fmt.Println(err)
	}

}
Output:

(custom) name is empty

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddCommonRules

func AddCommonRules(rules ...Rule)

AddCommonRules add the rules to common rules of the StandardValidator. See Validator.AddCommonRules

func NewFieldTagRule

func NewFieldTagRule(key string, tagHandler FieldTagHandler) *fieldTagRule

NewFieldTagRule returns a new rule related to the field tag. The rule verifies the value when it is a field value and has the specified tag.

Example
package main

import (
	"errors"
	"fmt"
	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

type TagValueHandler struct {
}

func (h *TagValueHandler) ParseTagValue(tagValue string) ([]valis.Rule, error) {
	if tagValue == "true" {
		return []valis.Rule{is.Required}, nil
	}
	return nil, errors.New("invalid required tag")
}

func main() {
	type User struct {
		Name *string `required:"true"`
		Age  *int    `required:"true"`
	}

	v := valis.NewValidator()
	u := User{}
	requiredTagRule := valis.NewFieldTagRule("required", &TagValueHandler{})

	if err := v.Validate(
		&u,
		valis.Field(&u.Name, requiredTagRule),
		valis.Field(&u.Age, requiredTagRule),
	); err != nil {
		fmt.Println(err)
	}

}
Output:

(required) .Name is required
(required) .Age is required

func SetErrorCollectorFactoryFunc

func SetErrorCollectorFactoryFunc(f ErrorCollectorFactoryFunc)

SetErrorCollectorFactoryFunc is update ErrorCollectorFactoryFunc of the StandardValidator. See Validator.SetErrorCollectorFactoryFunc

func Validate

func Validate(value interface{}, rules ...Rule) error

Validate the value using the StandardValidator. See Validator.Validate

Types

type CloneOpts

type CloneOpts struct {
	// When InheritLocation is true, Clone keeps the Location.
	InheritLocation bool
	// When InheritErrorCollector is true, Clone keeps the ErrorCollector.
	InheritErrorCollector bool
	// When Location is not nil and InheritLocation is false, Clone set the Location to the new Validator.
	Location *Location
	// When ErrorCollector is not nil and InheritErrorCollector is false, Clone set the ErrorCollector to the new Validator.
	ErrorCollector ErrorCollector
}

CloneOpts is an option of Clone.

type CombinationRule

type CombinationRule func(rules ...Rule) Rule

CombinationRule is a high-order function that returns a new Rule.

type ConvertFunc

type ConvertFunc func(value interface{}) (interface{}, error)

type Error

type Error = *errorDetail

func NewError

func NewError(code string, value interface{}, params ...interface{}) Error

type ErrorCollector

type ErrorCollector interface {
	HasError() bool
	Add(loc *Location, err Error)
	MakeError() error
}

ErrorCollector is an interface that receives some Error of each rule and creates the error returned by Validator.Validate.

func NewStandardErrorCollector

func NewStandardErrorCollector(nameResolver LocationNameResolver) ErrorCollector

NewStandardErrorCollector returns an ErrorCollector used by default.

type ErrorCollectorFactoryFunc

type ErrorCollectorFactoryFunc func() ErrorCollector

type FieldTagHandler added in v0.2.0

type FieldTagHandler interface {
	ParseTagValue(tagValue string) ([]Rule, error)
}

FieldTagHandler is an interface to be registered in FieldTagRule. It has a role in creating a Rule from the TagValue.

type Location

type Location struct {
	// contains filtered or unexported fields
}

Location indicates the location of the validating value.

func (*Location) Field

func (loc *Location) Field() *reflect.StructField

Field returns a reflect.StructField when the Kind is LocationKindField. Otherwise, occur panics.

func (*Location) FieldLocation

func (loc *Location) FieldLocation(field *reflect.StructField) *Location

FieldLocation returns a new Location that indicates the value at the field in the struct.

func (*Location) Index

func (loc *Location) Index() int

Index returns a Index when the Kind is LocationKindIndex. Otherwise, occur panics.

func (*Location) IndexLocation

func (loc *Location) IndexLocation(index int) *Location

IndexLocation returns a new Location that indicates the value at the index in the array or slice.

func (*Location) Key

func (loc *Location) Key() interface{}

Key returns a Key when the Kind is LocationKindMapKey or LocationKindMapValue. Otherwise, occur panics.

func (*Location) Kind

func (loc *Location) Kind() LocationKind

Kind returns a LocationKind of the Location.

func (*Location) MapKeyLocation

func (loc *Location) MapKeyLocation(key interface{}) *Location

MapKeyLocation returns a new Location that indicates the key.

func (*Location) MapValueLocation

func (loc *Location) MapValueLocation(key interface{}) *Location

MapValueLocation returns a new Location that indicates the value of the key.

func (*Location) Parent

func (loc *Location) Parent() *Location

Parent returns a parent location, when the Kind is not LocationKindRoot. Otherwise, occur panics.

type LocationError

type LocationError struct {
	Error
	Location *Location
}

type LocationKind

type LocationKind int

LocationKind is the type of Location.

const (
	// LocationKindRoot means the location of the root.
	LocationKindRoot LocationKind = iota
	// LocationKindField means the location of the field of the a struct.
	LocationKindField
	// LocationKindIndex means the location of the value in an array or slice.
	LocationKindIndex
	// LocationKindMapKey means the location of the key in a map.
	LocationKindMapKey
	// LocationKindMapValue means the location of the value in a map.
	LocationKindMapValue
)

type LocationNameResolver

type LocationNameResolver interface {
	ResolveLocationName(loc *Location) string
}

LocationNameResolver is an interface that creates a string corresponding to Location.

var (
	// DefaultLocationNameResolver is a LocationNamResolver used by default
	DefaultLocationNameResolver LocationNameResolver = &defaultLocationNameResolver{}
	// JSONLocationNameResolver is a LocationNameResolver that creates LocationNames using the json tag
	JSONLocationNameResolver LocationNameResolver = &jsonLocationNameResolver{}
	// RequestLocationNameResolver is a LocationNameResolver that creates LocationNames using the json and query tag
	RequestLocationNameResolver LocationNameResolver = &requestLocationNameResolver{}
)

type Rule

type Rule interface {
	Validate(validator *Validator, value interface{})
}

Rule is an interface where verification contents are defined.

var (
	// ValidatableRule is a rule that delegates to Validate methods when verifying.
	// See also Validatable and ValidatableWithValidator.
	ValidatableRule Rule = &validatableRule{}
)

func And

func And(rules ...Rule) Rule

And returns a new rule that verifies the value meets the rules and all common rules. Should only use it in your own rules, because to avoid validating common rules multiple times.

func Each

func Each(rules ...Rule) Rule

Each returns a new rule that verifies all elements of the array or slice meet the rules and all common rules.

func EachFields

func EachFields(rules ...Rule) Rule

EachFields returns a new rule that verifies all field values of the struct meet the rules and all common rules.

func EachKeys

func EachKeys(rules ...Rule) Rule

EachKeys returns a new rule that verifies all keys of the map meet the rules and all common rules.

func EachValues

func EachValues(rules ...Rule) Rule

EachValues returns a new rule that verifies all values of the map meet the rules and all common rules.

func Field

func Field(fieldPtr interface{}, rules ...Rule) Rule

Field returns a new rule that verifies the filed value meets the rules and all common rules.

func Index added in v0.6.0

func Index(idx int, rules ...Rule) Rule

Index returns a new rule that verifies the value at the index meets the rules and all common rules.

Example
package main

import (
	"fmt"
	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

func main() {
	v := valis.NewValidator()
	if err := v.Validate([]int{4, 2, 1, 3}, valis.Index(2, is.GreaterThan(2))); err != nil {
		fmt.Println(err)
	}
	if err := v.Validate([...]int{4, 2, 1, 3}, valis.Index(3, is.LessThan(2))); err != nil {
		fmt.Println(err)
	}
	if err := v.Validate([...]int{4, 2, 1, 3}, valis.Index(5, is.LessThan(2))); err != nil {
		fmt.Println(err)
	}

}
Output:

(gt) [2] must be greater than 2
(lt) [3] must be less than 2
(out_of_range) requires more than 5 elements

func IndexIfExist added in v0.6.0

func IndexIfExist(idx int, rules ...Rule) Rule

IndexIfExist returns a new rule. When the value at the index does not exist, the rule only checks the type of value. Otherwise, the rule same as the rule returned by the Index method.

Example
package main

import (
	"fmt"
	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
)

func main() {
	v := valis.NewValidator()
	if err := v.Validate([]int{4, 2, 1, 3}, valis.IndexIfExist(2, is.GreaterThan(2))); err != nil {
		fmt.Println(err)
	}
	if err := v.Validate([...]int{4, 2, 1, 3}, valis.IndexIfExist(3, is.LessThan(2))); err != nil {
		fmt.Println(err)
	}
	if err := v.Validate([...]int{4, 2, 1, 3}, valis.IndexIfExist(5, is.LessThan(2))); err != nil {
		fmt.Println(err)
	}

}
Output:

(gt) [2] must be greater than 2
(lt) [3] must be less than 2

func Key

func Key(key interface{}, rules ...Rule) Rule

Key returns a new rule that verifies the value of the key meets the rules and all common rules.

func Or

func Or(rules ...Rule) Rule

Or returns a new rule that verifies the value meets the rules at least one.

func To

func To(convertFunc ConvertFunc, rules ...Rule) Rule

To returns a new rule that verifies the converted value met all rules and all common rules.

type Validatable

type Validatable interface {
	Validate() error
}

Validatable will be delegated the validation by the ValidatableRule if implemented.

type ValidatableWithValidator

type ValidatableWithValidator interface {
	Validate(validator *Validator)
}

ValidatableWithValidator will be delegated the validation by the ValidatableRule if implemented.

type ValidationError

type ValidationError struct {
	// contains filtered or unexported fields
}

ValidationError is an error returned by Validator.Validate by default.

func NewValidationError

func NewValidationError(nameResolver LocationNameResolver, errors []*LocationError) *ValidationError

NewValidationError returns a new ValidationError.

func (*ValidationError) Details

func (e *ValidationError) Details() []*LocationError

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Translate

func (e *ValidationError) Translate(p *message.Printer) map[string][]string

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator provides validation methods. And, each rule uses a validator to save the error details.

func NewValidator

func NewValidator() *Validator

NewValidator returns a new Validator.

func (*Validator) AddCommonRules

func (v *Validator) AddCommonRules(rules ...Rule)

AddCommonRules add the rules to common rules.

func (*Validator) Clone

func (v *Validator) Clone(opts *CloneOpts) *Validator

Clone returns a new Validator inheriting the settings.

func (*Validator) DiveField

func (v *Validator) DiveField(field *reflect.StructField, f func(v *Validator))

DiveField moves from the current position to the next location specified the field and performs validation processing. Do not use it outside of Rules.

func (*Validator) DiveIndex

func (v *Validator) DiveIndex(index int, f func(v *Validator))

DiveIndex moves from the current position to the next location specified the index and performs validation processing. Do not use it outside of Rules.

func (*Validator) DiveMapKey

func (v *Validator) DiveMapKey(key interface{}, f func(v *Validator))

DiveMapKey moves from the current position to the next location specified the key and performs validation processing. Do not use it outside of Rules.

func (*Validator) DiveMapValue

func (v *Validator) DiveMapValue(key interface{}, f func(v *Validator))

DiveMapValue moves from the current position to the next location specified the key and performs validation processing. Do not use it outside of Rules.

func (*Validator) ErrorCollector

func (v *Validator) ErrorCollector() ErrorCollector

ErrorCollector returns an ErrorCollector.

func (*Validator) Location

func (v *Validator) Location() *Location

Location returns a current location.

func (*Validator) SetCommonRules

func (v *Validator) SetCommonRules(rules ...Rule)

SetCommonRules is update common rules.

func (*Validator) SetErrorCollectorFactoryFunc

func (v *Validator) SetErrorCollectorFactoryFunc(f ErrorCollectorFactoryFunc)

SetErrorCollectorFactoryFunc is update ErrorCollectorFactoryFunc.

func (*Validator) Validate

func (v *Validator) Validate(value interface{}, rules ...Rule) error

Validate the value. It returns an error if any rules are not met.

type WhenContext added in v0.2.0

type WhenContext struct {
	// contains filtered or unexported fields
}

WhenContext is an argument of conditions used by WhenRule.

func (*WhenContext) Location added in v0.2.0

func (ctx *WhenContext) Location() *Location

Location returns a current location.

func (*WhenContext) Value added in v0.2.0

func (ctx *WhenContext) Value() interface{}

Value returns the validating value.

type WhenRule

type WhenRule struct {
	// contains filtered or unexported fields
}

WhenRule is a rule that verifies the value meets the rules when conditions return true.

func If

func If(cond func(ctx *WhenContext) bool, rules ...Rule) *WhenRule

If is equiv to When

func When

func When(cond func(ctx *WhenContext) bool, rules ...Rule) *WhenRule

When returns a new Rule verify the value meets the rules when cond returns true.

Example
package main

import (
	"fmt"
	"github.com/soranoba/valis"
	"github.com/soranoba/valis/is"
	"reflect"
)

func main() {
	v := valis.NewValidator()

	isInt := func(ctx *valis.WhenContext) bool {
		return reflect.ValueOf(ctx.Value()).Kind() == reflect.Int
	}

	if err := v.Validate(0, valis.When(isInt, is.NonZero)); err != nil {
		fmt.Println(err)
	}
	if err := v.Validate("1", valis.When(isInt, is.NonZero).Else(is.In("a", "b", "c"))); err != nil {
		fmt.Println(err)
	}

}
Output:

(non_zero) can't be blank (or zero)
(inclusion) is not included in [a b c]

func (WhenRule) Else

func (r WhenRule) Else(rules ...Rule) Rule

Else set the rules verified when all conditions return false, and returns self.

func (*WhenRule) ElseIf

func (r *WhenRule) ElseIf(cond func(ctx *WhenContext) bool, rules ...Rule) *WhenRule

ElseIf set some Rule verified when all before conditions return false and cond returns true. And it returns self.

func (*WhenRule) ElseWhen

func (r *WhenRule) ElseWhen(rule *WhenRule) *WhenRule

ElseWhen set the WhenRule that verified when all before conditions, and returns self.

func (WhenRule) Validate

func (r WhenRule) Validate(validator *Validator, value interface{})

See Rule.Validate

Directories

Path Synopsis
Package code define error codes.
Package code define error codes.
Package valishelpers provides utilities.
Package valishelpers provides utilities.
The package implements some valis.Rule.
The package implements some valis.Rule.
Package tagrule implements some valis.Rule related to field tag.
Package tagrule implements some valis.Rule related to field tag.
Package to implements some valis.CombinationRule that verifies the converted value.
Package to implements some valis.CombinationRule that verifies the converted value.
Package translations provides for translations.
Package translations provides for translations.
Package when implements some valis.WhenRule.
Package when implements some valis.WhenRule.

Jump to

Keyboard shortcuts

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