From 4cdc1013db4b896e827abcd712556b99d6192fb3 Mon Sep 17 00:00:00 2001 From: HF Date: Tue, 21 May 2024 10:50:39 +0800 Subject: [PATCH] Translate validation error (#390) --- api/server/errors/errors.go | 5 --- api/server/httputils/httputils.go | 18 +++++++--- api/server/validator/helper.go | 33 +++++++++++++++++ api/server/validator/password.go | 42 ++++++++++++++++++++++ api/server/validator/validator.go | 59 +++++++++++++++++++++++++++---- go.mod | 2 ++ 6 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 api/server/validator/helper.go create mode 100644 api/server/validator/password.go diff --git a/api/server/errors/errors.go b/api/server/errors/errors.go index 5241ef7b..9b3f3f94 100644 --- a/api/server/errors/errors.go +++ b/api/server/errors/errors.go @@ -38,11 +38,6 @@ func NewError(err error, code int) Error { } } -func IsError(err error) bool { - _, ok := err.(Error) - return ok -} - var ( ErrForbidden = Error{ Code: http.StatusForbidden, diff --git a/api/server/httputils/httputils.go b/api/server/httputils/httputils.go index 691f7c5a..5fbaeac5 100644 --- a/api/server/httputils/httputils.go +++ b/api/server/httputils/httputils.go @@ -20,8 +20,10 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" "github.com/caoyingjunz/pixiu/api/server/errors" + validatorutil "github.com/caoyingjunz/pixiu/api/server/validator" ) type Response struct { @@ -73,11 +75,14 @@ func SetSuccess(c *gin.Context, r *Response) { // SetFailed 设置错误返回值 func SetFailed(c *gin.Context, r *Response, err error) { - if errors.IsError(err) { - SetFailedWithCode(c, r, err.(errors.Error).Code, err) - return + switch e := err.(type) { + case errors.Error: + SetFailedWithCode(c, r, e.Code, e) + case validator.ValidationErrors: + SetFailedWithValidationError(c, r, validatorutil.TranslateError(e)) + default: + SetFailedWithCode(c, r, http.StatusBadRequest, err) } - SetFailedWithCode(c, r, http.StatusBadRequest, err) } // SetFailedWithCode 设置错误返回值 @@ -86,6 +91,11 @@ func SetFailedWithCode(c *gin.Context, r *Response, code int, err error) { c.JSON(http.StatusOK, r) } +func SetFailedWithValidationError(c *gin.Context, r *Response, e string) { + r.SetMessageWithCode(e, http.StatusBadRequest) + c.JSON(http.StatusOK, r) +} + // AbortFailedWithCode 设置错误,code 返回值并终止请求 func AbortFailedWithCode(c *gin.Context, code int, err error) { r := NewResponse() diff --git a/api/server/validator/helper.go b/api/server/validator/helper.go new file mode 100644 index 00000000..a0bccb54 --- /dev/null +++ b/api/server/validator/helper.go @@ -0,0 +1,33 @@ +/* +Copyright 2024 The Pixiu Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validator + +import ( + "strings" + + "github.com/go-playground/validator/v10" +) + +// TranslateError returns the translated message of the validation error. +func TranslateError(errs validator.ValidationErrors) string { + messages := make([]string, len(errs)) + for i, err := range errs { + messages[i] = err.Translate(tran) + } + + return strings.Join(messages, "; ") +} diff --git a/api/server/validator/password.go b/api/server/validator/password.go new file mode 100644 index 00000000..b83b10c2 --- /dev/null +++ b/api/server/validator/password.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 The Pixiu Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validator + +import ( + "github.com/go-playground/validator/v10" + + "github.com/caoyingjunz/pixiu/pkg/util" +) + +var _ customValidator = (*passwordValidator)(nil) + +func init() { + register(&passwordValidator{ + common: newValidatorCommon("password", "密码强度不够,至少包含一个大写字母、一个小写字母、一个数字"), + }) +} + +// passwordValidator is a customized validator for validating user password. +type passwordValidator struct { + common +} + +// validate validates the password in request. +func (pv *passwordValidator) validate(fl validator.FieldLevel) bool { + password := fl.Field().String() + return util.ValidateStrongPassword(password) +} diff --git a/api/server/validator/validator.go b/api/server/validator/validator.go index 47fbbdf5..0193621c 100644 --- a/api/server/validator/validator.go +++ b/api/server/validator/validator.go @@ -17,20 +17,65 @@ limitations under the License. package validator import ( - "github.com/caoyingjunz/pixiu/pkg/util" - "github.com/gin-gonic/gin/binding" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" + zt "github.com/go-playground/validator/v10/translations/zh" ) +type customValidator interface { + getTag() string + translateError(ut ut.Translator) error + translate(ut ut.Translator, fe validator.FieldError) string + + // Should be implemented by the custom validator. + validate(fl validator.FieldLevel) bool +} + +var tran ut.Translator +var customValidators []customValidator + +// register adds a new custom validator to the validator list +func register(validator customValidator) { + customValidators = append(customValidators, validator) +} + func init() { + _zh := zh.New() // default is Chinese + uni := ut.New(_zh, _zh) + tran, _ = uni.GetTranslator("zh") + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { - _ = v.RegisterValidation("password", validatePassword) + _ = zt.RegisterDefaultTranslations(v, tran) + + for _, c := range customValidators { + _ = v.RegisterValidation(c.getTag(), c.validate) + _ = v.RegisterTranslation(c.getTag(), tran, c.translateError, c.translate) + } } } -// validatePassword validates the password in request. -func validatePassword(fl validator.FieldLevel) bool { - password := fl.Field().String() - return util.ValidateStrongPassword(password) +type common struct { + tag, err string +} + +func newValidatorCommon(tag, err string) common { + return common{ + tag: tag, + err: err, + } +} + +func (c common) getTag() string { + return c.tag +} + +func (c common) translateError(ut ut.Translator) error { + return ut.Add(c.tag, "{0}"+c.err, true) +} + +func (c common) translate(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T(c.tag, fe.Field()) + return t } diff --git a/go.mod b/go.mod index 7319155b..0614a029 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,8 @@ require ( github.com/gin-gonic/gin v1.8.1 github.com/go-openapi/spec v0.20.7 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-playground/locales v0.14.1 + github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/validator/v10 v10.19.0 github.com/go-sql-driver/mysql v1.6.0 github.com/goccy/go-json v0.10.2 // indirect