【Validator】自定义字段、结构体补充及自定义验证,go案例讲解ReportError和errors.As在其中的使用

devtools/2025/2/5 3:49:45/

自定义字段名称的显示

RegisterTagNameFunc,自定义字段名称的显示,以便于从字段标签(tag)中提取更有意义的名称。


代码示例:自定义字段名称
package mainimport ("fmt""reflect""strings""github.com/go-playground/validator/v10"
)type User struct {FirstName string `json:"first_name" fld:"First Name"`LastName  string `json:"last_name" fld:"Last Name"`Age       uint8  `json:"age" v:"gte=0,lte=130" fld:"Age"`Email     string `json:"email" v:"required,email" fld:"Email Address"`
}func main() {validate := validator.New()// 注册字段名称的自定义提取函数validate.RegisterTagNameFunc(func(field reflect.StructField) string {name := strings.SplitN(field.Tag.Get("fld"), ",", 2)[0]if name == "-" {return ""}return name})user := &User{FirstName: "",LastName:  "",Age:       150,Email:     "invalid-email",}err := validate.Struct(user)if err != nil {if validationErrors, ok := err.(validator.ValidationErrors); ok {for _, fieldErr := range validationErrors {fmt.Printf("Field: %s, Error: %s\n", fieldErr.Field(), fieldErr.Error())}} else {fmt.Println("Validation failed with unknown error:", err)}}
}

输出结果
Field: First Name, Error: Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag
Field: Last Name, Error: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag
Field: Age, Error: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
Field: Email Address, Error: Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag

自定义结构体级别验证

结构体级别验证允许定义复杂的跨字段验证逻辑。例如,我们可以验证 FirstNameLastName 至少需要一个非空。


代码示例:结构体级别验证
func StructLevelValidation(sl validator.StructLevel) {user := sl.Current().Interface().(User)if user.FirstName == "" && user.LastName == "" {sl.ReportError(user.FirstName, "FirstName", "FirstName", "requiredtogether", "")sl.ReportError(user.LastName, "LastName", "LastName", "requiredtogether", "")}
}func main() {validate := validator.New()// 注册结构体级别验证validate.RegisterStructValidation(StructLevelValidation, User)user := &User{FirstName: "",LastName:  "",Age:       25,Email:     "valid@example.com",}err := validate.Struct(user)if err != nil {if validationErrors, ok := err.(validator.ValidationErrors); ok {for _, fieldErr := range validationErrors {fmt.Printf("Field: %s, Error: %s\n", fieldErr.Field(), fieldErr.Error())}} else {fmt.Println("Validation failed with unknown error:", err)}}
}

输出结果
Field: FirstName, Error: Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'requiredtogether' tag
Field: LastName, Error: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'requiredtogether' tag

自定义验证函数

自定义验证函数允许我们定义更复杂的字段级别验证规则。例如,验证一个字符串是否是合法的 UUID 格式。


代码示例:字段级别自定义验证
func isUUID(fl validator.FieldLevel) bool {value := fl.Field().String()return len(value) == 36 // 简单模拟:UUID 长度为 36 个字符
}func main() {validate := validator.New()// 注册自定义验证函数validate.RegisterValidation("uuid", isUUID)type Resource struct {ID string `v:"required,uuid"`}resource := &Resource{ID: "invalid-uuid",}err := validate.Struct(resource)if err != nil {if validationErrors, ok := err.(validator.ValidationErrors); ok {for _, fieldErr := range validationErrors {fmt.Printf("Field: %s, Error: %s\n", fieldErr.Field(), fieldErr.Error())}} else {fmt.Println("Validation failed with unknown error:", err)}}
}

输出结果
Field: ID, Error: Key: 'Resource.ID' Error:Field validation for 'ID' failed on the 'uuid' tag

表格汇总

功能用法示例说明
自定义字段名称validate.RegisterTagNameFunc()从标签中提取更友好的字段名,如 fld:"First Name"
结构体级别验证validate.RegisterStructValidation(func, StructType)在结构体级别实现复杂跨字段逻辑
自定义字段验证validate.RegisterValidation("tag", func)自定义验证规则(如检查 UUID 格式)
错误报告sl.ReportError(field, name, structField, tag, param)在结构体验证中手动标记验证错误

补充

  • errors.As()的错误处理
  • ReportError进一步理解

struct_level.go

package validateimport ("errors""fmt""github.com/go-playground/validator/v10""reflect""strings"
)type StructLevelUser struct {FirstName stringLastName  stringAge       uint8  `v:"gte=0,lte=130"`Email     string `v:"required,email" fld:"e-email"`
}func StructLevel() {// 注册一个自定义的标签名称函数,决定了如何从结构体字段的标签中提取验证规则和显示名称。// 接受一个函数作为参数,该函数接收一个 reflect.StructField 类型的字段,// 并返回一个字符串,表示该字段的标签名称。validate.RegisterTagNameFunc(func(field reflect.StructField) string {name := strings.SplitN(field.Tag.Get("fld"), ",", 2)[0]/*如果 fld:"e-email,some-other-data",strings.SplitN会将其分为两部分:e-email 和 some-other-data,取第一部分即 e-emailname == "-" 的意义Go 的惯例中,标签值 "-" 通常用来表示“忽略”某个字段。例如:在 JSON 序列化时,json:"-" 表示跳过该字段。在这里,fld:"-" 也可以被约定为“忽略此字段的验证显示名称”。*/if name == "-" {return ""}return name})// 注册一个自定义的结构体级别的验证函数。// 它允许你在整个结构体层面进行更复杂的验证逻辑,而不仅仅是单个字段的验证。// 接受一个函数作为参数,该函数接收一个 validator.StructLevel 类型的参数。// StructLevel 提供了访问当前结构体实例的方法,以及报告错误的功能。validate.RegisterStructValidation(func(sl validator.StructLevel) {user := sl.Current().Interface().(StructLevelUser)if len(user.LastName) == 0 && len(user.FirstName) == 0 { // 如果 FirstName 和 LastName 至少要求一个非空// sl.ReportError 是 validator.StructLevel 提供的一个方法,用于在结构体级别的自定义验证中报告错误。/*fieldName验证错误时字段的自定义名称(如需要特定显示或与代码解耦)。一般与结构体字段名保持一致。structFieldName用于定位字段。应该与字段名完全匹配。tag无关紧要,是规定的处理标志自定义的标签,标识错误类型。例如,"required" 或 "fnamelname",可根据业务需求定义。param可选参数,表示验证规则的额外信息。通常用于补充说明或配置自定义逻辑(这里为空字符串)*/sl.ReportError(user.FirstName, "fname", "FirstName", "fnamelname", "")sl.ReportError(user.LastName, "lname", "LastName", "fnamelname", "")}}, StructLevelUser{})user := &StructLevelUser{FirstName: "n",LastName:  "c",Age:       100,Email:     "123nick@0voice.com",}err := validate.Struct(user)if err != nil {var InvalidValidationError *validator.InvalidValidationErrorif errors.As(err, &InvalidValidationError) {fmt.Println(err)return} else {for _, err := range err.(validator.ValidationErrors) {fmt.Println(err)}return}}
}

struct.go

package validateimport ("errors""fmt""github.com/go-playground/validator/v10"
)type User struct { // validate.SetTagName("v")在这里,结构体的标签开头起作用Name            string            `v:"required,alphaunicode"`Age             uint8             `v:"gte=10,lte=130"` // gte=10: 年龄必须大于等于 10。lte=130: 年龄必须小于等于 130。Phone           string            `v:"required,e164"`  // e164: 电话号码必须符合 E.164 格式(国际电话号码格式)Email           string            `v:"required,email"`FavouriteColor1 string            `v:"iscolor"`                    // 必须是一个有效的颜色值(例如:#ff0000)FavouriteColor2 string            `v:"hexcolor|rgb|rgba|hsl|hsla"` // 必须是十六进制颜色、RGB、RGBA、HSL 或 HSLA 颜色格式之一Address         *Address          `v:"required"`ContactUser     []*ContactUser    `v:"required,gte=1,dive"`                                                      // dive: 对列表中的每个元素进行递归验证,后面如果继续跟逻辑运算,则处理对象是dive递归的元素Hobby           []string          `v:"required,gte=2,dive,required,gte=2,alphaunicode"`                          // dive: 对列表中的每个元素进行递归验证Data            map[string]string `v:"required,gte=2,dive,keys,alpha,len=2,endkeys,required,gte=2,alphaunicode"` // keys,alpha,len=2: 键必须是字母且长度为 2。 endkeys: 结束对键的验证// required,gte=2,alphaunicode 值不能为空且必须包含至少 2 个字符,且只能是字母、数字或 Unicode 字符
}type ContactUser struct {Name    string   `v:"required,alphaunicode"`Age     uint8    `v:"gte=10,lte=130"`                                     // omitempty,代表当字段为空,不会出现在输出中Phone   string   `v:"required_without_all=Email Address,omitempty,e164"`  // required_without_all 无需全部,即 如果 Phone 和 Email 都为空,则 Phone 和 Email 都必须填写,否则,Phone 可以为空。Email   string   `v:"required_without_all=Phone Address,omitempty,email"` //  如果 Phone 和 Address 字段都为空,则 Email 必须存在;否则,Email 可以为空。 email 必须为emailAddress *Address `v:"required_without_all=Phone Email"`
}type Address struct {Province string `v:"required"`City     string `v:"required"`
}func StructValidate() {addr := &Address{Province: "湖南",City:     "长沙",}contactUser1 := &ContactUser{Name:  "nick",Age:   18,Phone: "+861380013800",}contactUser2 := &ContactUser{Name:  "张三",Age:   18,Email: "nick@0voice.com",}user := &User{Name:            "nick",Age:             18,Phone:           "+861380013800",Email:           "nick@0voice.com",FavouriteColor1: "#ffff",FavouriteColor2: "rgb(255,255,255)",Address:         addr,ContactUser:     []*ContactUser{contactUser1, contactUser2},Hobby:           []string{"篮球", "羽毛球"},Data:            map[string]string{"AB": "篮球", "CD": "羽毛球"},}err := validate.Struct(user)if err != nil {//if errors, ok := err.(validator.ValidationErrors); ok {// 错误的原因在于 validate.Struct(user) 返回的错误并不是直接的//validator.ValidationErrors 类型,而是可能被包装在其他类型的错误中。//因此,直接进行类型断言会失败。// 使用 errors.As 函数来检查是否包含 validator.ValidationErrors 类型的错误。var validationErrors validator.ValidationErrorsif errors.As(err, &validationErrors) { // errors.As 函数的第二个参数需要是一个指针,指向一个可以接收错误类型的变量。// 因此,一定要先var一个可以接受的,然后再使用errors.As,而不是直接if errors.As(err, &validator.ValidationErrors)for _, err := range validationErrors {fmt.Println(err)}} else {fmt.Println("Validation failed with unknown error:", err)}}fmt.Println("All validations passed!")
}

https://github.com/0voice


http://www.ppmy.cn/devtools/156165.html

相关文章

在LINUX上安装英伟达CUDA Toolkit

下载安装包 wget https://developer.download.nvidia.com/compute/cuda/12.8.0/local_installers/cuda-repo-rhel8-12-8-local-12.8.0_570.86.10-1.x86_64.rpm 安装RPM包 sudo rpm -i cuda-repo-rhel8-12-8-local-12.8.0_570.86.10-1.x86_64.rpm sudo dnf clean all sudo dnf…

Git进阶之旅:Git 多人合作

项目克隆: git clone 仓库地址:把远程项目克隆到本地形成一个本地的仓库 克隆下来的仓库和远程仓库的名称一致 注意:git clone 远程仓库地址 远程仓库名:把远程仓库克隆下来,并自定义仓库名 多人协作: …

Flutter使用Flavor实现切换环境和多渠道打包

在Android开发中通常我们使用flavor进行多渠道打包,flutter开发中同样有这种方式,不过需要在原生中配置 具体方案其实flutter官网个了相关示例(https://docs.flutter.dev/deployment/flavors),我这里记录一下自己的操作 Android …

【2】阿里面试题整理

[1]. 说一下Java与C的区别。 Java和C是两种在软件开发领域应用非常广泛的语言,但它们的设计理念和应用场景有所不同。 Java是一种基于JVM的解释型语言,具有跨平台性,使用自动垃圾回收机制,这使得开发者可以更专注于业务逻辑&…

SRS代码目录

代码目录: src/目录下核心代码: core:核心功能模块,包括日志、配置、错误处理等;protocol:实现RTMP、HTTP-FLV、HLS等协议的模块;app:应用层的实现,包括流的发布、播放…

普罗米修斯监控服务搭建位置全解析:权衡与抉择

在数字化时代,监控系统对于企业的稳定运营和业务发展至关重要。普罗米修斯作为一款备受青睐的开源监控和告警工具,其搭建位置的决策绝非小事,它紧密关联着监控系统的性能、可靠性与安全性,如同为整座大厦奠定基石。接下来&#xf…

npm 和 pip 安装中常见问题总结

安装路径的疑惑:NPM 和 PIP 的安装机制 NPM 安装路径规则: 依赖安装在项目目录下: 当你运行 npm install --save-dev jest,它会在当前目录(例如 F:\)下创建一个 node_modules 文件夹,把 jest 安…

MySQL为什么默认引擎是InnoDB ?

大家好,我是锋哥。今天分享关于【MySQL为什么默认引擎是InnoDB ?】面试题。希望对大家有帮助; MySQL为什么默认引擎是InnoDB ? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 默认引擎是 InnoDB,主要…