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

ops/2025/2/4 18:31:49/

自定义字段名称的显示

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/ops/155643.html

相关文章

网络安全攻防实战:从基础防护到高级对抗

📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 引言 在信息化时代,网络安全已经成为企业、政府和个人必须重视的问题。从数据泄露到勒索软件攻击,每一次…

实验六 项目二 简易信号发生器的设计与实现 (HEU)

声明:代码部分使用了AI工具 实验六 综合考核 Quartus 18.0 FPGA 5CSXFC6D6F31C6N 1. 实验项目 要求利用硬件描述语言Verilog(或VHDL)、图形描述方式、IP核,结合数字系统设计方法,在Quartus开发环境下&#xff…

Word List 2

词汇颜色标识解释 词汇表中的生词 词汇表中的词组成的搭配、派生词 例句中的生词 我自己写的生词(用于区分易混淆的词,无颜色标识) 不认识的单词或句式 单词的主要汉语意思 不太理解的句子语法和结构 Word List 2 英文音标中文regi…

HarmonyOS:给您的应用添加通知

一、通知介绍 通知旨在让用户以合适的方式及时获得有用的新消息,帮助用户高效地处理任务。应用可以通过通知接口发送通知消息,用户可以通过通知栏查看通知内容,也可以点击通知来打开应用,通知主要有以下使用场景: 显示…

《 C++ 点滴漫谈: 二十五 》空指针,隐秘而危险的杀手:程序崩溃的真凶就在你眼前!

摘要 本博客全面解析了 C 中指针与空值的相关知识,从基础概念到现代 C 的改进展开,涵盖了空指针的定义、表示方式、使用场景以及常见注意事项。同时,深入探讨了 nullptr 的引入及智能指针在提升代码安全性和简化内存管理方面的优势。通过实际…

从零开始构建一个JAVA项目

本篇文章将从结构框架入手,系统介绍一个完整Java程序的结构步骤,不涉及JAVA基础代码学习。 在本文章中先简单介绍Maven、Spring、MyBatis三种Java类型。 一、分类介绍 首先我们先来了解Java程序的类型,不同类型结构略有区别。Java程序的类型…

网络基础

协议 协议就是约定 网络协议是协议中的一种 协议分层 协议本身也是软件,在设计上为了更好的模块化,解耦合,也是设计成为层状结构的 两个视角: 小白:同层协议,直接通信 工程师:同层协议&…

MySQL(3)

约束 概述 约束演示 外键约束 概念:约束是作用在表中字段上的规则,用于限制存储在表中的数据 目的:保证数据库中数据的正确,有效性和完整性 not null 非空约束 unique 唯一约束 primary key 主键约束 主键是一行数据的唯一…