Custom Checkers
Need something the built-ins don't cover? Register your own checker by adding a function to the govalid.Checkers map. Once registered, the new rule is usable from any valid tag — no other plumbing required.
The simplest case
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)
}
}
}内容can not contain 'e99'The contract
A checker is:
type CheckFunc func(ctx CheckerContext) *ErrContextReturn nil on success and a non-nil *ErrContext on failure.
The CheckerContext carries everything you need:
| Field | What it gives you |
|---|---|
FieldValue | the actual value (you'll usually type-assert it) |
FieldType | reflect.Type of the field |
FieldName | Go field name |
FieldLabel | resolved label (locale-aware) |
StructValue | parent struct as reflect.Value (for cross-field rules) |
Rule.params | parameters from the tag, e.g. ["a","b","c"] for list:a,b,c |
TemplateLanguage | active locale |
Error helpers
Don't build *ErrContext by hand. Use these helpers:
| Helper | When to use |
|---|---|
NewErrorContext(c) | Standard rule failure — uses your registered template. |
MakeValueTypeError(c) | Field's runtime kind isn't supported. |
MakeCheckerParamError(c) | Tag parameters are missing or malformed. |
MakeFieldNotFoundError(c) | Cross-field rule referenced an unknown field. |
Using parameters
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)
}type Form struct {
Path string `valid:"startsWith:/api,/admin"`
}Looking at sibling fields
Use c.StructValue to read other fields by name. The built-in equal checker is a good template:
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)
}Supplying a localized message
Every built-in checker has an entry in the locale templates. Add yours with SetMessageTemplates for each locale you support:
govalid.SetMessageTemplates(map[string]string{
"noE99": "can not contain 'e99'",
}, language.English)
govalid.SetMessageTemplates(map[string]string{
"noE99": "不能包含 e99",
}, language.Chinese)TIP
The template string is concatenated with the field label. Templates that start with {{ and end with }} skip the label entirely — useful for system-style messages like _unknownErrorTemplate.
Registering at startup
The Checkers map is not synchronized for concurrent writes. Mutate it once at program startup, then leave it alone while requests are flying:
func init() {
govalid.Checkers["noE99"] = noE99Checker
}Reading the map is safe for concurrent use as long as no writer is active at the same time, which is the typical Go map contract.