Files
core/valid/validator.go
T

86 lines
2.5 KiB
Go
Raw Normal View History

package valid
import (
"errors"
"reflect"
"strings"
"code.nochebuena.dev/einherjar/core/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
}
var _ Validator = (*validator)(nil)
// New returns a Validator. Without options, DefaultMessages (English) is used.
// Field names in error context use the json struct tag when available,
// falling back to the Go field name.
func New(opts ...Option) Validator {
cfg := &config{mp: DefaultMessages}
for _, o := range opts {
o(cfg)
}
v := playground.New()
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "" || name == "-" {
return fld.Name
}
return name
})
for _, cv := range cfg.customValidators {
if err := v.RegisterValidation(cv.tag, func(fl playground.FieldLevel) bool {
return cv.fn(fieldLevelAdapter{fl})
}); err != nil {
panic("valid: WithCustomValidator tag " + cv.tag + ": " + err.Error())
}
}
return &validator{v: v, mp: cfg.mp}
}
type fieldLevelAdapter struct{ fl playground.FieldLevel }
func (a fieldLevelAdapter) Field() reflect.Value { return a.fl.Field() }
func (a fieldLevelAdapter) Param() string { return a.fl.Param() }
func (a fieldLevelAdapter) FieldName() string { return a.fl.FieldName() }
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 playground.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)
}