An idiomatic Go (golang) validation package. Supports configurable and extensible validation rules (validators) using normal language constructs instead of erro
An idiomatic Go (golang) validation package. Supports configurable and extensible validation rules (validators) using normal language constructs instead of erro
ozzo-validation is a Go package that provides configurable and extensible data validation capabilities. It has the following features:
Validatable interface.sql.Valuer interface (e.g. sql.NullString).For an example on how this library is used in an application, please refer to go-rest-api which is a starter kit for building RESTful APIs in Go.
Go 1.13 or above.
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.
Run the following command to install the package:
go get github.com/go-ozzo/ozzo-validation/v4
For a simple value, such as a string or an integer, you may use validation.Validate() to validate it. For example,
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.
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,
…
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.
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,
…
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.
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).
…
If you need to allow extra keys, you can achieve this by using validation.Map().AllowExtraKeys(), or validation.DynamicMap().
…
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.
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().
…
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,
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:
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 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,
if err := a.Validate(); err != nil {
if e, ok := err.(validation.InternalError); ok {
// an internal error happened
fmt.Println(e.InternalError())
}
}
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 callvalidation.Validate()to validate the value in its original type because this will cause infinite loops. For example, if you define a new typeMyStringasstringand implementvalidation.ValidatableforMyString, within theValidate()function you should cast the value tostringfirst before callingvalidation.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.
…
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.
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,
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.
The Each validation rule allows you to apply a set of rules to each element of an array, slice, or map.
…
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.
sql.ValuerIf 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.
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
No open issues yet, or sync has not completed.