Golang使用validator进行数据校验及自定义翻译器

包下载:go get github.com/go-playground/validator/v10

https://pkg.go.dev/github.com/go-playground/validator/v10#pkg-overview

一、概述

在接口开发经常会遇到一个问题是后端需要写大量的繁琐代码进行数据校验,所以就想着有没有像前端校验一样写规则进行匹配校验,然后就发现了validator包,一个比较强大的校验工具包下面是一些学习总结,详细内容可以查看validator

二、操作符说明

标记 标记说明
, 多操作符分割
| 或操作
- 跳过字段验证

三、常用标记说明

比较运算符

运算符 运算描述 例子
eq 等于 Field validate:"eq=0"
gt 大于 Field validate:"gt=0"
gte 大于等于 Field validate:"gte=0"
lt 小于 Field validate:"lt=0"
lte 小于等于 Field validate:"lte=0"
ne 不等于 Field validate:"ne=0"

字段验证运算

运算符 运算描述 例子
eqcsfield 跨不同结构体字段相等 Struct1.Field validate:"eqcsfield=Struct2.Field2"
eqfield 同一结构体字段相等 Field validate:"eqfield=Field2"
fieldcontains 包含字段
fieldexcludes 未包含字段
gtcsfield 跨不同结构体字段大于 Struct1.Field validate:"gtcsfield=Struct2.Field2"
gtecsfield 跨不同结构体字段大于等于 Struct1.Field validate:"gtecsfield=Struct2.Field2"
gtefield 同一结构体字段大于等于 Field validate:"gtefield=Field2"
gtfield 同一结构体字段相等 Field validate:"gtfield=Field2"
ltcsfield 跨不同结构体字段小于 Struct1.Field validate:"ltcsfield=Struct2.Field2"
ltecsfield 跨不同结构体字段小于等于 Struct1.Field validate:"ltecsfield=Struct2.Field2"
ltefield 同一个结构体字段小于等于 Field validate:"ltefield=Field2"
ltfield 同一个结构体字段小于 Field validate:"ltfield=Field2"
necsfield 跨不同结构体字段不想等 Struct1.Field validate:"necsfield=Struct2.Field2"
nefield 同一个结构体字段不想等 Field validate:"nefield=Field2"

网络字段验证运算

运算符 运算描述 例子
cidr 有效 CIDR
cidrv4 有效 CIDRv4
cidrv6 有效 CIDRv6
datauri 是否有效 URL
fqdn 有效完全合格的域名 (FQDN)
hostname 是否站点名称 RFC 952
hostname_port 是否站点端口
ip 是否包含有效 IP Field validate:"ip"
ip4_addr 是否有效 IPv4
ip6_addr 是否有效 IPv6
ip_addr 是否有效 IP
ipv4 是否有效 IPv4 Field validate:"ipv4"
ipv6 是否有效 IPv6
mac 是否媒体有效控制有效 MAC 地址
tcp4_addr 是否有效 TCPv4 传输控制协议地址
tcp6_addr 是否有效 TCPv6 传输控制协议地址
tcp_addr 是否有效 TCP 传输控制协议地址
udp4_addr 是否有效 UDPv4 用户数据报协议地
udp6_addr 是否有效 UDPv6 用户数据报协议地
udp_addr 是否有效 UDPv 用户数据报协议地
unix_addr Unix 域套接字端点地址
uri 是否包含有效的 URI Field validate:"uri"
url 是否包含有效的 URL Field validate:"url"

字符串验证运算

运算符 运算描述 例子
alpha 是否全部由字母组成的
alphanum 是否全部由数字组成的 Field validate:"alphanum"
alphanumunicode 是否全部由 unicode 字母数字组成 Field validate:"alphanumunicode"
alphaunicode 是否全部由 unicode 字母组成 Field validate:"alphaunicode"
ascii ASCII
contains 是否包含全部 Field validate:"contains=@"
endswith 尾部是否以此字符结束 Field validate:"endswith=abc"
lowercase 是否全小写字符组成 Field validate:"lowercase"
multibyte 是否多字节字符
number 是否数字
numeric 是否包含基本的数值 Field validate:"numeric"
printascii 是否可列印的 ASCII
startswith 开头是否以此字符开始 Field validate:"startswith=abc"
uppercase 是否全大写字符组成 Field validate:"uppercase"
containsfield 字段包含另一个字段 Field validate:"containsfield=Field2"
excludesfield 字段不包含另一个字段 Field validate:"excludesfield=Field2"
hexadecimal 字符串值是否包含有效的十六进制 Field validate:"hexadecimal"
hexcolor 字符串值是否包含有效的十六进制颜色 Field validate:"hexcolor"
containsany 字符串值包含子字符串值中的任何字符 Field validate:"containsany=abc"
containsrune 字符串值包含提供的特殊符号值 Field validate:"containsrune=☢"
excludes 字符串值不包含子字符串值 Field validate:"excludes=@"
excludesall 字符串值不包含任何子字符串值 Field validate:"excludesall=abc"
excludesrune 字符串值不包含提供的特殊符号值 Field validate:"containsrune=☢"
excluded_with
excluded_with_all
excluded_without
excluded_without_all

数据格式验证运算

运算符 运算描述 例子
base64 是否 Base64 Field validate:"base64"
base64url 是否 Base64UR
btc_addr 是否 Bitcoin 地址
btc_addr_bech32 是否 Bitcoin Bech32 地址
datetime 是否有效 Datetime Field validate:"datetime"
email 是否有效 E-mail Field validate:"email"
html 是否有效 HTML 标签
json 是否有效 JSON Field validate:"json"
rgb 是否有效 RGB
rgba 是否有效 RGBA
uuid 是否有效通用唯一标识符

其他验证运算

运算符 运算描述 例子
dir 是否有效目录
file 是否有效文件目录 Field validate:"file"
isdefault 是默认值 Field validate:"isdefault=0"
len 指定长度 Field validate:"len=0"
max 最大值 Field validate:"max=1"
min 最小值 Field validate:"min=1"
required 必须传入 Field或Struct validate:"required"
required_if Field validate:"required_if=Field1 Value"
required_unless
unique 唯一的 Field validate:"unique"
required_with 其他字段其中一个不为空且当前字段不为空 Field validate:"required_with=Field1 Field2"
required_with_all 其他所有字段不为空且当前字段不为空 Field validate:"required_with_all=Field1 Field2"
required_without 其他字段其中一个为空且当前字段不为空 Field `validate:"required_without=Field1 Field2"
required_without_all 其他所有字段为空且当前字段不为空 Field validate:"required_without_all=Field1 Field2"
oneof 其中之一 Field validate:"oneof=5 7 9"
omitempty 空时忽略 Field或Struct validate:"omitempty"
structonly 仅验证结构体,不验证任何结构体字段 Struct validate:"structonly"
nostructlevel 不运行任何结构级别的验证 Struct validate:"nostructlevel"
dive 向下延伸验证,多层向下需要多个dive标记 [][]string validate:"gt=0,dive,len=1,dive,required"
dive Keys & EndKeys 与dive同时使用,用于对map对象的键的和值的验证,keys为键,endkeys为值 map[string]string validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"

Aliases:

Tag Description
iscolor hexcolor|rgb|rgba|hsl|hsla
country_code iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

四、标记使用注意

1、当搜索条件与特殊标记冲突时,如:逗号(,),或操作(|),中横线(-)等则需要使用 UTF-8十六进制表示形式

例:

1
2
3
4
Copytype Test struct {
Field1 string `validate:"excludesall=|"` // 错误
Field2 string `validate:"excludesall=0x7C"` // 正确.
}

2、可通过validationErrors := errs.(validator.ValidationErrors)获取错误对象自定义返回响应错误

3、自定义校验结果翻译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始化翻译器
func validateInit() {
zh_ch := zh.New()
uni := ut.New(zh_ch) // 万能翻译器,保存所有的语言环境和翻译数据
Trans, _ = uni.GetTranslator("zh") // 翻译器
Validate = validator.New()
_ = zh_translations.RegisterDefaultTranslations(Validate, Trans)
// 添加额外翻译
_ = Validate.RegisterTranslation("required_without", Trans, func(ut ut.Translator) error {
return ut.Add("required_without", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_without", fe.Field())
return t
})
}

五、使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// 实例化验证对象
var validate = validator.New()
func main() {
// 结构体验证
type Inner struct {
String string `validate:"contains=111"`
}
inner := &Inner{String: "11@"}
errs := validate.Struct(inner)
if errs != nil {
fmt.Println(errs.Error())
}
// 变量验证
m := map[string]string{"": "", "val3": "val3"}
errs = validate.Var(m, "required,dive,keys,required,endkeys,required")
if errs != nil {
fmt.Println(errs.Error())
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package main

import (
"fmt"
"github.com/go-playground/validator/v10"
)

// 用户信息
type User struct {
Name string `validate:"required"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
Phone int64 `validate:"required,number"`
Addresses []*Address `validate:"required,dive,required"`
}

// 用户地址信息
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Phone string `validate:"required"`
}

// 用 Validate 的单个实例来缓存结构体信息
var validate *validator.Validate


func main() {
//创建一个示例
validate = validator.New()

address := &Address{
Street: "Lujiazui No.21 ",
City: "Shanghai",
Phone: "021-7777777",
}

user := &User{
Name: "Aklman",
Age: 135,
Email: "aklman888@gmail.com",
Phone: 17702177888,
Addresses: []*Address{address},
}
//验证结构体
validateStruct(user)
//单一验证变量
validateVariable()
}

//复杂结构数据的验证
func validateStruct(user *User) {
err := validate.Struct(user)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
fmt.Println(err)
return
}

for _, err := range err.(validator.ValidationErrors) {

fmt.Println(err.Namespace())
fmt.Println(err.Field())
fmt.Println(err.StructNamespace())
fmt.Println(err.StructField())
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
fmt.Println()
}
return
}

// 执行下一步操作
}

//单一结构体的数据验证
func validateVariable() {
myEmail := "aklman888@gmail.com"

errs := validate.Var(myEmail, "required,email")

if errs != nil {
fmt.Println(errs)
return
}
}

dive
dive用于深入到切片、数组和map中进行数据校验
Example#1

[][]

1
2
3
4
string with validation tag "gt=0,dive,len=1,dive,required"
// gt=0 will be applied to []
// len=1 will be applied to []string
// required will be applied to string

Example#2

1
2
3
4
string with validation tag "gt=0,dive,dive,required"
// gt=0 will be applied to []
// []string will be spared validation
// required will be applied to string

六、gin框架中使用验证翻译器

1. 定义错误翻译器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//validator.go
// 定义一个全局翻译器
var trans ut.Translator

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
//修改gin框架中的Validator属性,实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json tag的自定义方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

zhT := zh.New() //中文翻译器
enT := en.New() //英文翻译器

// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)

// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}

// 添加额外翻译
_ = v.RegisterTranslation("required_with", trans, func(ut ut.Translator) error {
return ut.Add("required_with", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_with", fe.Field())
return t
})
_ = v.RegisterTranslation("required_without", trans, func(ut ut.Translator) error {
return ut.Add("required_without", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_without", fe.Field())
return t
})
_ = v.RegisterTranslation("required_without_all", trans, func(ut ut.Translator) error {
return ut.Add("required_without_all", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_without_all", fe.Field())
return t
})

// 注册翻译器
switch locale {
case "en":
err = enTranslations.RegisterDefaultTranslations(v, trans)
case "zh":
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}

func addValueToMap(fields map[string]string) map[string]interface{} {
res := make(map[string]interface{})
for field, err := range fields {
fieldArr := strings.SplitN(field, ".", 2)
if len(fieldArr) > 1 {
NewFields := map[string]string{fieldArr[1]: err}
returnMap := addValueToMap(NewFields)
if res[fieldArr[0]] != nil {
for k, v := range returnMap {
res[fieldArr[0]].(map[string]interface{})[k] = v
}
} else {
res[fieldArr[0]] = returnMap
}
continue
} else {
res[field] = err
continue
}
}
return res
}

// 去掉结构体名称前缀
func removeTopStruct(fields map[string]string) map[string]interface{} {
lowerMap := map[string]string{}
for field, err := range fields {
fieldArr := strings.SplitN(field, ".", 2)
lowerMap[fieldArr[1]] = err
}
res := addValueToMap(lowerMap)
return res
}

//handler中调用的错误翻译方法
func ErrResp(err error) *gin.H {
errs, ok := err.(validator.ValidationErrors)
fmt.Println(reflect.TypeOf(err))
if !ok {
return &gin.H{
"errCode": -1,
"errMsg": err.Error(), // 翻译校验错误提示
}
}
return &gin.H{
"errCode": -1,
"errMsg": removeTopStruct(errs.Translate(trans)), // 翻译校验错误提示
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//router.go
//初始化翻译器,这部分可放在main.go或router.go中
if err := util.InitTrans("zh"); err != nil {
log.Fatalf("init trans failed, err:%v\n", err)
return
}
//handler.go
//翻译错误
func (h handler) Login(c *gin.Context) {
req := models.LoginReq{}
err := c.ShouldBindWith(&req, binding.JSON)
if err != nil {
c.JSON(http.StatusBadRequest, util.ErrResp(err)) //这里调用翻译
return
}
}

2. 定义错误翻译器2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package validate

import (
"errors"
"fmt"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
"strings"
)

// 定义一个全局翻译器T
var trans ut.Translator

func getValidator() (*validator.Validate, error) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
return v, nil
}
return nil, errors.New("get validator failed")
}

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
// 修改gin框架中的Validator引擎属性,实现自定制

v, _ := getValidator()

zhT := zh.New() // 中文翻译器
enT := en.New() // 英文翻译器

// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)

// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}

// 注册翻译器
switch locale {
case "en":
err = enTranslations.RegisterDefaultTranslations(v, trans)
case "zh":
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
if err != nil {
return err
}
return
}

// registerTranslator 为自定义字段添加翻译功能
func registerTranslator(tag string, msg string) validator.RegisterTranslationsFunc {
return func(trans ut.Translator) error {
if err := trans.Add(tag, msg, false); err != nil {
return err
}
return nil
}
}

// translate 自定义字段的翻译方法
func translate(trans ut.Translator, fe validator.FieldError) string {
msg, err := trans.T(fe.Tag(), fe.Field())
if err != nil {
panic(fe.(error).Error())
}
return msg
}

// RegisterTranslation
// 注意!因为这里会使用到trans实例
// 所以这一步注册要放到trans初始化的后面
func RegisterTranslation(tr map[string]string) (err error) {
v, _ := getValidator()
for key, val := range tr {
if err := v.RegisterTranslation(
key,
trans,
registerTranslator(key, val),
translate,
); err != nil {
return err
}
}
return
}

func RegisterTagName(fn validator.TagNameFunc) {
v, _ := getValidator()
// 注册一个获取json tag的自定义方法
v.RegisterTagNameFunc(fn)

}

func RegisterStructValidation(fn validator.StructLevelFunc, types ...interface{}) {
v, _ := getValidator()
v.RegisterStructValidation(fn, types...)
}

func RegisterValidation(tag string, fn validator.Func) (err error) {
v, _ := getValidator()
// 在校验器注册自定义的校验方法
if err := v.RegisterValidation(tag, fn); err != nil {
return err
}
return
}

func RegisterCustomType(fn validator.CustomTypeFunc, types ...interface{}) {
v, _ := getValidator()
// 在校验器注册自定义类型的校验方法
v.RegisterCustomTypeFunc(fn, types...)
}

func removeTopStruct(fields map[string]string) map[string]string {
res := map[string]string{}
for field, err := range fields {
res[field[strings.Index(field, ".")+1:]] = err
}
return res
}

func GetError(err error) error {
var result []string
if err == nil {
return err
}

errs, ok := err.(validator.ValidationErrors)
if !ok {
return err
} else {
for _, e := range errs.Translate(trans) {
result = append(result, e)
}
}

return errors.New(strings.Join(result, ";"))
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
package validate

import (
"fmt"
"github.com/go-playground/validator/v10"
"reflect"
"strings"
"testing"
"time"
)

// 自定义错误提示信息的字段名
func TestRegisterTagName(t *testing.T) {
type SignUpParam struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
// Name为必填字段;Email为必填字段 => name为必填字段;email为必填字段
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

v, _ := getValidator()
inner := &SignUpParam{Age: 1, Name: "", Email: ""}
errs := v.Struct(inner)
if errs != nil {
t.Log(GetError(errs))
}
// out:
// name为必填字段;email为必填字段

}

// 自定义结构体校验方法
func TestRegisterStructValidation(t *testing.T) {
type SignUpParam struct {
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
// Name为必填字段;Email为必填字段 => name为必填字段;email为必填字段
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

// 为SignUpParam注册自定义校验方法
RegisterStructValidation(func(sl validator.StructLevel) {
su := sl.Current().Interface().(SignUpParam)

if su.Password != su.RePassword {
// 输出错误提示信息,最后一个参数就是传递的param
sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password")
}
}, SignUpParam{})

v, _ := getValidator()
inner := &SignUpParam{Password: "1", RePassword: ""}
errs := v.Struct(inner)
if errs != nil {
t.Log(GetError(errs))
}
// out:
// re_password必须等于password
}

// 自定义字段校验方法
func TestRegisterValidation(t *testing.T) {
type SignUpParam struct {
// 需要使用自定义校验方法checkDate做参数校验的字段Date
Date string `json:"date" binding:"required,datetime=2006-01-02,checkDate"`
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
// Name为必填字段;Email为必填字段 => name为必填字段;email为必填字段
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

if err := RegisterValidation("checkDate", func(fl validator.FieldLevel) bool {
date, err := time.Parse("2006-01-02", fl.Field().String())
if err != nil {
return false
}
if date.Before(time.Now()) {
return false
}
return true
}); err != nil {
t.Log(err)
return
}

// 自定义翻译方法
// 'SignUpParam.date' Error:Field validation for 'date' failed on the 'checkDate' tag => date必须要晚于当前日期
tr := make(map[string]string)
tr["checkDate"] = "{0}必须要晚于当前日期"
if err := RegisterTranslation(tr); err != nil {
t.Log(err)
}

v, _ := getValidator()
inner := &SignUpParam{Date: "2020-10-12"}
errs := v.Struct(inner)
if errs != nil {
t.Log(GetError(errs))
}
// out:
// date必须要晚于当前日期
}

type JsonTime struct {
t time.Time
}

func (t JsonTime) String() string {
return t.t.Format("2006-01-02 15:04:05")
}

func TestRegisterCustomType(t *testing.T) {

type SignUpParam struct {
TestTime JsonTime `json:"test_time" binding:"required"`
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
// Name为必填字段;Email为必填字段 => name为必填字段;email为必填字段
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

// 为SignUpParam注册自定义校验方法
RegisterCustomType(func(field reflect.Value) interface{} {
if field.Type() == reflect.TypeOf(JsonTime{}) {
if jt, ok := field.Interface().(JsonTime); ok {
timeStr := jt.String()
// 0001-01-01 00:00:00 是 go 中 time.Time 类型的空值
// 这里返回 Nil 则会被 validator 判定为空值,而无法通过 `binding:"required"` 规则
if timeStr == "0001-01-01 00:00:00" {
return nil
}
return timeStr
}

}
return nil
}, JsonTime{})

v, _ := getValidator()
inner := &SignUpParam{TestTime: JsonTime{}}
errs := v.Struct(inner)
if errs != nil {
t.Log(GetError(errs))
}
// out:
// test_time为必填字段
}

func TestRegisterAlias(t *testing.T) {
type Test struct {
Array []string `json:"array" binding:"required,gt=0,dive,required"`
Map map[string]string `json:"map" binding:"required,gt=0,dive,keys,keymax,endkeys,required,max=1000"`
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

v, _ := getValidator()
v.RegisterAlias("keymax", "max=10")
inner := &Test{}
err := v.Struct(inner)
t.Log(GetError(err))

inner.Array = []string{"", ""}
inner.Map = map[string]string{"test > than 10": ""}
err = v.Struct(inner)
t.Log(GetError(err))

}

func TestVarWithValue(t *testing.T) {
name1 := "dj"
name2 := "dj2"

v, _ := getValidator()
fmt.Println(v.VarWithValue(name1, name2, "eqfield"))
fmt.Println(v.VarWithValue(name1, name2, "nefield"))
}

func TestValidateMap(t *testing.T) {
data := map[string]interface{}{
"name": "Arshiya Kiani",
"email": "zytel3301@gmail.com",
"details": map[string]interface{}{
"family_members": map[string]interface{}{
"father_name": "m",
"mother_name": "h",
},
"salary": "1000",
},
}

// Rules must be set as the structure as the data itself. If you want to dive into the
// map, just declare its rules as a map
rules := map[string]interface{}{
"name": "min=4,max=32",
"email": "required,email",
"details": map[string]interface{}{
"family_members": map[string]interface{}{
"father_name": "required,min=4,max=32",
"mother_name": "required,min=4,max=32",
},
"salary": "number",
},
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

v, _ := getValidator()
r := v.ValidateMap(data, rules)
t.Log(r)
}

func TestDive(t *testing.T) {
// Address houses a users address information
type Address struct {
Street string `binding:"required"`
City string `binding:"required"`
Planet string `binding:"required"`
Phone string `binding:"required"`
}

// User contains user information
type User struct {
FirstName string `json:"fname"`
LastName string `json:"lname"`
Age uint8 `binding:"gte=0,lte=130"`
Email string `json:"e-mail" validate:"required,email"`
FavouriteColor string `binding:"hexcolor|rgb|rgba"`
Addresses []*Address `binding:"required,dive,required"` // a person can have a home and cottage...
}

if err := InitTrans("zh"); err != nil {
fmt.Printf("init trans failed, err:%v\n", err)
return
}

// 注册一个获取json tag的自定义方法
RegisterTagName(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})

RegisterStructValidation(func(sl validator.StructLevel) {
user := sl.Current().Interface().(User)

if len(user.FirstName) == 0 && len(user.LastName) == 0 {
sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "")
sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "")
}

}, User{})

// 自定义翻译方法
tr := make(map[string]string)
tr["fnameorlname"] = "{0}名字不能为空"
if err := RegisterTranslation(tr); err != nil {
t.Log(err)
}

// build 'User' info, normally posted data etc...
address := &Address{
Street: "Eavesdown Docks",
Planet: "Persphone",
Phone: "none",
City: "Unknown",
}

user := &User{
FirstName: "",
LastName: "",
Age: 45,
Email: "Badger.Smith@gmail",
FavouriteColor: "#000",
Addresses: []*Address{address},
}

// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
v, _ := getValidator()
err := v.Struct(user)
if err != nil {

// this check is only needed when your code could produce
// an invalid value for validation such as interface with nil
// value most including myself do not usually have code like this.
if _, ok := err.(*validator.InvalidValidationError); ok {
t.Log(err)
return
}

for _, err := range err.(validator.ValidationErrors) {

t.Log(err.Namespace()) // can differ when a custom TagNameFunc is registered or
t.Log(err.Field()) // by passing alt name to ReportError like below
t.Log(err.StructNamespace())
t.Log(err.StructField())
t.Log(err.Tag())
t.Log(err.ActualTag())
t.Log(err.Kind())
t.Log(err.Type())
t.Log(err.Value())
t.Log(err.Param())
t.Log()
}

t.Log(GetError(err))
// from here you can create your own error messages in whatever language you wish
return
}

}