从结构标签验证返回自定义错误消息

huangapple go评论76阅读模式
英文:

Return custom error message from struct tag validation

问题

我正在使用Go 1.17和Gin,我想在将数据发送到数据库之前实现结构体验证。我参考了Gin文档中的示例。

在结构体中,我们可以使用不同的标签来验证字段,就像这样:

type User struct {
    FirstName      string `json:"first_name" binding:"required"`
    LastName       string `json:"last_name" binding:"required"`
    Age            uint8  `json:"age" binding:"gte=0,lte=130"`
    Email          string `json:"email" binding:"required,email"`
    FavouriteColor string `json:"favourite_color" binding:"iscolor"`
}

在处理程序中,我可以这样获取错误:

var u User
if err := c.ShouldBindWith(&u, binding.Query); err == nil {
    c.JSON(http.StatusOK, gin.H{"message": "Good Job"})
} else {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}

错误消息将会是:

{
    "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag\nKey: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag"
}

错误消息太冗长了,有没有办法返回更好的错误给用户呢?我想将JSON响应模型化为:

{
    "errors": [
        "first_name": "This field is required",
        "last_name": "This field is required",
        "age": "This field is required",
        "email": "Invalid email"
    ]
}
英文:

I'm using Go 1.17 with Gin and I want to implement a struct validation before sending the data to my database. I took the example from Gin documentation.

In the struct we can declare different tags to validate a field like this:

type User struct {
	FirstName      string `json:"first_name" binding:"required"`
	LastName       string `json:"last_name" binding:"required"`
	Age            uint8  `json:"age" binding:"gte=0,lte=130"`
	Email          string `json:"email" binding:"required,email"`
	FavouriteColor string `json:"favourite_color" binding:"iscolor"`
}

And in the handler I can grab the error like this:

var u User
if err := c.ShouldBindWith(&u, binding.Query); err == nil {
	c.JSON(http.StatusOK, gin.H{"message": "Good Job"})
} else {
	c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}

The error message will be:

{
    "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag\nKey: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag"
}

The error messages are too verbose how it's possible to returns a better error to the user? I'd like to model the json response like:

{
	"errors": [
		"first_name": "This field is required",
		"last_name": "This field is required",
		"age": "This field is required",
		"email": "Invalid email"
	]
}

答案1

得分: 13

Gin gonic使用github.com/go-playground/validator/v10包来进行绑定验证。如果验证失败,返回的错误是validator.ValidationErrors

虽然没有明确提到,但在模型绑定和验证中提到:

> Gin使用go-playground/validator/v10进行验证。在此处查看有关标签用法的完整文档。

该链接指向go-playground/validator/v10的文档,在那里你可以找到Validation Functions Return Type error这一段。

你可以使用标准的errors包来检查是否是该错误类型,解包它,并访问单个字段,这些字段是validator.FieldError。然后,你可以构造任何你想要的错误消息。

给定这样的错误模型:

type ApiError struct {
	Field string
	Msg   string
}

你可以这样做:

	var u User
	err := c.BindQuery(&u);
	if err != nil {
		var ve validator.ValidationErrors
		if errors.As(err, &ve) {
			out := make([]ApiError, len(ve))
			for i, fe := range ve {
				out[i] = ApiError{fe.Field(), msgForTag(fe.Tag())}
			}
			c.JSON(http.StatusBadRequest, gin.H{"errors": out})
		}
		return
	}

使用一个辅助函数来输出自定义的验证规则错误消息:

func msgForTag(tag string) string {
	switch tag {
	case "required":
		return "This field is required"
	case "email":
		return "Invalid email"
	}
	return ""
}

在我的测试中,这将输出:

{
    "errors": [
        {
            "Field": "Number",
            "Msg": "This field is required"
        }
    ]
}

注意:为了获得具有动态键的JSON输出,你可以使用map[string]string而不是固定的结构模型。

英文:

Gin gonic uses the package github.com/go-playground/validator/v10 to perform binding validation. If the validation fails, the error returned is a validator.ValidationErrors.

This is not mentioned explicitly but here in Model binding and validation it states:

> Gin uses go-playground/validator/v10 for validation. Check the full docs on tags usage here.

That links to the go-playground/validator/v10 documentation, where you find the paragraph Validation Functions Return Type error.

You can use the standard errors package to check if the error is that, unwrap it, and access the single fields, which are validator.FieldError. From that, you can construct whatever error message you want.

Given an error model like this:

type ApiError struct {
	Field string
	Msg   string
}

You can do this:

	var u User
	err := c.BindQuery(&u);
	if err != nil {
		var ve validator.ValidationErrors
		if errors.As(err, &ve) {
			out := make([]ApiError, len(ve))
			for i, fe := range ve {
				out[i] = ApiError{fe.Field(), msgForTag(fe.Tag())}
			}
			c.JSON(http.StatusBadRequest, gin.H{"errors": out})
		}
		return
	}

with a helper function to output a custom error message for your validation rules:

func msgForTag(tag string) string {
	switch tag {
	case "required":
		return "This field is required"
	case "email":
		return "Invalid email"
	}
	return ""
}

In my test, this outputs:

{
    "errors": [
        {
            "Field": "Number",
            "Msg": "This field is required"
        }
    ]
}

PS: To have a json output with dynamic keys, you can use map[string]string instead of a fixed struct model.

huangapple
  • 本文由 发表于 2021年11月23日 01:15:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/70069834.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定