Skip to content

自定义校验器

需要内置规则没覆盖的能力?只需要把一个函数注册进 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
FieldNameGo 字段名
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 的 标准契约。

基于 MIT 协议发布。