feat(valid): initial stable release v0.9.0
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/
This commit is contained in:
38
docs/adr/ADR-003-message-provider-i18n.md
Normal file
38
docs/adr/ADR-003-message-provider-i18n.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# ADR-003: MessageProvider Pattern for i18n
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
Validation error messages shown to end users must be human-readable. Applications targeting different locales need messages in different languages. Hardcoding English messages inside the `Validator` implementation would make internationalization impossible without forking the package.
|
||||
|
||||
At the same time, the majority of applications use a single language throughout their lifetime. Requiring every caller to configure a message provider would be boilerplate-heavy for the common case.
|
||||
|
||||
## Decision
|
||||
|
||||
A `MessageProvider` interface is defined:
|
||||
|
||||
```go
|
||||
type MessageProvider interface {
|
||||
Message(field, tag, param string) string
|
||||
}
|
||||
```
|
||||
|
||||
`Message` receives the failing field name, the rule tag, and the rule parameter (e.g., `"5"` for `min=5`, or `""` if none), and returns a human-readable string.
|
||||
|
||||
Two built-in implementations are provided as package-level variables:
|
||||
- `DefaultMessages` — English, used automatically when no option is passed to `New()`.
|
||||
- `SpanishMessages` — Spanish, available as an opt-in via `WithMessageProvider(valid.SpanishMessages)`.
|
||||
|
||||
Custom implementations are supported by passing any value that satisfies `MessageProvider` to `WithMessageProvider`.
|
||||
|
||||
The `New()` constructor defaults to `DefaultMessages` and applies options via a `config` struct, following the functional options pattern. This means zero boilerplate for the common (English) case and a single option call for overrides.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive**: English is the zero-configuration default — `valid.New()` requires no arguments.
|
||||
- **Positive**: Spanish is available without any external dependency — just `valid.SpanishMessages`.
|
||||
- **Positive**: Applications can supply their own `MessageProvider` for any other language or for message formats that include the failing value, link to docs, etc.
|
||||
- **Negative**: The built-in providers handle only four tags (`required`, `email`, `min`, `max`) explicitly; all others fall through to a generic fallback message. Applications using many custom tags should supply a custom provider.
|
||||
- **Note**: Message formatting uses the struct field name as returned by `go-playground/validator` (the Go field name, e.g. `"Email"`), not a JSON tag. If user-facing messages must show the JSON key name, a custom `MessageProvider` combined with a registered tag name function on the playground validator would be needed.
|
||||
Reference in New Issue
Block a user