Awesome Go

ozzo-validation

CategoryValidation
SubcategoryValidation
Stars4,150

Supports validation of various data types (structs, strings, maps, slices, etc.) with configurable and extensible validation rules specified in usual code constructs instead of struct tags

About ozzo-validation

This large README is shown as a plain-text preview. Read the full README at the source

# ozzo-validation

[![CI](https://github.com/go-ozzo/ozzo-validation/actions/workflows/ci.yml/badge.svg)](https://github.com/go-ozzo/ozzo-validation/actions)
[![codecov](https://codecov.io/gh/go-ozzo/ozzo-validation/branch/master/graph/badge.svg)](https://codecov.io/gh/go-ozzo/ozzo-validation)
[![GoDoc](https://godoc.org/github.com/go-ozzo/ozzo-validation?status.png)](http://godoc.org/github.com/go-ozzo/ozzo-validation)
[![Go Report](https://goreportcard.com/badge/github.com/go-ozzo/ozzo-validation)](https://goreportcard.com/report/github.com/go-ozzo/ozzo-validation)

## Description

ozzo-validation is a Go package that provides configurable and extensible data validation capabilities.
It has the following features:

* use normal programming constructs rather than error-prone struct tags to specify how data should be validated.
* can validate data of different types, e.g., structs, strings, byte slices, slices, maps, arrays.
* can validate custom data types as long as they implement the `Validatable` interface.
* can validate data types that implement the `sql.Valuer` interface (e.g. `sql.NullString`).
* customizable and well-formatted validation errors.
* error code and message translation support.
* provide a rich set of validation rules right out of box.
* extremely easy to create and use custom validation rules.

For an example on how this library is used in an application, please refer to [go-rest-api](https://github.com/qiangxue/go-rest-api) which is a starter kit for building RESTful APIs in Go.

## Requirements

Go 1.13 or above.


## Getting Started

The ozzo-validation package mainly includes a set of validation rules and two validation methods. You use 
validation rules to describe how a value should be considered valid, and you call either `validation.Validate()`
or `validation.ValidateStruct()` to validate the value.


### Installation

Run the following command to install the package:

```
go get github.com/go-ozzo/ozzo-validation/v4
```

### Validating a Simple Value

For a simple value, such as a string or an integer, you may use `validation.Validate()` to validate it. For example, 

```go
package main

import (
	"fmt"

	"github.com/go-ozzo/ozzo-validation/v4"
	"github.com/go-ozzo/ozzo-validation/v4/is"
)

func main() {
	data := "example"
	err := validation.Validate(data,
		validation.Required,       // not empty
		validation.Length(5, 100), // length between 5 and 100
		is.URL,                    // is a valid URL
	)
	fmt.Println(err)
	// Output:
	// must be a valid URL
}
```

The method `validation.Validate()` will run through the rules in the order that they are listed. If a rule fails
the validation, the method will return the corresponding error and skip the rest of the rules. The method will
return nil if the value passes all validation rules.


### Validating a Struct

For a struct value, you usually want to check if its fields are valid. For example, in a RESTful application, you
may unmarshal the request payload into a struct and then validate the struct fields. If one or multiple fields
are invalid, you may want to get an error describing which fields are invalid. You can use `validation.ValidateStruct()`
to achieve this purpose. A single struct can have rules for multiple fields, and a field can be associated with multiple 
rules. For example,

```go
type Address struct {
	Street string
	City   string
	State  string
	Zip    string
}

func (a Address) Validate() error {
	return validation.ValidateStruct(&a,
		// Street cannot be empty, and the length must between 5 and 50
		validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
		// City cannot be empty, and the length must between 1 and 50
		validation.Field(&a.City, validation.Required, validation.Length(1, 50)),
		// State cannot be empty, and must be a string consisting of two letters in upper case
		validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
		// State cannot be empty, and must be a string consisting of five digits
		validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
	)
}

a := Address{
    Street: "123",
    City:   "Unknown",
    State:  "Virginia",
    Zip:    "12345",
}

err := a.Validate()
fmt.Println(err)
// Output:
// Street: the length must be between 5 and 50; State: must be in a valid format.
```

Note that when calling `validation.ValidateStruct` to validate a struct, you should pass to the method a pointer 
to the struct instead of the struct itself. Similarly, when calling `validation.Field` to specify the rules
for a struct field, you should use a pointer to the struct field. 

When the struct validation is performed, the fields are validated in the order they are specified in `ValidateStruct`. 
And when each field is validated, its rules are also evaluated in the order they are associated with the field.
If a rule fails, an error is recorded for that field, and the validation will continue with the next field.


### Validating a Map

Sometimes you might need to work with dynamic data stored in maps rather than a typed model. You can use `validation.Map()`
in this situation. A single map can have rules for multiple keys, and a key can be associated with multiple 
rules. For example,

```go
c := map[string]interface{}{
	"Name":  "Qiang Xue",
	"Email": "q",
	"Address": map[string]interface{}{
		"Street": "123",
		"City":   "Unknown",
		"State":  "Virginia",
		"Zip":    "12345",
	},
}

err := validation.Validate(c,
	validation.Map(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Email cannot be empty and should be in a valid email format.
		validation.Key("Email", validation.Required, is.Email),
		// Validate Address using its own validation rules
		validation.Key("Address", validation.Map(
			// Street cannot be empty, and the length must between 5 and 50
			validation.Key("Street", validation.Required, validation.Length(5, 50)),
			// City cannot be empty, and the length must between 1 and 50
			validation.Key("City", validation.Required, validation.Length(1, 50)),
			// State cannot be empty, and must be a string consisting of two letters in upper case
			validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
			// State cannot be empty, and must be a string consisting of five digits
			validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
		)),
	),
)
fmt.Println(err)
// Output:
// Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address.
```

When the map validation is performed, the keys are validated in the order they are specified in `Map`. 
And when each key is validated, its rules are also evaluated in the order they are associated with the key.
If a rule fails, an error is recorded for that key, and the validation will continue with the next key.

#### Allowing Extra Keys

By default, `validation.Map()` will return an `Extra: key not expected` error if there's unexpected key inside the map (you have to specify all expected keys in the validation rules).

```go
c := map[string]interface{}{
	"Name":  "Qiang Xue",
	"Email": "q",
	"Address": map[string]interface{}{
		"Street": "123",
		"City":   "Unknown",
	},
}

err := validation.Validate(c,
	validation.Map(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Validate Address using its own validation rules
		validation.Key("Address", validation.Map(
			// Street cannot be empty.
			validation.Key("Street", validation.Required),
		)),
	),
)
fmt.Println(err)
// Output:
// Address: (City: key not expected); Email: key not expected.
```

If you need to allow extra keys, you can achieve this by using `validation.Map().AllowExtraKeys()`, or `validation.DynamicMap()`.

```go
err := validation.Validate(c,
	validation.Map(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Validate Address using its own validation rules
		validation.Key("Address", validation.Map(
			// Street cannot be empty.
			validation.Key("Street", validation.Required),
		).AllowExtraKeys()),
	).AllowExtraKeys(),
)
fmt.Println(err)
// Output:
// ""

err2 := validation.Validate(c,
	validation.DynamicMap(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Validate Address using its own validation rules
		validation.Key("Address", validation.DynamicMap(
			// Street cannot be empty.
			validation.Key("Street", validation.Required),
		)),
	),
)
fmt.Println(err2)
// Output:
// ""
```

#### Allowing Optional Keys

By default, `validation.Key()` expect the key to be provided and will return an `XXX: required key is missing.` error if the key doesn't exist in the map.

```go
c := map[string]interface{}{
	"Name":  "Qiang Xue",
}

err := validation.Validate(c,
	validation.Map(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Email cannot be empty and should be in a valid email format.
		validation.Key("Email", validation.Required, is.Email),
	),
)
fmt.Println(err)
// Output:
// Email: required key is missing.
```

If you need to allow optional key, you can achieve this by using `validation.Key().Optional()` or `validation.OptionalKey()`.

```go
c := map[string]interface{}{
	"Name":  "Qiang Xue",
}

err := validation.Validate(c,
	validation.Map(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Email is optional, when it exists, it cannot be empty and should be in a valid email format.
		validation.Key("Email", validation.Required, is.Email).Optional(),
	),
)
fmt.Println(err)
// Output:
// ""

err2 := validation.Validate(c,
	validation.Map(
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Key("Name", validation.Required, validation.Length(5, 20)),
		// Email is optional, when it exists, it cannot be empty and should be in a valid email format.
		validation.OptionalKey("Email", validation.Required, is.Email),
	),
)
fmt.Println(err2)
// Output:
// ""
```

### Validation Errors

The `validation.ValidateStruct` method returns validation errors found in struct fields in terms of `validation.Errors` 
which is a map of fields and their corresponding errors. Nil is returned if validation passes.

By default, `validation.Errors` uses the struct tags named `json` to determine what names should be used to 
represent the invalid fields. The type also implements the `json.Marshaler` interface so that it can be marshaled 
into a proper JSON object. For example,

```go
type Address struct {
	Street string `json:"street"`
	City   string `json:"city"`
	State  string `json:"state"`
	Zip    string `json:"zip"`
}

// ...perform validation here...

err := a.Validate()
b, _ := json.Marshal(err)
fmt.Println(string(b))
// Output:
// {"street":"the length must be between 5 and 50","state":"must be in a valid format"}
```

You may modify `validation.ErrorTag` to use a different struct tag name.

If you do not like the magic that `ValidateStruct` determines error keys based on struct field names or corresponding
tag values, you may use the following alternative approach:

```go
c := Customer{
	Name:  "Qiang Xue",
	Email: "q",
	Address: Address{
		State:  "Virginia",
	},
}

err := validation.Errors{
	"name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)),
	"email": validation.Validate(c.Name, validation.Required, is.Email),
	"zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
}.Filter()
fmt.Println(err)
// Output:
// email: must be a valid email address; zip: cannot be blank.
```

In the above example, we build a `validation.Errors` by a list of names and the corresponding validation results. 
At the end we call `Errors.Filter()` to remove from `Errors` all nils which correspond to those successful validation 
results. The method will return nil if `Errors` is empty.

The above approach is very flexible as it allows you to freely build up your validation error structure. You can use
it to validate both struct and non-struct values. Compared to using `ValidateStruct` to validate a struct, 
it has the drawback that you have to redundantly specify the error keys while `ValidateStruct` can automatically 
find them out.


### Internal Errors

Internal errors are different from validation errors in that internal errors are caused by malfunctioning code (e.g.
a validator making a remote call to validate some data when the remote service is down) rather
than the data being validated. When an internal error happens during data validation, you may allow the user to resubmit
the same data to perform validation again, hoping the program resumes functioning. On the other hand, if data validation
fails due to data error, the user should generally not resubmit the same data again.

To differentiate internal errors from validation errors, when an internal error occurs in a validator, wrap it
into `validation.InternalError` by calling `validation.NewInternalError()`. The user of the validator can then check
if a returned error is an internal error or not. For example,

```go
if err := a.Validate(); err != nil {
	if e, ok := err.(validation.InternalError); ok {
		// an internal error happened
		fmt.Println(e.InternalError())
	}
}
```


## Validatable Types

A type is validatable if it implements the `validation.Validatable` interface. 

When `validation.Validate` is used to validate a validatable value, if it does not find any error with the 
given validation rules, it will further call the value's `Validate()` method. 

Similarly, when `validation.ValidateStruct` is validating a struct field whose type is validatable, it will call 
the field's `Validate` method after it passes the listed rules.

> Note: When implementing `validation.Validatable`, do not call `validation.Validate()` to validate the value in its
> original type because this will cause infinite loops. For example, if you define a new type `MyString` as `string`
> and implement `validation.Validatable` for `MyString`, within the `Validate()` function you should cast the value 
> to `string` first before calling `validation.Validate()` to validate it.

In the following example, the `Address` field of `Customer` is validatable because `Address` implements 
`validation.Validatable`. Therefore, when validating a `Customer` struct with `validation.ValidateStruct`,
validation will "dive" into the `Address` field.

```go
type Customer struct {
	Name    string
	Gender  string
	Email   string
	Address Address
}

func (c Customer) Validate() error {
	return validation.ValidateStruct(&c,
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
		// Gender is optional, and should be either "Female" or "Male".
		validation.Field(&c.Gender, validation.In("Female", "Male")),
		// Email cannot be empty and should be in a valid email format.
		validation.Field(&c.Email, validation.Required, is.Email),
		// Validate Address using its own validation rules
		validation.Field(&c.Address),
	)
}

c := Customer{
	Name:  "Qiang Xue",
	Email: "q",
	Address: Address{
		Street: "123 Main Street",
		City:   "Unknown",
		State:  "Virginia",
		Zip:    "12345",
	},
}

err := c.Validate()
fmt.Println(err)
// Output:
// Address: (State: must be in a valid format.); Email: must be a valid email address.
```

Sometimes, you may want to skip the invocation of a type's `Validate` method. To do so, simply associate
a `validation.Skip` rule with the value being validated.

### Maps/Slices/Arrays of Validatables

When validating an iterable (map, slice, or array), whose element type implements the `validation.Validatable` interface,
the `validation.Validate` method will call the `Validate` method of every non-nil element.
The validation errors of the elements will be returned as `validation.Errors` which maps the keys of the
invalid elements to their corresponding validation errors. For example,

```go
addresses := []Address{
	Address{State: "MD", Zip: "12345"},
	Address{Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"},
	Address{City: "Unknown", State: "NC", Zip: "123"},
}
err := validation.Validate(addresses)
fmt.Println(err)
// Output:
// 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.).
```

When using `validation.ValidateStruct` to validate a struct, the above validation procedure also applies to those struct 
fields which are map/slices/arrays of validatables. 

#### Each

The `Each` validation rule allows you to apply a set of rules to each element of an array, slice, or map.

```go
type Customer struct {
    Name      string
    Emails    []string
}

func (c Customer) Validate() error {
    return validation.ValidateStruct(&c,
        // Name cannot be empty, and the length must be between 5 and 20.
		validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
		// Emails are optional, but if given must be valid.
		validation.Field(&c.Emails, validation.Each(is.Email)),
    )
}

c := Customer{
    Name:   "Qiang Xue",
    Emails: []Email{
        "[email protected]",
        "invalid",
    },
}

err := c.Validate()
fmt.Println(err)
// Output:
// Emails: (1: must be a valid email address.).
```

### Pointers

When a value being validated is a pointer, most validation rules will validate the actual value pointed to by the pointer.
If the pointer is nil, these rules will skip the validation.

An exception is the `validation.Required` and `validation.NotNil` rules. When a pointer is nil, they
will report a validation error.


### Types Implementing `sql.Valuer`

If a data type implements the `sql.Valuer` interface (e.g. `sql.NullString`), the built-in validation rules will handle
it properly. In particular, when a rule is validating such data, it will call the `Value()` method and validate
the returned value instead.


### Required vs. Not Nil

When validating input values, there are two different scenarios about checking if input values are provided or not.

In the first scenario, an input value is considered missing if it is not entered or it is entered as a zero value
(e.g. an empty string, a zero integer). You can use the `validation.Required` rule in this case.

In the second scenario, an input value is considered missing only if it is not entered. A pointer field is usually
used in this case so that you can detect if a value is entered or not by checking if the pointer is nil or not.
You can use the `validation.NotNil` rule to ensure a value is entered (even if it is a zero value).


### Embedded Structs

The `validation.ValidateStruct` method will properly validate a struct that contains embedded structs. In particular,
the fields of an embedded struct are treated as if they belong directly to the containing struct. For example,

```go
type Employee struct {
	Name string
}

type Manager struct {
	Employee
	Level int
}

m := Manager{}
err := validation.ValidateStruct(&m,
	validation.Field(&m.Name, validation.Required),
	validation.Field(&m.Level, validation.Required),
)
fmt.Println(err)
// Output:
// Level: cannot be blank; Name: cannot be blank.
```

In the above code, we use `&m.Name` to specify the validation of the `Name` field of the embedded struct `Employee`.
And the validation error uses `Name` as the key for the error associated with the `Name` field as if `Name` a field

Frequently Asked Questions

What is ozzo-validation?

ozzo-validation is a Validation library for the Go programming language. Supports validation of various data types (structs, strings, maps, slices, etc.) with configurable and extensible validation rules specified in usual code constructs instead of struct tags

How many GitHub stars does ozzo-validation have?

ozzo-validation has 4,150 GitHub stars in the directory's latest synchronization.

How do I install ozzo-validation?

Install ozzo-validation with the Go module system using `go get go-ozzo/ozzo-validation`. Check the repository for the current installation instructions.

What category does ozzo-validation belong to?

ozzo-validation is listed under Validation, specifically Validation.

← Back to Validation