binding:"required,min=3"、oneof、email、dive……这些规则到底怎么判、报什么错、和字段类型有什么关系?这篇用一份全不合格的请求把常用规则跑一遍,逐个看 FieldError 里的信息,再看错误的三种类型和嵌套、切片的校验边界。
binding/default_validator.go(Gin v1.12.0):
88 | }
89 |
90 | func (v *defaultValidator) lazyinit() {
91 | v.once.Do(func() {
92 | v.validate = validator.New()
93 | v.validate.SetTagName("binding")
94 | })Gin 把 go-playground/validator/v10 包了一层,tag 名改成了 binding,第一次校验时才初始化。顶层是切片时 Gin 自己逐个元素调用校验器;其它非结构体类型直接放行。
err=
Req.Name tag=min param="3" kind=string value=ab
Req.Age tag=gte param="18" kind=int value=17
Req.Email tag=email param="" kind=string value=not-an-email
Req.Role tag=oneof param="admin user guest" kind=string value=root
Req.Site tag=url param="" kind=string value=ftp//x
Req.ID tag=uuid param="" kind=string value=123
Req.Tags[1] tag=alpha param="" kind=string value=b1
Req.Zip tag=numeric param="" kind=string value=12a45
Req.Birth tag=datetime param="2006-01-02" kind=string value=2024-13-01
Req.IP tag=ip param="" kind=string value=999.1.1.1
Req.Confirm tag=eqfield param="Pass" kind=string value=abc124
--- 同一份结构体,全都合格:
err= nil(校验通过)每个失败的字段是一个 FieldError:Namespace()(带结构体名和下标的完整路径,如 Req.Tags[1])、Tag()、Param()(规则的参数,如 min=3 里的 3、oneof 里的取值列表)、Kind()、Value()。要把校验错误翻译成面向用户的文案,基本就靠 Tag 和 Param 查表。同一个字段多条规则时,第一条失败就停止,只报一项。
body={"name":"ab","age":17} →
validator.ValidationErrors
Key: 'Req.Name' Error:Field validation for 'Name' failed on the 'min' tag
Key: 'Req.Age' Error:Field validation for 'Age' failed on the 'gte' tag
body={"name":"alice","age":"x"} →
*json.UnmarshalTypeError
json: cannot unmarshal string into Go struct field Req.age of type int
body={"name":"alice", →
*errors.errorString
unexpected EOF
body=[] →
*json.UnmarshalTypeError
json: cannot unmarshal array into Go value of type main.ReqShouldBindJSON 返回的错误不止校验失败一种:字段类型不对是 *json.UnmarshalTypeError(而且是在校验之前就报错,不会进入校验),JSON 语法错误是普通 error。用 errors.As 判断是不是 validator.ValidationErrors,再决定怎么回应客户端。
string "abc"(3 字节 3 字符) min=3: 通过
string "你好"(6 字节 2 字符) min=3: 不通过(按字符数,不是字节数)
string "你好吗"(9 字节 3 字符) min=3: 通过
int 2 min=3: 不通过; int 3: 通过; int 100: 通过(数字比大小)
float 2.9 min=3: 不通过; float 3.0: 通过
[]int{1,2} min=3: 不通过; []int{1,2,3}: 通过(比元素个数)
map 2 个键 min=3: 不通过; 3 个键: 通过同一条规则对不同类型含义不同:字符串数的是字符(rune)个数,不是字节数,所以中文 “你好” 只算 2;整数、浮点数比较数值;切片和 map 比较元素个数。len、eq 等也是同样的规则。
err=
Order.Addr.City tag=required param="" kind=string value=
Order.Ship.City tag=required param="" kind=string value=
Order.Items2[0].Sku tag=required param="" kind=string value=
Order.ByKey[k].Sku tag=required param="" kind=string value=
--- 顶层是切片:Gin 的 ValidateStruct 逐个校验元素
binding.SliceValidationError [0]: Key: 'Item.Sku' Error:Field validation for 'Sku' failed on the 'required' tag | [1]: Key: 'Item.Sku' Error:Field validation for 'Sku' 嵌套的结构体字段(含指针)会自动校验;切片和 map 里的元素必须在字段上加 dive,否则里面的 required 等规则不会被检查——这是最常见的“规则写了却没生效”。顶层切片则由 Gin 逐个校验,错误类型是 SliceValidationError,每项带 [下标] 前缀。