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:
79
valid.go
Normal file
79
valid.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package valid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"code.nochebuena.dev/go/xerrors"
|
||||
playground "github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// Validator validates structs using struct tags.
|
||||
type Validator interface {
|
||||
// Struct validates v and returns a *xerrors.Err if validation fails.
|
||||
// Returns nil if v is valid.
|
||||
// Returns ErrInvalidInput for field constraint failures (first error only).
|
||||
// Returns ErrInternal if v is not a struct.
|
||||
Struct(v any) error
|
||||
}
|
||||
|
||||
// Option configures a Validator.
|
||||
type Option func(*config)
|
||||
|
||||
// config holds constructor configuration.
|
||||
type config struct {
|
||||
mp MessageProvider
|
||||
}
|
||||
|
||||
// WithMessageProvider sets a custom MessageProvider.
|
||||
// Default: DefaultMessages (English).
|
||||
func WithMessageProvider(mp MessageProvider) Option {
|
||||
return func(c *config) {
|
||||
c.mp = mp
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a Validator. Without options, DefaultMessages (English) is used.
|
||||
func New(opts ...Option) Validator {
|
||||
cfg := &config{mp: DefaultMessages}
|
||||
for _, o := range opts {
|
||||
o(cfg)
|
||||
}
|
||||
return &validator{
|
||||
v: playground.New(),
|
||||
mp: cfg.mp,
|
||||
}
|
||||
}
|
||||
|
||||
// validator is the concrete implementation of Validator.
|
||||
type validator struct {
|
||||
v *playground.Validate
|
||||
mp MessageProvider
|
||||
}
|
||||
|
||||
// Struct implements Validator.
|
||||
//
|
||||
// Only the first validation error is surfaced. Apps needing all failures can
|
||||
// cast errors.Unwrap(err) to validator.ValidationErrors themselves.
|
||||
func (val *validator) Struct(v any) error {
|
||||
err := val.v.Struct(v)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var invalidErr *playground.InvalidValidationError
|
||||
if errors.As(err, &invalidErr) {
|
||||
return xerrors.Wrap(xerrors.ErrInternal, "internal validation error", err)
|
||||
}
|
||||
|
||||
var validationErrs playground.ValidationErrors
|
||||
if errors.As(err, &validationErrs) {
|
||||
first := validationErrs[0]
|
||||
msg := val.mp.Message(first.Field(), first.Tag(), first.Param())
|
||||
return xerrors.New(xerrors.ErrInvalidInput, msg).
|
||||
WithContext("field", first.Field()).
|
||||
WithContext("tag", first.Tag()).
|
||||
WithError(err)
|
||||
}
|
||||
|
||||
return xerrors.Wrap(xerrors.ErrInternal, "internal validation error", err)
|
||||
}
|
||||
Reference in New Issue
Block a user