# `valid` > Struct validation backed by `go-playground/validator` with structured error output. **Module:** `code.nochebuena.dev/go/valid` **Tier:** 1 — depends on `xerrors` (Tier 0) and `go-playground/validator/v10` **Go:** 1.25+ **Dependencies:** `code.nochebuena.dev/go/xerrors`, `github.com/go-playground/validator/v10` --- ## Overview `valid` wraps [`go-playground/validator/v10`](https://github.com/go-playground/validator) and maps validation failures to structured [`*xerrors.Err`](../xerrors) values. It replaces the old `check` package, adding a plain constructor, a swappable message provider, and English messages by default. ## Installation ```sh go get code.nochebuena.dev/go/valid ``` ## Quick start ```go import "code.nochebuena.dev/go/valid" type CreateUserRequest struct { Name string `validate:"required"` Email string `validate:"required,email"` Age int `validate:"min=18,max=120"` } v := valid.New() err := v.Struct(req) if err != nil { // err is a *xerrors.Err with code ErrInvalidInput. var xe *xerrors.Err if errors.As(err, &xe) { fmt.Println(xe.Code()) // INVALID_ARGUMENT fmt.Println(xe.Message()) // "field 'Email' must be a valid email address" fmt.Println(xe.Fields()) // map[field:Email tag:email] } } ``` ## Usage ### Creating a validator ```go // English messages (default). v := valid.New() // Spanish messages. v := valid.New(valid.WithMessageProvider(valid.SpanishMessages)) // Custom message provider. v := valid.New(valid.WithMessageProvider(myProvider)) ``` ### Validating structs ```go err := v.Struct(myStruct) ``` | Outcome | Error returned | |---------|---------------| | All fields pass | `nil` | | Field constraint failure | `*xerrors.Err` with `ErrInvalidInput`, first error only | | Not a struct | `*xerrors.Err` with `ErrInternal` | The returned `*xerrors.Err` for field failures carries: - `Fields()["field"]` — the failing struct field name - `Fields()["tag"]` — the failing validation rule (e.g. `"email"`, `"required"`) - `Unwrap()` — the underlying `validator.ValidationErrors` ### Message providers `MessageProvider` maps a validation failure to a human-readable message: ```go type MessageProvider interface { Message(field, tag, param string) string } ``` Built-in presets: | Variable | Language | Usage | |----------|----------|-------| | `DefaultMessages` | English | automatic (no option needed) | | `SpanishMessages` | Spanish | `WithMessageProvider(valid.SpanishMessages)` | Custom provider example: ```go type myMessages struct{} func (m myMessages) Message(field, tag, param string) string { switch tag { case "required": return field + " is mandatory" default: return field + " failed: " + tag } } v := valid.New(valid.WithMessageProvider(myMessages{})) ``` ## Design decisions **No singleton** — `valid.New(opts...)` returns a plain value. Multiple validators with different configurations can coexist. Tests create isolated instances without global state. **Only the first validation error is surfaced** — `go-playground/validator` returns all field errors at once; we surface only the first for API simplicity. Apps needing all failures can cast `errors.Unwrap(err)` to `validator.ValidationErrors`: ```go var xe *xerrors.Err errors.As(err, &xe) var ve validator.ValidationErrors errors.As(errors.Unwrap(xe), &ve) // all field errors ``` **`valid` imports `xerrors` directly** — `valid` is Tier 1 and `xerrors` is Tier 0. The import is intentional; `valid` constructs `*xerrors.Err` values. Duck-typing is reserved for cases where the import would create a circular or cross-tier dependency. **Spanish is bundled, not a separate module** — the Spanish preset is a small, zero-dep addition. Splitting it into a separate module would add publish overhead for negligible gain. ## Ecosystem ``` Tier 0: xerrors ↑ (direct import — constructs *xerrors.Err) Tier 1: valid ← you are here ↑ Tier 2: httputil (injects valid.Validator into generic handler) ``` ## License MIT