Skip to content

Repository files navigation

Validator

About

This is a lightweight extendable validator library for Golang structs.

The library is based on golang tags. It comes with a set of generic stdlib validation functions and provides a straightforward way to develop your own validation logic.

Example

package main
import (
"github.com/osdrv/validator"apiv1
)
typeMessagestruct {
Id apiv1.MessageId`validate:"apiv1_message_id"`Titlestring`validate:"nonempty, maxlen(255)"`Versionint`validate:"gt(0)"`UserId apiv1.UserId`validate:"apiv1_user_id"`Kindstring`validate:"enum(text, audio, video)"`ReplyTo apiv1.MessageId`validate:"optional, apiv1_message_id"`
}
funcmain() {
varmessageMessageiferr:=validator.Validate(message); err!=nil {
// Handle validation error
}
}
package apiv1
import (
"github.com/osdrv/validator"
)
typeMessageId=stringtypeUserId=stringvar (
MessageIdRegex=regexp.MustCompile("^[_\\-0-9a-zA-Z]{32}:[_\\-0-9a-zA-Z]{31}$")
UserIdRegex=regexp.MustCompile("^[_\\-0-9a-zA-Z]{32}$")
)
funcValidateMessageId(vstring) (bool, string) {
returnMessageIdRegex.MatchString(v), "does not look like message Id"
}
funcValidateUserId(vstring) (bool, string) {
returnUserIdRegex.MatchString(v), "does not look like user Id"
}
funcinit() {
validator.Register("apiv1_message_id", ValidateMessageId)
validator.Register("apiv1_user_id", ValidateUserId)
}

Built-in functions

Stringer interface

A non-primitive type would be probed for implementing a stringer interface:

typestringerinterface {
String() string
}

STDLib

Function handleAccepted argumentsDetails
emptyNo arguments
enumA list of bools, ints (including: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr), strings and stringer interface
eqA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
gtA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
gteA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
lenA single string or stringer interface
ltA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
lteA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
maxlenA single int argument
neA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
noneA list of bools, ints (including: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr), strings and stringer interface
nonemptyA single argument of type: int(all the flavors above), bool (casted to string), string and stringer interface
optionalNo arguments
rangeA list of bools, ints (including: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr), strings and stringer interface

Implementing a custom validation function

Validator function interface

A validator func should conform to one of the interfaces below:

func (v<Any>, extra...<Any>) boolfunc (v<Any>, extra...<Any>) (bool, string)
func (v<Any>, extra...<Any>) (bool, string, bool)

In these signatures <Any> means any type: the arguments defined in validator tags would be casted to the corresponding type. For example:

Assuming there is a validation tag that looks like: validate:"foo(bar, baz)", and the corresponding validation function defined as:

validator.Register("foo", func(v interface{}, s1, s2 string) bool {
// some validation logic returning whether value v is valid or not
})

The expected values s1 and s2 would be: "bar" and "baz" correspondingly.

Here is another example that should emphasize the idea:

Assuming the same validation function is being used, validate:"foo(1, 42)" would invoke the function def above with: s1 = "1" and s2 = "42" because the validator library will guess the types from the function definition. Please pay attention to the argument types you provide in the actual validation function and ensure an automatic type cast is possible. If not sure, consider using interface{} and do a type casting manually.

In the function argument list, there is 1 mandatory argument v: the factual value found under the tagged struct field. It can be of any type: the value won't be type-casted while passing around.

The rest of the arguments is completely optional and depends on the validator logic. A validator function can therefore be variative (see std::enum and std::range for more details).

A validator function should return either:

  • a single bool, indicating whether the value is valid or not
  • value above + a string error message; it is safe to always return an error message even if the field is correct: it would be ignored
  • values above + chain breaker flag; see Chaining section for more details

Chaining

By default, all validators are chainable: one can declare a validator chain with a comma-separated list like: validate:"gt(5), lt(10), ne(7)". A chain invokes validators in a declaration order. It means all validators in the chain are conjucted with an AND logic.

In contrast to that, a chain breaker flag can be used as a third return value from a validator function. validator.Continue is assumed by default. If validator.Break is returned by defaul, the chain stops here with a validation result from the current validator function which returned the chain break directive.

Returning a chain breaker is somewhat rare. This is how optional is implemented: if a zero-value is provided, it prevents the remainig chain from execution and returns a valid flag.

Contributing

If you found an issue, please open an issue in this repository.

If you have an idea or a proposal, please submit a pull request.

If in doubts or just want to say hi, feel free to send me an email.

License

Distributed under MIT License, please see LICENSE file within the code for more details.

Author

Oleg Sidorov , 2020-2021

About

A lightweight Golang struct validator

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages