Struct validation backed by go-playground/validator/v10 with xerrors integration and pluggable i18n message providers. What's included: - Validator interface with Struct(v any) error method - New(...Option) constructor with WithMessageProvider functional option - MessageProvider interface for i18n; DefaultMessages (EN) and SpanishMessages (ES) built in - ValidationErrors mapped to xerrors.ErrInvalidInput with field and tag context keys - InvalidValidationError (non-struct input) mapped to xerrors.ErrInternal - Full playground.ValidationErrors attached via WithError for callers needing all failures Tested-via: todo-api POC integration Reviewed-against: docs/adr/
79 lines
3.3 KiB
Markdown
79 lines
3.3 KiB
Markdown
# 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.
|