validating

package module
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2024 License: MIT Imports: 6 Imported by: 52

README

validating

A Go library for validating structs, maps and slices.

Features

  1. Simple

    Simple and stupid, no magic involved.

  2. Type-safe

    Schema is defined in Go, which is type-safer (and more powerful) than traditional struct tags.

  3. Flexible

    • Validators are composable.
    • Nested struct validation is well supported.
    • Schema can be defined inside or outside struct.
    • Validator customizations are made easy.
  4. No reflection

Installation

$ go get github.com/RussellLuo/validating/v3@latest

Quick Start

package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Address struct {
	Country string
	City    string
}

func (a Address) Schema() v.Schema {
	return v.Schema{
		v.F("country", a.Country): v.Nonzero[string]().Msg("empty country"),
		v.F("city", a.City):       v.In("A", "B", "C").Msg("must be A or B or C"),
	}
}

type Person struct {
	Name    string
	Age     int
	Address Address
}

func (p Person) Schema() v.Schema {
	return v.Schema{
		v.F("name", p.Name):       v.LenString(1, 5).Msg("bad name"),
		v.F("age", p.Age):         v.Gte(10).Msg("must be older than 10 years old"),
		v.F("address", p.Address): p.Address.Schema(),
	}
}

func main() {
	p := Person{}
	errs := v.Validate(p.Schema())
	for _, err := range errs {
		fmt.Println(err)
	}
}
$ go run main.go
name: INVALID(bad name)
age: INVALID(must be older than 10 years old)
address.country: INVALID(empty country)
address.city: INVALID(must be A or B or C)

Validator factories and validators

To be strict, this library has a conceptual distinction between validator factory and validator.

A validator factory is a function used to create a validator, which will do the actual validation.

Built-in validator factories
Extension validator factories
Validator customizations

Examples

Documentation

Check out the Godoc.

Thanks

This library borrows some ideas from the following libraries:

License

MIT

Documentation

Overview

Example (Customizations)
package main

import (
	"fmt"
	"time"

	v "github.com/RussellLuo/validating/v3"
)

func mapNonzero(field *v.Field) v.Errors {
	value, ok := field.Value.(map[string]time.Time)
	if !ok {
		var want map[string]time.Time
		return v.NewUnsupportedErrors("mapNonzero", field, want)
	}
	if len(value) == 0 {
		return v.NewInvalidErrors(field, "is zero valued")
	}
	return nil
}

type MyValidator struct{}

func (mv MyValidator) Validate(field *v.Field) v.Errors {
	return mapNonzero(field)
}

func main() {
	var value map[string]time.Time

	errs := v.Validate(v.Schema{
		v.F("value", value): v.Func(mapNonzero),
	})
	fmt.Printf("errs from the func-validator: %+v\n", errs)

	errs = v.Validate(v.Schema{
		v.F("value", value): MyValidator{},
	})
	fmt.Printf("errs from the struct-validator: %+v\n", errs)

}
Output:

errs from the func-validator: value: INVALID(is zero valued)
errs from the struct-validator: value: INVALID(is zero valued)
Example (NestedStruct)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Address struct {
	Country, Province, City string
}

type Person struct {
	Name    string
	Age     int
	Address Address
}

func main() {
	p := Person{}
	err := v.Validate(v.Schema{
		v.F("name", p.Name): v.LenString(1, 5),
		v.F("age", p.Age):   v.Gte(10),
		v.F("address", p.Address): v.Nested(func(addr Address) v.Validator {
			return v.Schema{
				v.F("country", addr.Country):   v.Nonzero[string](),
				v.F("province", addr.Province): v.Nonzero[string](),
				v.F("city", addr.City):         v.Nonzero[string](),
			}
		}),
	})
	fmt.Printf("err: %+v\n", err)

	//err: name: INVALID(has an invalid length), age: INVALID(is lower than the given value), address.country: INVALID(is zero valued), address.province: INVALID(is zero valued), address.city: INVALID(is zero valued)
}
Output:

Example (NestedStructMap)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Member struct {
	Name string
}

type Person1 struct {
	Name   string
	Age    int
	Family map[string]*Member
}

func makeSchema1(p *Person1) v.Schema {
	return v.Schema{
		v.F("name", p.Name): v.LenString(1, 5),
		v.F("age", p.Age):   v.Nonzero[int](),
		v.F("family", p.Family): v.EachMap[map[string]*Member](v.Nested(func(member *Member) v.Validator {
			return v.Schema{
				v.F("name", member.Name): v.LenString(10, 15).Msg("is too long"),
			}
		})),
	}
}

// The equivalent implementation using Map.
//
//nolint:golint,unused
func makeSchema1_Map(p *Person1) v.Schema {
	return v.Schema{
		v.F("name", p.Name): v.LenString(1, 5),
		v.F("age", p.Age):   v.Nonzero[int](),
		v.F("family", p.Family): v.Map(func(m map[string]*Member) map[string]v.Validator {
			schemas := make(map[string]v.Validator)
			for relation, member := range m {
				schemas[relation] = v.Schema{
					v.F("name", member.Name): v.LenString(10, 15).Msg("is too long"),
				}
			}
			return schemas
		}),
	}
}

func main() {
	p1 := Person1{}
	err := v.Validate(makeSchema1(&p1))
	fmt.Printf("err of p1: %+v\n", err)

	p2 := Person1{Family: map[string]*Member{
		"father": {"father's name"},
		"mother": {"mother's name is long"},
	}}
	err = v.Validate(makeSchema1(&p2))
	fmt.Printf("err of p2: %+v\n", err)

	//err of p1: name: INVALID(has an invalid length), age: INVALID(is zero valued)
	//err of p2: name: INVALID(has an invalid length), age: INVALID(is zero valued), family[mother].name: INVALID(is too long)
}
Output:

Example (NestedStructPointer)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Address2 struct {
	Country, Province, City string
}

type Person2 struct {
	Name    string
	Age     int
	Address *Address2
}

func makeSchema2(p *Person2) v.Schema {
	return v.Schema{
		v.F("name", p.Name): v.LenString(1, 5),
		v.F("age", p.Age):   v.Lte(50),
		v.F("address", p.Address): v.All(
			v.Nonzero[*Address2]().Msg("is nil"),
			v.Nested(func(addr *Address2) v.Validator {
				return v.Schema{
					v.F("country", addr.Country):   v.Nonzero[string](),
					v.F("province", addr.Province): v.Nonzero[string](),
					v.F("city", addr.City):         v.Nonzero[string](),
				}
			}),
		),
	}
}

func main() {
	p1 := Person2{}
	err := v.Validate(makeSchema2(&p1))
	fmt.Printf("err of p1: %+v\n", err)

	p2 := Person2{Age: 60, Address: &Address2{}}
	err = v.Validate(makeSchema2(&p2))
	fmt.Printf("err of p2: %+v\n", err)

	//err of p1: name: INVALID(has an invalid length), address: INVALID(is nil)
	//err of p2: name: INVALID(has an invalid length), age: INVALID(is greater than the given value), address.country: INVALID(is zero valued), address.province: INVALID(is zero valued), address.city: INVALID(is zero valued)
}
Output:

Example (NestedStructSchemaInside)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Address3 struct {
	Country, Province, City string
}

func (a *Address3) Schema() v.Schema {
	return v.Schema{
		v.F("country", a.Country):   v.Nonzero[string](),
		v.F("province", a.Province): v.Nonzero[string](),
		v.F("city", a.City):         v.Nonzero[string](),
	}
}

type Person3 struct {
	Name    string
	Age     int
	Address Address3
}

func (p *Person3) Schema() v.Schema {
	return v.Schema{
		v.F("name", p.Name):       v.LenString(1, 5),
		v.F("age", p.Age):         v.Gte(10),
		v.F("address", p.Address): p.Address.Schema(),
	}
}

func main() {
	p := Person3{}
	err := v.Validate(p.Schema())
	fmt.Printf("err: %+v\n", err)

	//err: name: INVALID(has an invalid length), age: INVALID(is lower than the given value), address.country: INVALID(is zero valued), address.province: INVALID(is zero valued), address.city: INVALID(is zero valued)
}
Output:

Example (NestedStructSlice)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Phone struct {
	Number, Remark string
}

type Person4 struct {
	Name   string
	Age    int
	Phones []*Phone
}

func makeSchema4(p *Person4) v.Schema {
	return v.Schema{
		v.F("name", p.Name): v.LenString(1, 5),
		v.F("age", p.Age):   v.Nonzero[int](),
		v.F("phones", p.Phones): v.EachSlice[[]*Phone](v.Nested(func(phone *Phone) v.Validator {
			return v.Schema{
				v.F("number", phone.Number): v.Nonzero[string](),
				v.F("remark", phone.Remark): v.LenString(5, 7),
			}
		})),
	}
}

// The equivalent implementation using Slice.
//
//nolint:golint,unused
func makeSchema4_Slice(p *Person4) v.Schema {
	return v.Schema{
		v.F("name", p.Name): v.LenString(1, 5),
		v.F("age", p.Age):   v.Nonzero[int](),
		v.F("phones", p.Phones): v.Slice(func(s []*Phone) (schemas []v.Validator) {
			for _, phone := range s {
				schemas = append(schemas, v.Schema{
					v.F("number", phone.Number): v.Nonzero[string](),
					v.F("remark", phone.Remark): v.LenString(5, 7),
				})
			}
			return
		}),
	}
}

func main() {
	p1 := Person4{}
	err := v.Validate(makeSchema4(&p1))
	fmt.Printf("err of p1: %+v\n", err)

	p2 := Person4{Phones: []*Phone{
		{"13011112222", "private"},
		{"13033334444", "business"},
	}}
	err = v.Validate(makeSchema4(&p2))
	fmt.Printf("err of p2: %+v\n", err)

	//err of p1: name: INVALID(has an invalid length), age: INVALID(is zero valued)
	//err of p2: name: INVALID(has an invalid length), age: INVALID(is zero valued), phones[1].remark: INVALID(has an invalid length)
}
Output:

Example (SimpleMap)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

func main() {
	ages := map[string]int{
		"foo": 0,
		"bar": 1,
	}
	err := v.Validate(v.Value(ages, v.EachMap[map[string]int](v.Nonzero[int]())))
	fmt.Printf("%+v\n", err)

}
Output:

[foo]: INVALID(is zero valued)
Example (SimpleSlice)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

func main() {
	names := []string{"", "foo"}
	err := v.Validate(v.Value(names, v.EachSlice[[]string](v.Nonzero[string]())))
	fmt.Printf("%+v\n", err)

}
Output:

[0]: INVALID(is zero valued)
Example (SimpleStringIsIP)
package main

import (
	"fmt"
	"net"

	v "github.com/RussellLuo/validating/v3"
)

func main() {
	isIP := func(value string) bool {
		return net.ParseIP(value) != nil
	}

	value := "192.168.0."
	err := v.Validate(v.Value(value, v.Is(isIP).Msg("invalid IP")))
	fmt.Printf("%+v\n", err)

}
Output:

INVALID(invalid IP)
Example (SimpleStruct)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

type Person5 struct {
	Name string
	Age  int
}

func main() {
	p := Person5{Age: 1}
	err := v.Validate(v.Schema{
		v.F("name", p.Name): v.LenString(1, 5).Msg("length is not between 1 and 5"),
		v.F("age", p.Age):   v.Nonzero[int](),
	})
	fmt.Printf("%+v\n", err)

}
Output:

name: INVALID(length is not between 1 and 5)
Example (SimpleValue)
package main

import (
	"fmt"

	v "github.com/RussellLuo/validating/v3"
)

func main() {
	err := v.Validate(v.Value(0, v.Range(1, 5)))
	fmt.Printf("%+v\n", err)

}
Output:

INVALID(is not between the given range)

Index

Examples

Constants

View Source
const (
	ErrUnsupported = "UNSUPPORTED"
	ErrInvalid     = "INVALID"
)

Variables

View Source
var And = All

And is an alias of All.

View Source
var Or = Any

Or is an alias of Any.

Functions

This section is empty.

Types

type AnyValidator

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

AnyValidator is a validator that allows users to change the returned errors by calling LastError().

func Any

func Any(validators ...Validator) *AnyValidator

Any is a composite validator factory used to create a validator, which will succeed as long as any sub-validator succeeds.

func (*AnyValidator) LastError

func (av *AnyValidator) LastError() *AnyValidator

LastError makes AnyValidator return the error from the last validator if all inner validators fail.

func (*AnyValidator) Validate

func (av *AnyValidator) Validate(field *Field) Errors

Validate delegates the actual validation to its inner validators.

type Error

type Error interface {
	error
	Field() string
	Kind() string
	Message() string
}

func NewError

func NewError(field, kind, message string) Error

type Errors

type Errors []Error

func NewErrors

func NewErrors(field, kind, message string) Errors

func NewInvalidErrors

func NewInvalidErrors(field *Field, msg string) Errors

func NewUnsupportedErrors

func NewUnsupportedErrors(validatorName string, field *Field, want ...any) Errors

func Validate

func Validate(v Validator) (errs Errors)

Validate invokes v.Validate with an empty field.

func (*Errors) Append

func (e *Errors) Append(errs ...Error)

func (Errors) Error

func (e Errors) Error() string

func (Errors) Map

func (e Errors) Map() map[string]Error

Map converts the given errors to a map[string]Error, where the keys of the map are the field names.

type Field

type Field struct {
	Name  string
	Value any
}

Field represents a (Name, Value) pair that needs to be validated.

func F

func F(name string, value any) *Field

F is a shortcut for creating a pointer to Field.

type Func

type Func func(field *Field) Errors

Func is an adapter to allow the use of ordinary functions as validators. If f is a function with the appropriate signature, Func(f) is a Validator that calls f.

func (Func) Validate

func (f Func) Validate(field *Field) Errors

Validate calls f(field).

type MessageValidator

type MessageValidator struct {
	Message   string
	Validator Validator
}

MessageValidator is a validator that allows users to customize the INVALID error message by calling Msg().

func Eq

func Eq[T comparable](value T) (mv *MessageValidator)

Eq is a leaf validator factory used to create a validator, which will succeed when the field's value equals the given value.

func Gt

func Gt[T constraints.Ordered](value T) (mv *MessageValidator)

Gt is a leaf validator factory used to create a validator, which will succeed when the field's value is greater than the given value.

func Gte

func Gte[T constraints.Ordered](value T) (mv *MessageValidator)

Gte is a leaf validator factory used to create a validator, which will succeed when the field's value is greater than or equal to the given value.

func In

func In[T comparable](values ...T) (mv *MessageValidator)

In is a leaf validator factory used to create a validator, which will succeed when the field's value is equal to one of the given values.

func Is

func Is[T any](f func(T) bool) (mv *MessageValidator)

Is is a leaf validator factory used to create a validator, which will succeed when the predicate function f returns true for the field's value.

func LenSlice

func LenSlice[T ~[]E, E any](min, max int) (mv *MessageValidator)

LenSlice is a leaf validator factory used to create a validator, which will succeed when the length of the slice field is between min and max.

func LenString

func LenString(min, max int) (mv *MessageValidator)

LenString is a leaf validator factory used to create a validator, which will succeed when the length of the string field is between min and max.

func Lt

func Lt[T constraints.Ordered](value T) (mv *MessageValidator)

Lt is a leaf validator factory used to create a validator, which will succeed when the field's value is lower than the given value.

func Lte

func Lte[T constraints.Ordered](value T) (mv *MessageValidator)

Lte is a leaf validator factory used to create a validator, which will succeed when the field's value is lower than or equal to the given value.

func Match

func Match(re *regexp.Regexp) (mv *MessageValidator)

Match is a leaf validator factory used to create a validator, which will succeed when the field's value matches the given regular expression.

func Ne

func Ne[T comparable](value T) (mv *MessageValidator)

Ne is a leaf validator factory used to create a validator, which will succeed when the field's value does not equal the given value.

func Nin

func Nin[T comparable](values ...T) (mv *MessageValidator)

Nin is a leaf validator factory used to create a validator, which will succeed when the field's value is not equal to any of the given values.

func Nonzero

func Nonzero[T comparable]() (mv *MessageValidator)

Nonzero is a leaf validator factory used to create a validator, which will succeed when the field's value is nonzero.

func Not

func Not(validator Validator) (mv *MessageValidator)

Not is a composite validator factory used to create a validator, which will succeed when the given validator fails.

func Range

func Range[T constraints.Ordered](min, max T) (mv *MessageValidator)

Range is a leaf validator factory used to create a validator, which will succeed when the field's value is between min and max.

func RuneCount

func RuneCount(min, max int) (mv *MessageValidator)

RuneCount is a leaf validator factory used to create a validator, which will succeed when the number of runes in the field's value is between min and max.

func Zero

func Zero[T comparable]() (mv *MessageValidator)

Zero is a leaf validator factory used to create a validator, which will succeed when the field's value is zero.

func (*MessageValidator) Msg

Msg sets the INVALID error message.

func (*MessageValidator) Validate

func (mv *MessageValidator) Validate(field *Field) Errors

Validate delegates the actual validation to its inner validator.

type Schema

type Schema map[*Field]Validator

Schema is a field mapping, which defines the corresponding validator for each field.

func Value

func Value(value any, validator Validator) Schema

Value is a shortcut function used to create a schema for a simple value.

func (Schema) Validate

func (s Schema) Validate(field *Field) (errs Errors)

Validate validates fields per the given according to the schema.

type Validator

type Validator interface {
	Validate(field *Field) Errors
}

Validator is an interface for representing a validating's validator.

func All

func All(validators ...Validator) Validator

All is a composite validator factory used to create a validator, which will succeed only when all sub-validators succeed.

func Array

func Array[T ~[]E, E any](f func(T) []Validator) Validator

Array is an alias of Slice.

func EachMap

func EachMap[T map[K]V, K comparable, V any](validator Validator) Validator

EachMap is a composite validator factory used to create a validator, which will apply the given validator to each element (i.e. the map value) of the map field.

Usually, for simplicity, it's recommended to use EachMap. If you have more complex validation rules for map elements, such as different validation for each value or validation specific to keys, then you should use Map.

func EachSlice

func EachSlice[T ~[]E, E any](validator Validator) Validator

EachSlice is a composite validator factory used to create a validator, which will apply the given validator to each element of the slice field.

Usually, for simplicity, it's recommended to use EachSlice. If you have more complex validation rules for slice elements, such as different validation for each element, then you should use Slice.

func Map

func Map[T map[K]V, K comparable, V any](f func(T) map[K]Validator) Validator

Map is a composite validator factory used to create a validator, which will do the validation per the schemas associated with a map.

func Nested

func Nested[T any](f func(T) Validator) Validator

Nested is a composite validator factory used to create a validator, which will delegate the actual validation to the validator returned by f.

func Slice

func Slice[T ~[]E, E any](f func(T) []Validator) Validator

Slice is a composite validator factory used to create a validator, which will do the validation per the schemas associated with a slice.

func ZeroOr

func ZeroOr[T comparable](validator Validator) Validator

ZeroOr is a composite validator factory used to create a validator, which will succeed if the field's value is zero, or if the given validator succeeds.

ZeroOr will return the error from the given validator if it fails.

Jump to

Keyboard shortcuts

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