Slices¶
b.Slice(&m.X) returns the configurator of a []E field with the rules of the slice itself. As converts it to a typed slice configurator, the rules of the slice stay available after the conversion:
| Configurator | Elements | Rules on elements |
|---|---|---|
b.Slice(&m.X) |
any E |
Each with *shared.Field[E]: Check, Required, Modify, Custom, When, and As to convert the element |
.As(str.Slice) |
string, *string, named strings |
Each with the string configurator, Trim, Regexp, Email |
.As(num.Slice) |
numbers and named numbers | Each with the number configurator |
.As(timex.Slice), .As(eq.Slice) |
times, comparable values | Each with the time and the equality configurators, Unique of eq.Slice |
.As(vuuid.Slice) |
uuid.UUID, *uuid.UUID |
Each with the uuid configurator, AnyOf |
A pointer to a slice, *[]E, is converted from its field: b.Field(&m.Tags).As(shared.SlicePtr).As(str.Slice), a nil pointer is absent.
The slice and its elements¶
The rules of the configurator check the slice itself, the rules inside Each check every element:
| Rule | Fails when | Message |
|---|---|---|
Required() |
the slice is absent or empty | Should be fulfilled |
MinLen(n) |
the slice has less than n elements |
Cannot contain less than %d elements |
MaxLen(n) |
the slice has more than n elements |
Cannot contain more than %d elements |
Each(fn) |
— | fn configures the rules of the elements |
Trim, Regexp and Email of str.Slice and AnyOf of uuid.Slice are shortcuts for the same rules in Each. Any element of b.Slice is converted in Each too: e.As(num.Ptr).Max(100) for []*int.
package main
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/insei/valigo"
"github.com/insei/valigo/rules/str"
vuuid "github.com/insei/valigo/rules/uuid"
"github.com/insei/valigo/shared"
)
type Post struct {
Tags []string
Editors []*uuid.UUID
Quantities []int
}
func main() {
v := valigo.New()
valigo.Configure[Post](v, func(b valigo.Configurator[Post], p *Post) {
b.Slice(&p.Tags).As(str.Slice).Required().MaxLen(3).Each(func(tag *str.BaseConfigurator) {
tag.Trim().Required().MaxLen(10)
})
b.Slice(&p.Editors).As(vuuid.Slice).Each(func(id *vuuid.BaseConfigurator) {
id.Required()
})
b.Slice(&p.Quantities).Each(func(q *shared.Field[int]) {
q.Check(func(v int) bool { return v > 0 }, "Should be positive")
})
})
editor := uuid.MustParse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
post := &Post{
Tags: []string{"go", " ", "validation-library"},
Editors: []*uuid.UUID{&editor, nil},
Quantities: []int{1, 0},
}
for _, err := range v.Validate(context.Background(), post) {
fmt.Println(err)
}
}
Output:
Should be fulfilled (Tags[1]: )
Cannot be longer than 10 characters (Tags[2]: validation-library)
Should be fulfilled (Editors[1]: <nil>)
Should be positive (Quantities[1]: 0)
The location of an element error has the index of the element, the errors of the slice itself have the location of the field. Nil elements of slices of pointers are absent: only Required fails on them.
Custom and When¶
Custom and When of the slice configurators get the elements as shared.Elems[E], a view over []E and []*E without copying:
b.Slice(&p.Tags).As(str.Slice).Custom(func(ctx context.Context, h *shared.FieldCustomHelper, tags shared.Elems[string]) []shared.Error {
seen := make(map[string]bool, tags.Len())
for _, tag := range tags.All() { // tag is *string, nil for nil elements of []*string
if tag != nil && seen[*tag] {
return []shared.Error{h.ErrorT(ctx, *tag, "Tags should be unique")}
}
if tag != nil {
seen[*tag] = true
}
}
return nil
})
Len() returns the number of elements, At(i) the pointer to the element i, All() iterates over the indexes and the pointers.
Slices of pointers¶
Slice works with the declared element type: for []*int the element is *int. Convert the element to check the pointed values, nil elements are absent then: