2026-05-19 12:56:32 +00:00
|
|
|
package validate
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/go-playground/locales/en"
|
|
|
|
|
ut "github.com/go-playground/universal-translator"
|
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
|
|
|
entranslations "github.com/go-playground/validator/v10/translations/en"
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-19 13:15:18 +00:00
|
|
|
// Validate validates structs using go-playground/validator tags and custom options.
|
2026-05-19 12:56:32 +00:00
|
|
|
type Validate interface {
|
|
|
|
|
ValidateAll(obj any) error // 驗證整個結構體
|
|
|
|
|
BindToValidator(opts ...Option) error // 綁定客製化驗證規則
|
|
|
|
|
GetValidator() *validator.Validate // 取得原始 validator
|
|
|
|
|
GetTranslator() ut.Translator // 新增此方法以獲取翻譯器
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validator is the concrete implementation of the Validate interface.
|
|
|
|
|
type Validator struct {
|
|
|
|
|
V *validator.Validate
|
|
|
|
|
Trans ut.Translator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetTranslator returns the universal translator instance.
|
|
|
|
|
func (v *Validator) GetTranslator() ut.Translator {
|
|
|
|
|
return v.Trans
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetValidator returns the raw *validator.Validate instance.
|
|
|
|
|
func (v *Validator) GetValidator() *validator.Validate {
|
|
|
|
|
return v.V
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateAll validates a struct.
|
|
|
|
|
func (v *Validator) ValidateAll(obj any) error {
|
|
|
|
|
err := v.V.Struct(obj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
var validationErrors validator.ValidationErrors
|
|
|
|
|
if errors.As(err, &validationErrors) {
|
|
|
|
|
return translateValidationErrors(validationErrors, v.Trans)
|
|
|
|
|
}
|
|
|
|
|
// This could be an invalid argument error (e.g., not a struct)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BindToValidator registers custom validation rules and their translations.
|
|
|
|
|
func (v *Validator) BindToValidator(opts ...Option) error {
|
|
|
|
|
for _, item := range opts {
|
|
|
|
|
// Register validation function
|
|
|
|
|
if err := v.V.RegisterValidation(item.ValidatorName, item.ValidatorFunc); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to register validator '%s': %w", item.ValidatorName, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register translation message if provided
|
|
|
|
|
if item.TranslationFunc != nil && item.RegisterTranslationFunc != nil {
|
|
|
|
|
if err := v.V.RegisterTranslation(item.ValidatorName, v.Trans, item.RegisterTranslationFunc, item.TranslationFunc); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to register translation for validator '%s': %w", item.ValidatorName, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewValidator creates a new validator instance with a given translator.
|
|
|
|
|
// This is the most flexible way to create a validator, allowing for any locale.
|
|
|
|
|
func NewValidator(trans ut.Translator, opts ...Option) (Validate, error) {
|
|
|
|
|
v := validator.New()
|
|
|
|
|
|
|
|
|
|
if err := entranslations.RegisterDefaultTranslations(v, trans); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to register default translations: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
|
|
|
|
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
|
|
|
|
if name == "-" {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return name
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
validatorInstance := &Validator{
|
|
|
|
|
V: v,
|
|
|
|
|
Trans: trans,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := validatorInstance.BindToValidator(opts...); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to bind validator options: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return validatorInstance, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewWithDefaultEN creates a new validator instance with English as the default language.
|
|
|
|
|
// It's a convenience wrapper around NewValidator.
|
|
|
|
|
func NewWithDefaultEN(opts ...Option) (Validate, error) {
|
|
|
|
|
enLocale := en.New()
|
|
|
|
|
uni := ut.New(enLocale, enLocale)
|
|
|
|
|
trans, found := uni.GetTranslator("en")
|
|
|
|
|
if !found {
|
|
|
|
|
return nil, errors.New("failed to find 'en' translator")
|
|
|
|
|
}
|
|
|
|
|
return NewValidator(trans, opts...)
|
|
|
|
|
}
|