One of the main concepts of the package is to provide helpful violation descriptions for complex data structures. For example, if you have lots of structures used in other structures you want somehow to describe property paths to violated attributes.
The property path generated by the validator indicates
how it reached the invalid value from the root element. Property path is denoted by dots, while array access is denoted
by square brackets. For example, book.keywords[0] means that the violation occurred on the first element of
array keywords in the book object.
You can pass a property path by calling At function on any argument.
err := validator.Validate(
context.Background(),
validation.String(
"",
it.IsNotBlank(),
).At(
validation.PropertyName("properties"),
validation.ArrayIndex(1),
validation.PropertyName("tag"),
),
)
if violations, ok := validation.UnwrapViolations(err); ok {
for _, violation := range violations.All() {
fmt.Println("property path:", violation.PropertyPath().String())
}
}
// Output:
// property path: properties[1].tagAlso, you can create context validator by using validator.At(), validator.AtProperty() or validator.AtIndex()
methods. It can be used to validate a couple of attributes of one object.
err := validator.
AtProperty("properties").
AtIndex(1).
AtProperty("tag").
Validate(context.Background(), validation.String("", it.IsNotBlank()))
if violations, ok := validation.UnwrapViolations(err); ok {
for _, violation := range violations.All() {
fmt.Println("property path:", violation.PropertyPath().String())
}
}
// Output:
// property path: properties[1].tagFor a better experience with struct validation, you can use shorthand versions of validation arguments with passing property names:
validation.NilProperty();validation.BoolProperty();validation.NilBoolProperty();validation.NumberProperty();validation.NilNumberProperty();validation.StringProperty();validation.NilStringProperty();validation.CountableProperty();validation.TimeProperty();validation.NilTimeProperty();validation.EachNumberProperty();validation.EachStringProperty();validation.ValidProperty();validation.ValidSliceProperty();validation.ValidMapProperty();validation.ComparableProperty();validation.ComparablesProperty();validation.CheckProperty().
err := validator.Validate(
context.Background(),
validation.StringProperty("property", "", it.IsNotBlank()),
)
if violations, ok := validation.UnwrapViolations(err); ok {
for _, violation := range violations.All() {
fmt.Println("property path:", violation.PropertyPath().String())
}
}
// Output:
// property path: propertyThere are few ways to validate structs. The simplest one is to call the validator.Validate method with property
arguments.
document := Document{
Title: "",
Keywords: []string{"", "book", "fantasy", "book"},
}
err := validator.Validate(
context.Background(),
validation.StringProperty("title", document.Title, it.IsNotBlank()),
validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)),
validation.ComparablesProperty[string]("keywords", document.Keywords, it.HasUniqueValues[string]()),
validation.EachStringProperty("keywords", document.Keywords, it.IsNotBlank()),
)
if violations, ok := validation.UnwrapViolations(err); ok {
for _, violation := range violations.All() {
fmt.Println(violation)
}
}
// Output:
// violation at 'title': This value should not be blank.
// violation at 'keywords': This collection should contain 5 elements or more.
// violation at 'keywords': This collection should contain only unique elements.
// violation at 'keywords[0]': This value should not be blank.To check uniqueness by a key (e.g. by struct field), use validation.Slice or validation.SliceProperty with
it.HasUniqueValuesBy(keyFunc). Violations will include the element index in the path (e.g. items[0], items[1]):
type Item struct { ID string }
items := []Item{{ID: "a"}, {ID: "b"}, {ID: "a"}}
err := validator.Validate(ctx,
validation.SliceProperty("items", items, it.HasUniqueValuesBy(func(x Item) string { return x.ID })),
)
// violation at 'items[0]': This collection should contain only unique elements.
// violation at 'items[2]': This collection should contain only unique elements.The recommended way is to implement the validation.Validatable interface for your structures. By using it you can
build complex validation rules on a set of objects used in other objects.
type Product struct {
Name string
Tags []string
Components []Component
}
func (p Product) Validate(ctx context.Context, validator *validation.Validator) error {
return validator.Validate(
ctx,
validation.StringProperty("name", p.Name, it.IsNotBlank()),
validation.AtProperty(
"tags",
validation.Countable(len(p.Tags), it.HasMinCount(5)),
validation.Comparables[string](p.Tags, it.HasUniqueValues[string]()),
validation.EachString(p.Tags, it.IsNotBlank()),
),
validation.AtProperty(
"components",
validation.Countable(len(p.Components), it.HasMinCount(1)),
// this runs validation on each of the components
validation.ValidSlice(p.Components),
),
)
}
type Component struct {
ID int
Name string
Tags []string
}
func (c Component) Validate(ctx context.Context, validator *validation.Validator) error {
return validator.Validate(
ctx,
validation.StringProperty("name", c.Name, it.IsNotBlank()),
validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)),
)
}
func main() {
p := Product{
Name: "",
Tags: []string{"device", "", "phone", "device"},
Components: []Component{
{
ID: 1,
Name: "",
},
},
}
err := validator.ValidateIt(context.Background(), p)
if violations, ok := validation.UnwrapViolations(err); ok {
for _, violation := range violations.All() {
fmt.Println(violation)
}
}
// Output:
// violation at 'name': This value should not be blank.
// violation at 'tags': This collection should contain 5 elements or more.
// violation at 'tags': This collection should contain only unique elements.
// violation at 'tags[1]': This value should not be blank.
// violation at 'components[0].name': This value should not be blank.
// violation at 'components[0].tags': This collection should contain 1 element or more.
}You can use the When() method on any of the built-in constraints to execute conditional validation on it.
err := validator.Validate(
context.Background(),
validation.StringProperty("text", note.Text, it.IsNotBlank().When(note.IsPublic)),
)
if violations, ok := validation.UnwrapViolations(err); ok {
for _, violation := range violations.All() {
fmt.Println(violation)
}
}
// Output:
// violation at 'text': This value should not be blank.By default, when validating an object all constraints of it will be checked whether or not they pass. In some cases, however, you will need to validate an object against only some specific group of constraints. To do this, you can organize each constraint into one or more validation groups and then apply validation against one group of constraints.
Validation groups are working together only with validation groups passed to a constraint by WhenGroups() method. This method is implemented in all built-in constraints. If you want to use validation groups for your own constraints do not forget to implement this method in your constraint.
Be careful, empty groups are considered as the default group. Its value is equal to the validation.DefaultGroup.
See example.