gin-自定义validator错误提示

1 背景

gin框架中使用到了validator包,但是没有自定义通义的错误信息的地方。故需要通过反射自行实现。

2 知识点

  1. golang的反射,struct的反射才能拿到tag、pointer\array\slice\map需要Elem()才能取到其元素类型。
  2. validator验证错误之后,err的结构。

3 目标

迭代判断、替换验证错误信息为自定义的错误信息。

4 源码

  1. 源码
package main

import (
	"fmt"
	"net/http"
	"reflect"
	"regexp"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
)

// 定义请求参数结构体
type HelloParams struct {
	Name       string `json:"name" binding:"required" msg:"姓名不符合要求"`
	GirlFriend []struct {
		Name string `json:"name" binding:"required"`
		Age  int    `json:"age" binding:"required,min=18" msg:"年龄需在18岁以上"`
	} `json:"girl_friend" binding:"required,dive"`
	Hourse map[string]struct {
		Price int `json:"price" binding:"min=400000" msg:"价值需在40万以上"`
	} `json:"hourse" binding:"required,dive"`
	Hobby *struct {
		Type string `json:"type" binding:"required" msg:"请填写爱好类型"`
		Live struct {
			Level string `json:"level" binding:"required" msg:"请为你的生活打分"`
		}
	} `json:"hobby" binding:"required"`
	Food []string `json:"food" binding:"dive,min=3" msg:"食物3字符以上"`
}

func main() {
	r := gin.Default()

	r.POST("/hello", func(c *gin.Context) {
		var params HelloParams
		if err := c.ShouldBindJSON(&params); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"err": ParseError(params, err)})
			return
		}
		c.JSON(http.StatusOK, gin.H{"message": "Hello " + params.Name})
	})

	r.Run(":8080")
}

// 处理错误信息
func ParseError(obj any, err error) []string {
	outMsg := []string{}
	errMsg := make(map[string]string, 1)
	if _, ok := err.(validator.ValidationErrors); !ok {
		// 普通错误:直接返回信息
		outMsg = append(outMsg, err.Error())
		return outMsg
	} else {
		// binding类型的错误
		for _, e := range err.(validator.ValidationErrors) {
			errMsg[ParseKeyInDefaultErrMsg(e.Error())] = e.Error()
		}
	}
	fmt.Printf("%#v\n", errMsg)
	objType := reflect.TypeOf(obj)
	ConvertErrMsgToTag(objType.Name(), objType, "", &errMsg, &outMsg)
	return outMsg
}

// 从默认验证错误信息中,提取键路径
func ParseKeyInDefaultErrMsg(msg string) string {
	reg, _ := regexp.Compile(`\[.+\]`)
	l := make([]string, 0, 2)
	msgTmp := strings.Split(msg, " ")
	msg = strings.Trim(msgTmp[1], "'")
	msgTmp = strings.Split(msg, ".")
	for _, k := range msgTmp {
		l = append(l, reg.ReplaceAllString(k, ""))
	}
	return strings.Join(l, ".")
}

// 将错误信息转换成对应的tag标识
//
// kPath: 键路径,迭代进入下一层时,传入已经过的key,以匹配对应的tag;
// objType: 反射目标;
// msgTag: 字段tag中的msg;
// errMsg: 解析后的错误信息;
// outMsg: 待返回的错误信息集;
func ConvertErrMsgToTag(kPath string, objType reflect.Type, msgTag string, errMsg *map[string]string, outMsg *[]string) {
	_, haveErr := (*errMsg)[kPath]
	switch objType.Kind() {
	case reflect.Struct:
		for i := 0; i < objType.NumField(); i++ {
			f := objType.Field(i)
			msg := f.Tag.Get("msg") // 结构体成员才有tag
			ConvertErrMsgToTag(kPath+"."+f.Name, f.Type, msg, errMsg, outMsg)
		}
	case reflect.Array:
		fallthrough
	case reflect.Slice:
		fallthrough
	case reflect.Map:
		fallthrough
	case reflect.Pointer:
		// array/slice/map,Elem()返回其成员类型;pointer,返回所指目标类型
		// 此处prefix无需拼接subObj.Name(),仅是将Pointer类型转换类型,键路径未发生改变
		subObj := objType.Elem()
		if haveErr {
			if msgTag != "" {
				*outMsg = append(*outMsg, msgTag)
			} else {
				*outMsg = append(*outMsg, (*errMsg)[kPath])
			}
		} else {
			ConvertErrMsgToTag(kPath, subObj, msgTag, errMsg, outMsg)
		}
	default: // 普通类型
		if haveErr {
			if msgTag != "" {
				*outMsg = append(*outMsg, msgTag)
			} else {
				*outMsg = append(*outMsg, (*errMsg)[kPath])
			}
		}
	}
}

  1. 测试用例
{
    "name": "ted",
    "girl_friend": [
        {
            "name": "小红",
            "age": 17
        },
        {
            "name": "小绿",
            "age": 19
        }
    ],
    "hourse": {
        "广州": {
            "price": 39999
        }
    },
    "hobby":{},
    "food": ["披萨"]
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值