一、背景

在服务的API接口层面,我们常常需要验证参数的有效性。 Golang中,大部分参数校验场景实际上是先将数据Bind到结构体,然后校验其字段值。

一般地,校验结构体字段值有如下两种实现方式。

emailmax:100

思考:有没有一种方式,即简单易用(少写代码),又能满足各种复杂的校验场景?

答案是:有!结构体标签表达式 go-tagexpr 的出现,为我们提供了兼得鱼和熊掌的第三种选择。

二、认识 go-tagexpr

go-tagexpr 允许Gopher们在 struct tag 写表达式代码,并通过高性能的解释器计算其结果。

安装

go get -u github.com/bytedance/go-tagexpr

下面使用一个小示例,演示含有枚举、比较、字段关联的较复杂场景。

示例代码

import (
	"fmt"

	tagexpr "github.com/bytedance/go-tagexpr"
)

func ExampleTagexpr() {
	vm := tagexpr.New("te")
	type Meteorology struct {
		Season      string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
		Weather     string `te:"$!='snowing' || (Season)$=='winter'"`
		Temperature int    `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"`
	}
	m := &Meteorology{
		Season:      "summer",
		Weather:     "snowing",
		Temperature: 40,
	}
	r := vm.MustRun(m)
	fmt.Println(r.Eval("Season"))
	fmt.Println(r.Eval("Weather"))
	fmt.Println(r.Eval("Temperature@range"))
	fmt.Println(r.Eval("Temperature@alarm"))
	// Output:
	// true
	// false
	// false
	// Uncomfortable temperature: 40
}

代码诠释:

vm := tagexpr.New("te")
type Meteorology struct {
    Season      string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
    Weather     string `te:"$!='snowing' || (Season)$=='winter'"`
    Temperature int    `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"`
}
m := &Meteorology{
    Season:      "summer",
    Weather:     "snowing",
    Temperature: 40,
}
r := vm.MustRun(m)
r.Eval("Season")
r.Eval("Weather")
r.Eval("Temperature@range")
r.Eval("Temperature@alarm")

获取更多关于 go-expr 结构体标签表达式的语法知识 -> 查看这里

二、使用Validator校验

Validator 是有 go-expr 包提供的一个采用结构体标签表达式的参数校验组件。

主要特性

msg

安装

go get -u github.com/bytedance/go-tagexpr

我们基于前面示例稍作修改,来演示如何使用validator校验结构体字段的有效性。

示例代码

import (
	"fmt"

	"github.com/bytedance/go-tagexpr/validator"
)

func ExampleValidator() {
	vd := validator.New("vd")
	type Meteorology struct {
		Season      string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
		Weather     string `vd:"$!='snowing' || (Season)$=='winter'"`
		Temperature int    `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"`
		Contact     string `vd:"email($)"`
	}
	m := &Meteorology{
		Season:      "summer",
		Weather:     "rain",
		Temperature: 40,
		Contact:     "henrylee2cn@gmail.com",
	}
	err := vd.Validate(m)
	if err != nil {
		fmt.Println(err)
	}
	// Output:
	// Uncomfortable temperature: 40
}

代码诠释:

vd := validator.New("vd")
type Meteorology struct {
    Season      string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
    Weather     string `vd:"$!='snowing' || (Season)$=='winter'"`
    Temperature int    `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"`
    Contact     string `vd:"email($)"`
}
m := &Meteorology{
    Season:      "summer",
    Weather:     "rain",
    Temperature: 40,
    Contact:     "henrylee2cn@gmail.com",
}
err := vd.Validate(m)

注册自己的校验函数

email($)

下面以 email 函数的实现为例,演示如何注册自己的校验函数:

var pattern = "^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$"

emailRegexp := regexp.MustCompile(pattern)

validator.RegValidateFunc("email", func(args ...interface{}) bool {
	if len(args) != 1 {
		return false
	}
	s, ok := args[0].(string)
	if !ok {
		return false
	}
	return emailRegexp.MatchString(s)
}, true)

其中,validator.RegValidateFunc 的定义如下:

func RegValidateFunc(funcName string, fn func(args ...interface{}) bool, force ...bool) error

RegValidateFunc的force可选参数,表示是否强制覆盖已经注册了的同名函数。

**结论:**validator的使用方法非常简单、灵活且具有良好的扩展性,能够轻松满足各种复杂的验证场景。

获取更多关于 validator 校验器的语法知识 -> 查看这里