自定义校验器
需要内置规则没覆盖的能力?只需要把一个函数注册进 govalid.Checkers map。一旦注册,新规则就可以在任何 valid 标签 里使用——不需要其他配置。
最简单的例子
go
package main
import (
"fmt"
"strings"
"github.com/wuhan005/govalid"
)
func main() {
govalid.SetMessageTemplates(map[string]string{
"noE99": "can not contain 'e99'",
})
govalid.Checkers["noE99"] = func(c govalid.CheckerContext) *govalid.ErrContext {
v, ok := c.FieldValue.(string)
if !ok {
return govalid.MakeValueTypeError(c)
}
if strings.Contains(v, "e99") {
return govalid.NewErrorContext(c)
}
return nil
}
r := struct {
Content string `valid:"noE99" label:"内容"`
}{Content: "helloe99"}
if errs, ok := govalid.Check(r); !ok {
for _, err := range errs {
fmt.Println(err)
}
}
}text
内容can not contain 'e99'契约
校验器是:
go
type CheckFunc func(ctx CheckerContext) *ErrContext成功时返回 nil,失败时返回非 nil 的 *ErrContext。
CheckerContext 携带你需要的一切:
| 字段 | 含义 |
|---|---|
FieldValue | 实际的值(通常你会进行类型断言) |
FieldType | 字段的 reflect.Type |
FieldName | Go 字段名 |
FieldLabel | 解析后的 label(locale 感知) |
StructValue | 父 struct 的 reflect.Value(用于跨字段规则) |
Rule.params | 标签参数,例如 list:a,b,c 解析后的 ["a","b","c"] |
TemplateLanguage | 当前 locale |
错误辅助函数
不要手动构造 *ErrContext。用这些辅助函数:
| 辅助函数 | 何时使用 |
|---|---|
NewErrorContext(c) | 标准的规则失败——使用你已注册的模板。 |
MakeValueTypeError(c) | 字段的运行时 kind 不被支持。 |
MakeCheckerParamError(c) | 标签参数缺失或非法。 |
MakeFieldNotFoundError(c) | 跨字段规则引用了不存在的字段。 |
使用参数
go
const checker = "startsWith"
govalid.SetMessageTemplates(map[string]string{
checker: "必须以指定前缀开头",
})
govalid.Checkers[checker] = func(c govalid.CheckerContext) *govalid.ErrContext {
if len(c.Rule.params) == 0 {
return govalid.MakeCheckerParamError(c)
}
v, ok := c.FieldValue.(string)
if !ok {
return govalid.MakeValueTypeError(c)
}
for _, p := range c.Rule.params {
if strings.HasPrefix(v, p) {
return nil
}
}
return govalid.NewErrorContext(c)
}go
type Form struct {
Path string `valid:"startsWith:/api,/admin"`
}查看兄弟字段
通过 c.StructValue 按名字读取其他字段。内置的 equal 校验器是个 不错的模板:
go
govalid.Checkers["different"] = func(c govalid.CheckerContext) *govalid.ErrContext {
if len(c.Rule.params) != 1 {
return govalid.MakeCheckerParamError(c)
}
if !c.StructValue.IsValid() || c.StructValue.Kind() != reflect.Struct {
return govalid.MakeFieldNotFoundError(c)
}
other := c.Rule.params[0]
structType := c.StructValue.Type()
for i := 0; i < structType.NumField(); i++ {
if structType.Field(i).Name == other {
if fmt.Sprintf("%v", c.FieldValue) == fmt.Sprintf("%v", c.StructValue.Field(i).Interface()) {
return govalid.NewErrorContext(c)
}
return nil
}
}
return govalid.MakeFieldNotFoundError(c)
}提供本地化消息
每个内置校验器在 locale 模板里都有一条目。为你支持的每种 locale 用 SetMessageTemplates 加上你的:
go
govalid.SetMessageTemplates(map[string]string{
"noE99": "can not contain 'e99'",
}, language.English)
govalid.SetMessageTemplates(map[string]string{
"noE99": "不能包含 e99",
}, language.Chinese)TIP
模板字符串会与字段 label 拼接。以 {{ 开头并以 }} 结尾的模板会完全跳过 label——适合做 _unknownErrorTemplate 这类系统级消息。
启动时注册
Checkers map 不支持并发写。在程序启动时改一次,请求开始流入 之后就别再动了:
go
func init() {
govalid.Checkers["noE99"] = noE99Checker
}只要没有写者活动,多 goroutine 读 map 是安全的——这就是 Go map 的 标准契约。