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 validates structs using go-playground/validator tags and custom options. 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...) }