Files
valid/docs/adr/ADR-001-playground-validator-backend.md
Rene Nochebuena 328b80c060 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/
2026-03-18 21:02:26 +00:00

2.3 KiB

ADR-001: go-playground/validator as Hidden Backend

Status: Accepted Date: 2026-03-18

Context

Struct validation via struct tags is a well-understood pattern in Go. github.com/go-playground/validator/v10 is the de-facto standard library for this, supporting a large rule set (required, email, min, max, url, uuid, and hundreds more) and nested struct traversal.

However, directly using the playground *validate.Validate type throughout application code creates a hard dependency: its error types (ValidationErrors, InvalidValidationError) must be imported wherever errors are inspected, and its configuration API leaks into all call sites.

Decision

github.com/go-playground/validator/v10 is used as the sole validation backend but is not exposed in the public API.

The public API is the Validator interface, which has one method: Struct(v any) error. The concrete type validator (unexported) holds a *playground.Validate instance. The playground package is imported under the alias playground to make it unambiguous at a glance which types originate from it.

Callers never see playground.ValidationErrors or *playground.InvalidValidationError directly. The concrete error types are translated to *xerrors.Err inside Struct() before being returned.

The playground *Validate instance is created once inside New() with default options. No custom validators, tag name functions, or struct-level validators are registered. This keeps the API surface small and the behaviour predictable.

Consequences

  • Positive: The playground library can be upgraded or replaced without changing any call-site code — only the valid package internals change.
  • Positive: Application code only needs to import code.nochebuena.dev/go/valid and code.nochebuena.dev/go/xerrors; no direct dependency on go-playground/validator.
  • Negative: Advanced playground features (custom validators, RegisterTagNameFunc, cross-field validation) are not accessible without extending the valid package. If a project needs them, it should add Option functions or methods to Validator.
  • Note: The playground alias playground "github.com/go-playground/validator/v10" is retained in the source as a readability aid, not a requirement. It prevents confusion with the package name valid used in the surrounding code.