template-monorepo/internal/library/validate/validate.go

111 lines
3.3 KiB
Go

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"
)
// Validate ...
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...)
}