当前位置: 首页 > article >正文

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

自定义字段名称的显示

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


代码示例:自定义字段名称
package main

import (
	"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 validate

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

type StructLevelUser struct {
	FirstName string
	LastName  string
	Age       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-email
			name == "-" 的意义
			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.InvalidValidationError
		if errors.As(err, &InvalidValidationError) {
			fmt.Println(err)
			return
		} else {
			for _, err := range err.(validator.ValidationErrors) {
				fmt.Println(err)
			}
			return
		}
	}
}


struct.go

package validate

import (
	"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 必须为email
	Address *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.ValidationErrors
		if 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.kler.cn/a/523020.html

相关文章:

  • 第十四讲 JDBC数据库
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-head.py
  • 基于 AWS SageMaker 对 DeepSeek-R1-Distilled-Llama-8B 模型的精调与实践
  • gesp(C++六级)(6)洛谷:P10109:[GESP202312 六级] 工作沟通
  • 【NLP251】NLP RNN 系列网络
  • Python3 【函数】:见证算法的优雅与力量
  • 2. Java-MarkDown文件解析-工具类
  • 20【变量的深度理解】
  • 最大值的期望 与 期望的最大值
  • mysql学习笔记-事务基础知识
  • 渗透测试之WAF规则触发绕过规则之规则库绕过方式
  • Linux进程调度与等待:背后的机制与实现
  • 大数据学习之Kafka消息队列、Spark分布式计算框架一
  • AWS SimSpace Weaver
  • 如何在本地部署deepseek r1模型?
  • 物业软件推动物业行业数字化转型 实现高效管理和优质客户体验
  • 【PyTorch】6.张量形状操作:在深度学习的 “魔方” 里,玩转张量形状
  • Couchbase UI: Query
  • 具身智能技术趋势
  • 编程语言中的常见Bug及解决方案
  • 2024年除夕
  • Hive存储系统全面测试报告
  • MATLAB中处理大数据的技巧与方法
  • 计算机网络之计算机网络的定义、组成与功能
  • 如何靠Python完成设备巡检
  • 数字电子技术(十六)——CMOS反相器以及CMOS反相器的电路结构和工作原理