Categorygithub.com/censync/go-validator
modulepackage
1.0.0
Repository: https://github.com/censync/go-validator.git
Documentation: pkg.go.dev

# README

Package validator implements value validations based on struct tags.

In code it is often necessary to validate that a given value is valid before using it for something. A typical example might be something like this.

if age < 18 {
	return error.New("age cannot be under 18")
}

This is a simple enough example, but it can get significantly more complex, especially when dealing with structs.

l := len(strings.Trim(s.Username))
if l < 3 || l > 40  || !regexp.MatchString("^[a-zA-Z]$", s.Username) ||	s.Age < 18 || s.Password {
	return errors.New("Invalid request")
}

You get the idea. Package validator allows one to define valid values as struct tags when defining a new struct type.

type NewUserRequest struct {
	Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]$"`
	Name string     `validate:"nonzero"`
	Age int         `validate:"min=18"`
	Password string `validate:"min=8"`
}

Then validating a variable of type NewUserRequest becomes trivial.

nur := NewUserRequest{Username: "something", ...}
if valid, _ := validator.Validate(nur); valid {
	// do something
}

Builtin validator functions

Here is the list of validator functions builtin in the package.

max
	For numeric numbers, max will simply make sure that the value is
	equal to the parameter given. For strings, it checks that
	the string length is exactly that number of characters. For slices,
	arrays, and maps, validates the number of items. (Usage: len=10)

max
	For numeric numbers, max will simply make sure that the value is
	lesser or equal to the parameter given. For strings, it checks that
	the string length is at most that number of characters. For slices,
	arrays, and maps, validates the number of items. (Usage: max=10)

min
	For numeric numbers, min will simply make sure that the value is
	greater or equal to the parameter given. For strings, it checks that
	the string length is at least that number of characters. For slices,
	arrays, and maps, validates the number of items. (Usage: min=10)

nonzero
	This validates that the value is not zero. The appropriate zero value
	is given by the Go spec (e.g. for int it's 0, for string it's "", for
	pointers is nil, etc.) Usage: nonzero

regexp
	Only valid for string types, it will validate that the value matches
	the regular expression provided as parameter. (Usage: regexp=^a.*b$)

in
	For string, int, float. Validates that the value is presented in the
	whitelist. (Usage: in='str1,str2,str3')
	Note: any string containing commas must be single-quoted to prevent
	parsing failures.

type
	Checks if the value is valid for defined type(one of: base64, timestamp).
	(Usage: type=base64)

Note that there are no tests to prevent conflicting validator parameters. For instance, these fields will never be valid.

...
A int     `validate:"max=0,min=1"`
B string  `validate:"len=10,regexp=^$"
...

Custom validation functions

It is possible to define custom validation functions by using SetValidationFunc. First, one needs to create a validation function.

// Very simple validation func
func notZZ(v interface{}, param string) error {
	st := reflect.ValueOf(v)
	if st.Kind() != reflect.String {
		return validate.ErrUnsupported
	}
	if st.String() == "ZZ" {
		return errors.New("value cannot be ZZ")
	}
	return nil
}

Then one needs to add it to the list of validation funcs and give it a "tag" name.

validate.SetValidationFunc("notzz", notZZ)

Then it is possible to use the notzz validation tag. This will print "Field A error: value cannot be ZZ"

type T struct {
	A string  `validate:"nonzero,notzz"`
}
t := T{"ZZ"}
if valid, errs := validator.Validate(t); !valid {
	fmt.Printf("Field A error: %s\n", errs["A"][0])
}

To use parameters, it is very similar.

// Very simple validator with parameter
func notSomething(v interface{}, param string) error {
	st := reflect.ValueOf(v)
	if st.Kind() != reflect.String {
		return validate.ErrUnsupported
	}
	if st.String() == param {
		return errors.New("value cannot be " + param)
	}
	return nil
}

And then the code below should print "Field A error: value cannot be ABC".

validator.SetValidationFunc("notsomething", notSomething)
type T struct {
	A string  `validate:"notsomething=ABC"`
}
t := T{"ABC"}
if valid, errs := validator.Validate(t); !valid {
	fmt.Printf("Field A error: %s\n", errs["A"][0])
}

As well, it is possible to overwrite builtin validation functions.

validate.SetValidationFunc("min", myMinFunc)

And you can delete a validation function by setting it to nil.

validate.SetValidationFunc("notzz", nil)
validate.SetValidationFunc("nonzero", nil)

Using a non-existing validation func in a field tag will always return false and with error validate.ErrUnknownTag.

Finally, package validator also provides a helper function that can be used to validate simple variables/values.

// valid: true, errs: []
valid, errs = validator.Valid(42, "min=10, max=50")

// valid: false, errs: [validate.ErrZeroValue]
valid, errs = validator.Valid(nil, "nonzero")

// valid: false, errs: [validate.ErrMin,validate.ErrMax]
valid, errs = validator.Valid("hi", "nonzero,min=3,max=2")

Custom tag name

In case there is a reason why one would not wish to use tag 'validate' (maybe due to a conflict with a different package), it is possible to tell the package to use a different tag.

validator.SetTag("valid")

Then.

Type T struct {
	A int    `valid:"min=8, max=10"`
	B string `valid:"nonzero"`
}

SetTag is permanent. The new tag name will be used until it is again changed with a new call to SetTag. A way to temporarily use a different tag exists.

validator.WithTag("foo").Validate(t)
validator.WithTag("bar").Validate(t)
// But this will go back to using 'validate'
validator.Validate(t)

Multiple validators

You may often need to have a different set of validation rules for different situations. In all the examples above, we only used the default validator but you could create a new one and set specific rules for it.

For instance, you might use the same struct to decode incoming JSON for a REST API but your needs will change when you're using it to, say, create a new instance in storage vs. when you need to change something.

type User struct {
	Username string `validate:"nonzero"`
	Name string     `validate:"nonzero"`
	Age int         `validate:"nonzero"`
	Password string `validate:"nonzero"`
}

Maybe when creating a new user, you need to make sure all values in the struct are filled, but then you use the same struct to handle incoming requests to, say, change the password, in which case you only need the Username and the Password and don't care for the others. You might use two different validators.

type User struct {
	Username string `creating:"nonzero" chgpw:"nonzero"`
	Name string     `creating:"nonzero"`
	Age int         `creating:"nonzero"`
	Password string `creating:"nonzero" chgpw:"nonzero"`
}

var (
	creationValidator = validator.NewValidator()
	chgPwValidator = validator.NewValidator()
)

func init() {
	creationValidator.SetTag("creating")
	chgPwValidator.SetTag("chgpw")
}

...

func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
	var u User
	json.NewDecoder(r.Body).Decode(&user)
	if valid, _ := creationValidator.Validate(user); !valid {
		// the request did not include all of the User
		// struct fields, so send a http.StatusBadRequest
		// back or something
	}
	// create the new user
}

func SetNewUserPasswordHandler(w http.ResponseWriter, r *http.Request) {
	var u User
	json.NewDecoder(r.Body).Decode(&user)
	if valid, _ := chgPwValidator.Validate(user); !valid {
		// the request did not Username and Password,
		// so send a http.StatusBadRequest
		// back or something
	}
	// save the new password
}

It is also possible to do all of that using only the default validator as long as SetTag is always called before calling validator.Validate() or you chain the with WithTag().

======================== type User struct { Firstname string validate:"attr=firstname,min=3,msg_min=errors.form.too_small,max=15,msg_max=errors.form.too_big,regexp=^[a-zA-Z]$,msg_regexp=My custom message" Lastname string validate:"attr=lastname,min=3,msg_min=errors.form.too_small,max=15,msg_max=errors.form.too_big,regexp=^[a-zA-Z]$,msg_regexp=my custom message" }

Результат валидации при некорректных данных будет map, где поле ключа соответствует названию атрибута (задается как "attr" в тэге), а значение - первой ошибке из списка правил валидации, если их несколько для одного поля.

map[string]error{ "firstname" : "errors.form.too_small", "lastname" : "my custom message", }

Добавить возможность использования кастомных сообщений об ошибках. Они должны задаваться параметром тега "msg_%rule_name%", где %rule_name% - правило валидации.

Необходимые правила валидации:

"empty" - для любого объекта (для цифровых значений 0)
"min", "max" - для string, slice длина, для числовых значений само значение
"in" - значение входит в slice типа поля, к которому относится, к примеру validate:"attr=sex,in=male,female" , для числовых значений: validate:"attr=option,in=1,9,42"
"compare" - значение соответствует заданному согласно типу validate:"attr=agree_terms,compare=true"
"type" - значение соответствует некоторому типу, пока только timestamp и base64 validate:"attr=created_at,type=timestamp"

Для правил min, max должны обрабатываться плейсхолдеры {min} {max} при генерации ошибок. msg_min=The value should be more than {min}.

# Functions

NewValidator creates a new Validator.
SetTag allows you to change the tag name used in structs.
SetValidationFunc sets the function to be used for a given validation constraint.
Valid validates a value based on the provided tags and returns errors found or nil.
Validate validates the fields of a struct based on 'validator' tags and returns errors found indexed by the field name.
WithTag creates a new Validator with the new tag name.

# Variables

ErrBadParameter is the error returned when an invalid parameter is provided to a validation rule (e.g.
ErrInvalid is the error returned when variable is invalid (normally a nil pointer).
ErrInvalidTypedValue is the error error returned when a passed value doesn't correspond with defined type.
ErrInvalidValue is the error error returned when a passed value was not found in rule's list.
ErrLen is the error returned when length is not equal to param specified.
ErrMax is the error returned when variable is more than maximum specified.
ErrMin is the error returned when variable is less than mininum value specified.
ErrRegexp is the error returned when the value does not match the provided regular expression parameter.
ErrUnknownTag is the error returned when an unknown tag is found.
ErrUnsupported is the error error returned when a validation rule is used with an unsupported variable type.
ErrZeroValue is the error returned when variable has zero valud and nonzero was specified.

# Structs

TextErr is an error that also implements the TextMarshaller interface for serializing out to various plain text encodings.
Validator implements a validator.

# Type aliases

ErrorArray is a slice of errors returned by the Validate function.
ErrorMap is a map which contains all errors from validating a struct.
ValidationFunc is a function that receives the value of a field and a parameter used for the respective validation tag.