# valid Struct validation via go-playground/validator, returning xerrors-typed errors and supporting pluggable i18n messages. ## Purpose Wraps `github.com/go-playground/validator/v10` behind a minimal `Validator` interface. Translates playground error types into `*xerrors.Err` values with stable `Code` values (`ErrInvalidInput`, `ErrInternal`), so HTTP middleware can map validation failures to HTTP status codes without knowing anything about the underlying library. ## Tier & Dependencies **Tier 1** — depends on: - `code.nochebuena.dev/go/xerrors` (Tier 0, error types) - `github.com/go-playground/validator/v10` (external, hidden behind the interface) ## Key Design Decisions - **Playground validator as hidden backend** (ADR-001): `*playground.Validate` is never exposed in the public API. Callers interact only with the `Validator` interface and `*xerrors.Err` errors. - **xerrors integration** (ADR-002): `ValidationErrors` → `ErrInvalidInput`; `InvalidValidationError` (non-struct arg) → `ErrInternal`. Only the first failing field is surfaced; the full `playground.ValidationErrors` is attached via `WithError` for callers that need all failures. - **MessageProvider for i18n** (ADR-003): Human-readable messages are delegated to a `MessageProvider` interface. `DefaultMessages` (English) is used automatically. `SpanishMessages` is opt-in. Custom providers are accepted via `WithMessageProvider`. ## Patterns Default (English): ```go v := valid.New() if err := v.Struct(req); err != nil { // err is *xerrors.Err with Code() == xerrors.ErrInvalidInput } ``` Spanish messages: ```go v := valid.New(valid.WithMessageProvider(valid.SpanishMessages)) ``` Custom message provider: ```go type myMessages struct{} func (m myMessages) Message(field, tag, param string) string { ... } v := valid.New(valid.WithMessageProvider(myMessages{})) ``` Accessing structured context from the error: ```go var xe *xerrors.Err if errors.As(err, &xe) { field := xe.Fields()["field"] // e.g. "Email" tag := xe.Fields()["tag"] // e.g. "email" } ``` Accessing all validation errors: ```go var xe *xerrors.Err errors.As(err, &xe) var ve playground.ValidationErrors errors.As(errors.Unwrap(xe), &ve) // iterate ve for all failing fields ``` ## What to Avoid - Do not expose `*playground.Validate` or `playground.ValidationErrors` from any new public function. Keep the backend hidden. - Do not add generics to the `Validator` interface. The `Struct(v any) error` signature is intentionally non-generic; the playground library uses reflection internally. - Do not register global custom validators on the shared `*playground.Validate` instance — that would break isolation between callers that share a process but expect different validation behaviour. - Do not construct a new `*playground.Validate` per call; `New()` creates one instance per `Validator`. ## Testing Notes - `valid_test.go` covers: `New()` defaults, custom `MessageProvider` injection, valid struct (nil error), `required`/`email`/`min`/`max` failures, non-struct input (`ErrInternal`), `Fields()` context keys, `Unwrap` to `playground.ValidationErrors`, Spanish messages, and all built-in message tags. - `compliance_test.go` checks at compile time that `valid.New()` satisfies `Validator`, and that `DefaultMessages`/`SpanishMessages` satisfy `MessageProvider`. - No network or database access — all tests are pure in-process.