Introduction
ozzo-validation
is a very powerful and flexible data verification library. Unlike other data verification libraries based on struct tag, ozzo-validation
believes that struct tag is more prone to errors during use. Because struct tag is essentially a string, it is completely based on string analysis and cannot use the language's static checking mechanism. It is easy to write errors unknowingly and not easily, and it is difficult to troubleshoot errors in actual code.
ozzo-validation
advocates using codes to specify rules for verification. In fact ozzo
is to assist the development of a Web application framework, including ORM library ozzo-dbx
, routing library ozzo-routing
, log database ozzo-log
, configuration repository ozzo-config
and the most famous, the most widely used data validation Library ozzo-validation
. The author even came up with a template go-rest-api
developing Web applications.
Quick to use
The code in this article uses Go Modules.
Create a directory and initialize:
$ mkdir ozzo-validation && cd ozzo-validation
$ go mod init github.com/darjun/go-daily-lib/ozzo-validation
Install the ozzo-validation
library:
$ go get -u github.com/go-ozzo/ozzo-validation/v4
ozzo-validation
is more intuitive to write:
package main
import (
"fmt"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/go-ozzo/ozzo-validation/v4"
)
func main() {
name := "darjun"
err := validation.Validate(name,
validation.Required,
validation.Length(2, 10),
is.URL)
fmt.Println(err)
}
ozzo-validation
uses the function Validate()
to verify the basic type value. The first parameter passed in is the data to be verified, and then one or more verification rules are passed in with variable parameters. In the above example, a string is checked. We use code to express the rules:
validation.Required
: Indicates that the value must be set, for a string, it cannot be empty;validation.Length(2, 10)
: Specify the range of length;is.URL
: A large number of auxiliary methods are built in theis
is.URL
must be in URL format.
Validate()
function checks the data in sequence according to the incoming rules, until a certain rule fails to be checked, or all rules are checked successfully. If a rule returns a failure, skip the following rules and return an error directly. If the data passes all the rules, a nil
is returned.
Run the above program output:
must be a valid URL
Because the string "darjun" is obviously not a valid URL. If the is.URL
rule is removed, the run output nil
.
Structure
Use ValidateStruct()
function to check a structure object. We need to specify the verification rules for each field in the structure in turn:
type User struct {
Name string
Age int
Email string
}
func validateUser(u *User) error {
err := validation.ValidateStruct(u,
validation.Field(&u.Name, validation.Required, validation.Length(2, 10)),
validation.Field(&u.Age, validation.Required, validation.Min(1), validation.Max(200)),
validation.Field(&u.Email, validation.Required, validation.Length(10, 50), is.Email))
return err
}
ValidateStruct()
accepts a structure pointer as the first parameter, and then specifies the rules of each field in turn. Field rules validation.Field()
function, which accepts a pointer to a specific field, followed by one or more rules. Above we limit, the length of the name is between [2, 10], the age is between [1, 200] (let’s assume that humans can live up to 200 years now), the length of the e-mail address is between [10, 50], and use is.Email
restricts it must be a legal email address. At the same time, these 3 fields are required ( validation.Required
).
Then we construct a legal User
object and an illegal User
object, and verify them separately:
func main() {
u1 := &User {
Name: "darjun",
Age: 18,
Email: "darjun@126.com",
}
fmt.Println("user1:", validateUser(u1))
u2 := &User {
Name: "lidajun12345",
Age: 201,
Email: "lidajun's email",
}
fmt.Println("user2:", validateUser(u2))
}
Program output:
user1: <nil>
user2: Age: must be no greater than 200; Email: must be a valid email address; Name: the length must be between 2 and 10.
For the structure, validation
checks the incoming rules for each field in turn. For a certain field, if a rule fails the check, the following rule is skipped, continues to check the next field . If a field fails to verify, the error information about the field will be included in the result, as in the example above.
Map
Sometimes the data is stored in a map
instead of a structure. At this time, you can use validation.Map()
specify the rules for verifying map
. In the validation.Map()
rules, you need to use validation.Key()
specify one or more rules corresponding to each key in turn. Finally, pass the map
type data and the validation.Map()
rule to the validation.Validate()
function to check:
func validateUser(u map[string]interface{}) error {
err := validation.Validate(u, validation.Map(
validation.Key("name", validation.Required, validation.Length(2, 10)),
validation.Key("age", validation.Required, validation.Min(1), validation.Max(200)),
validation.Key("email", validation.Required, validation.Length(10, 50), is.Email),
))
return err
}
func main() {
u1 := map[string]interface{} {
"name": "darjun",
"age": 18,
"email": "darjun@126.com",
}
fmt.Println("user1:", validateUser(u1))
u2 := map[string]interface{} {
"name": "lidajun12345",
"age": 201,
"email": "lidajun's email",
}
fmt.Println("user2:", validateUser(u2))
}
We modified the above example and used map[string]interface{}
store User
information. map
is similar to the structure, and it is verified sequentially according to the order of the keys specified in validation.Map()
If a key fails to verify, record the error message. Finally, the error information of all keys is summarized and returned. Run the program:
user1: <nil>
user2: age: must be no greater than 200; email: must be a valid email address; name: the length must be between 2 and 10.
Verifiable type
ozzo-validation
library provides an interface Validatable
:
type Validatable interface {
// Validate validates the data and returns an error if validation fails.
Validate() error
}
All types that implement the Validatable
interface are verifiable types. validation.Validate()
function verifies a certain type of data, it first verifies all the rules passed into the function. If these rules are passed, the Validate()
function determines whether the type has implemented the Validatbale
interface. If it is achieved, call its Validate()
method for verification. We let the User
type in the Validatable
interface:
type User struct {
Name string
Age int
Gender string
Email string
}
func (u *User) Validate() error {
err := validation.ValidateStruct(u,
validation.Field(&u.Name, validation.Required, validation.Length(2, 10)),
validation.Field(&u.Age, validation.Required, validation.Min(1), validation.Max(200)),
validation.Field(&u.Gender, validation.Required, validation.In("male", "female")),
validation.Field(&u.Email, validation.Required, validation.Length(10, 50), is.Email))
return err
}
Since User
implements the Validatable
interface, we can directly call the Validate()
function to verify:
func main() {
u1 := &User{
Name: "darjun",
Age: 18,
Gender: "male",
Email: "darjun@126.com",
}
fmt.Println("user1:", validation.Validate(u1, validation.NotNil))
u2 := &User{
Name: "lidajun12345",
Age: 201,
Email: "lidajun's email",
}
fmt.Println("user2:", validation.Validate(u2, validation.NotNil))
}
After passing the NotNil
verification, the Validate()
function will also call the User.Validate()
method for verification.
It should be noted that Validate()
method of the type that Validatable
interface, you cannot directly call the validation.Validate()
function for the value of the type, which will lead to infinite recursion:
type UserName string
func (n UserName) Validate() error {
return validation.Validate(n,
validation.Required, validation.Length(2, 10))
}
func main() {
var n1, n2 UserName = "dj", "lidajun12345"
fmt.Println("username1:", validation.Validate(n1))
fmt.Println("username2:", validation.Validate(n2))
}
We defined a new type UserName
based on string
, stipulating that UserName
not empty and the length is in the range of [2, 10]. However, the above Validate()
in the method UserName
type variable n
passed as a function validation.Validate()
. The internal inspection of this function finds that UserName
implements the Validatable
interface, and then calls its Validate()
method, resulting in infinite recursion.
We only need to simply convert n
to string
type:
func (n UserName) Validate() error {
return validation.Validate(string(n),
validation.Required, validation.Length(2, 10))
}
A collection of verifiable types
Validate()
function verifies a collection (slice/array/map, etc.) whose elements are of a Validate()
type (that is, implements the Validatable
interface), it will call the 060d4a5075f584 method of its elements in turn, and finally the verification returns a validation.Errors
type. This is actually a map[string]error
type. The key is the key of the element (the index is for slices and arrays, and the key for map
), and the value is an error value. example:
func main() {
u1 := &User{
Name: "darjun",
Age: 18,
Gender: "male",
Email: "darjun@126.com",
}
u2 := &User{
Name: "lidajun12345",
Age: 201,
Email: "lidajun's email",
}
userSlice := []*User{u1, u2}
userMap := map[string]*User{
"user1": u1,
"user2": u2,
}
fmt.Println("user slice:", validation.Validate(userSlice))
fmt.Println("user map:", validation.Validate(userMap))
}
userSlice
sections parity error in the second element of the key will result 1
return (index) of, userMap
button user2
parity error will result in the key user2
Returns. operation result:
user slice: 1: (Age: must be no greater than 200; Email: must be a valid email address; Gender: cannot be blank; Name: the length must be between 2 and 10.).
user map: user2: (Age: must be no greater than 200; Email: must be a valid email address; Gender: cannot be blank; Name: the length must be between 2 and 10.).
If we need to satisfy certain rules for every element in the set, we can use the validation.Each()
function. For example, our User
object has multiple mailboxes, and the format of each mailbox address is required to be legal:
type User struct {
Name string
Age int
Emails []string
}
func (u *User) Validate() error {
return validation.ValidateStruct(u,
validation.Field(&u.Emails, validation.Each(is.Email)))
}
func main() {
u := &User{
Name: "dj",
Age: 18,
Emails: []string{
"darjun@126.com",
"don't know",
},
}
fmt.Println(validation.Validate(u))
}
The error message will indicate which location data is invalid:
Emails: (1: must be a valid email address.).
Conditional rule
We can set rules for another field based on the value of one field. For example, our User
object has two fields: the boolean Student
indicates whether it is still a student, and the string School
indicates the school. When Student
is true
, the field School
must exist and the length is in the range of [10, 20]:
type User struct {
Name string
Age int
Student bool
School string
}
func (u *User) Validate() error {
return validation.ValidateStruct(u,
validation.Field(&u.Name, validation.Required, validation.Length(2, 10)),
validation.Field(&u.Age, validation.Required, validation.Min(1), validation.Max(200)),
validation.Field(&u.School, validation.When(u.Student, validation.Required, validation.Length(10, 20))))
}
func main() {
u1 := &User{
Name: "dj",
Age: 18,
Student: true,
}
u2 := &User{
Name: "lidajun",
Age: 31,
}
fmt.Println("user1:", validation.Validate(u1))
fmt.Println("user2:", validation.Validate(u2))
}
We use the validation.When()
function, which accepts a Boolean value as the first parameter, and one or more rules as the variable parameters. Only when the first parameter is true
, will the following rule check be performed.
u1
Because the field Student
is true
, the School
field cannot be empty. u2
because Student=false
, School
fields are optional. run:
user1: School: cannot be blank.
user2: <nil>
When checking the registered user information, we make sure that the user must set the email or mobile phone number, and the conditional rules can also be used:
type User struct {
Email string
Phone string
}
func (u *User) Validate() error {
return validation.ValidateStruct(u,
validation.Field(&u.Email, validation.When(u.Phone == "", validation.Required.Error("Either email or phone is required."), is.Email)),
validation.Field(&u.Phone, validation.When(u.Email == "", validation.Required.Error("Either email or phone is required."), is.Alphanumeric)))
}
func main() {
u1 := &User{}
u2 := &User{
Email: "darjun@126.com",
}
u3 := &User{
Phone: "17301251652",
}
u4 := &User{
Email: "darjun@126.com",
Phone: "17301251652",
}
fmt.Println("user1:", validation.Validate(u1))
fmt.Println("user2:", validation.Validate(u2))
fmt.Println("user3:", validation.Validate(u3))
fmt.Println("user4:", validation.Validate(u4))
}
If the Phone
field is empty, Email
must be set. Conversely, if the Email
field is empty, Phone
must be set. All rules can call the Error()
method to set custom error messages. Run output:
user1: Email: Either email or phone is required.; Phone: Either email or phone is required..
user2: <nil>
user3: <nil>
user4: <nil>
Custom rule
In addition to the rules provided by the library, we can also define our own rules. The rule is implemented as a function of the following type:
func Validate(value interface{}) error
Below we implement a function to check whether the IP address is legal. Here we introduce a library commonregex
. This library contains most of the commonly used regular expressions. I also wrote an article before to introduce the use of this library, daily library. you are interested, you can check it out.
func checkIP(value interface{}) error {
ip, ok := value.(string)
if !ok {
return errors.New("ip must be string")
}
ipList := commonregex.IPs(ip)
if len(ipList) != 1 || ipList[0] != ip {
return errors.New("invalid ip format")
}
return nil
}
Then define a network address structure and verification method, and use a custom verification function validation.By()
type Addr struct {
IP string
Port int
}
func (a *Addr) Validate() error {
return validation.ValidateStruct(a,
validation.Field(&a.IP, validation.Required, validation.By(checkIP)),
validation.Field(&a.Port, validation.Min(1024), validation.Max(65536)))
}
verification:
func main() {
a1 := &Addr{
IP: "127.0.0.1",
Port: 6666,
}
a2 := &Addr{
IP: "xxx.yyy.zzz.hhh",
Port: 7777,
}
fmt.Println("addr1:", validation.Validate(a1))
fmt.Println("addr2:", validation.Validate(a2))
}
run:
addr1: <nil>
addr2: IP: invalid ip format.
Rule set
It is a bit inconvenient to specify the rules one by one each time. At this time, we can group the commonly used verification rules into a rule group, and use this group directly when needed. For example, the legal user name agreed in our project must be ASCII letters and numbers, with a length of 10-20, and the user name must not be empty. The rule group is nothing special, it is just a slice of the rule:
var NameRule = []validation.Rule{
validation.Required,
is.Alphanumeric,
validation.Length(10, 20),
}
func main() {
name1 := "lidajun12345"
name2 := "lidajun@!#$%"
name3 := "short"
name4 := "looooooooooooooooooong"
fmt.Println("name1:", validation.Validate(name1, NameRule...))
fmt.Println("name2:", validation.Validate(name2, NameRule...))
fmt.Println("name3:", validation.Validate(name3, NameRule...))
fmt.Println("name4:", validation.Validate(name4, NameRule...))
}
run:
name1: <nil>
name2: must contain English letters and digits only
name3: the length must be between 10 and 20
name4: the length must be between 10 and 20
to sum up
ozzo-validation
promote a code specifies rules instead of error-prone struct tag
, and provides a number of built-in rule. The code written with ozzo-validation
is clear, easy to read, and friendly to the compiler (many errors are exposed at compile time). This article introduces the ozzo-validation
library. The core is two functions Validate()
and ValidateStruct()
. The former is used to verify basic types or verifiable types, and the latter is used to verify structures. In the actual encoding process, the structure will generally implement the Validatbale
interface to change it into a Validate()
type, and then call the 060d4a5075fa94 function to verify.
ozzo-validation
can also check the collection, you can customize the check rules, and you can define a general check group. In addition, ozzo-validation
has many advanced features, such as custom errors, context.Context
, using regular expressions to define rules, etc. If you are interested, you can explore it yourself.
If you find a fun and useful Go language library, welcome to submit an issue on the Go Daily Library GitHub😄
reference
- ozzo-validation GitHub:github.com/go-ozzo/ozzo-validation
- go-rest-api GitHub:github.com/qiangxue/go-rest-api
- Go Daily Library: 160d4a5075fb65 https://darjun.github.io/2020/09/05/godailylib/commonregex/
- Go a library GitHub every day: https://github.com/darjun/go-daily-lib
I
My blog: https://darjun.github.io
Welcome to follow my WeChat public account [GoUpUp], learn together and make progress together~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。