86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
|
|
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)
|
||
|
|
}
|