Relevant Data Structure
Custom rule method definition, and the corresponding input parameter data structure.
// RuleFuncInput holds the input parameters that passed to custom rule function RuleFunc.
type RuleFuncInput struct {
// Rule specifies the validation rule string, like "required", "between:1,100", etc.
Rule string
// Message specifies the custom error message or configured i18n message for this rule.
Message string
// Value specifies the value for this rule to validate.
Value *gvar.Var
// Data specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value.
// You can ignore the parameter `Data` if you do not really need it in your custom validation rule.
Data *gvar.Var
}
// RuleFunc is the custom function for data validation.
type RuleFunc func(ctx context.Context, in RuleFuncInput) error
Brief method parameter explanation:
- Context parameter
ctx
is required. RuleFuncInput
data structure explanation:Rule
indicates the current validation rule, including rule parameters, such as:required
,between:1,100
,length:6
etc.Message
specifies the validation error message returned upon validation failure.Value
specifies the data value being validated. Note that the type is a*gvar.Var
generic, so you can pass any type of parameter.Data
specifies the parameters passed during validation, useful when validating amap
orstruct
, particularly in joint validation. This value is runtime input and may benil
.
Custom errors natively support i18n
features. You only need to configure i18n translation information following gf.gvalid.rule.custom_rule_name
. This information is automatically fetched from the i18n manager upon validation failure and passed to your registered custom validation method via the Message
parameter.
Global Validation Rule Registration
Custom rules are divided into two types: global rule registration and local rule registration.
Global rules are rules that take effect globally. After registration, they can be used for data validation by method or object usage.
Register validation method:
// RegisterRule registers custom validation rule and function for package.
func RegisterRule(rule string, f RuleFunc) {
customRuleFuncMap[rule] = f
}
// RegisterRuleByMap registers custom validation rules using map for package.
func RegisterRuleByMap(m map[string]RuleFunc) {
for k, v := range m {
customRuleFuncMap[k] = v
}
}
You need to define a validation method as per RuleFunc
type, implement the validation you need, and then use RegisterRule
to register it in the global management of the gvalid
module. This registration logic is generally executed during program initialization. This method will be automatically called when data validation is performed; returning nil
indicates validation success, otherwise, a non-empty error
type value should be returned.
Note: The custom rule registration methods do not support concurrent calls. You need to register it during program startup (such as in the boot
package); dynamic registration at runtime is not possible, otherwise, it will cause concurrency safety issues.
Example 1, Order ID Existence Validation
In e-commerce business, when we perform operations on orders, we can validate whether the given order ID exists through custom rules. Therefore, we can register a global rule order-exist
to achieve this.
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gvalid"
"time"
)
type Request struct {
OrderId int64 `v:"required|order-exist"`
ProductName string
Amount int64
// ...
}
func init() {
rule := "order-exist"
gvalid.RegisterRule(rule, RuleOrderExist)
}
func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error {
// SELECT COUNT(*) FROM `order` WHERE `id` = xxx
count, err := g.Model("order").
Ctx(ctx).
Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "",
Force: false,
}).
WhereNot("id", in.Value.Int64()).
Count()
if err != nil {
return err
}
if count == 0 {
return gerror.Newf(`invalid order id "%d"`, in.Value.Int64())
}
return nil
}
func main() {
var (
ctx = gctx.New()
req = &Request{
OrderId: 65535,
ProductName: "HikingShoe",
Amount: 10000,
}
)
err := g.Validator().CheckStruct(ctx, req)
fmt.Println(err)
}
Example 2, User Uniqueness Rule
During user registration, we often need to check whether the submitted name/account is unique. Therefore, we can register a global rule unique-name
to achieve this.
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gvalid"
"time"
)
type User struct {
Id int
Name string `v:"required|unique-name#Please enter the user name|User name is already taken"`
Pass string `v:"required|length:6,18"`
}
func init() {
rule := "unique-name"
gvalid.RegisterRule(rule, RuleUniqueName)
}
func RuleUniqueName(ctx context.Context, in gvalid.RuleFuncInput) error {
var user *User
if err := in.Data.Scan(&user); err != nil {
return gerror.Wrap(err, `Scan data to user failed`)
}
// SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx
count, err := g.Model("user").
Ctx(ctx).
Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "",
Force: false,
}).
WhereNot("id", user.Id).
WhereNot("name", user.Name).
Count()
if err != nil {
return err
}
if count > 0 {
if in.Message != "" {
return gerror.New(in.Message)
}
return gerror.Newf(`user name "%s" is already token by others`, user.Name)
}
return nil
}
func main() {
var (
ctx = gctx.New()
user = &User{
Id: 1,
Name: "john",
Pass: "123456",
}
)
err := g.Validator().CheckStruct(ctx, user)
fmt.Println(err)
}
Local Validation Rule Registration
Local rules are rules that take effect only within the current validation object. Validation rules are registered into the current chain operation process being used rather than globally.
Registration method:
// RuleFunc registers one custom rule function to current Validator.
func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator
// RuleFuncMap registers multiple custom rule functions to current Validator.
func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator
Brief introduction:
RuleFunc
method is used to register a single custom validation rule to the current object.RuleFuncMap
method is used to register multiple custom validation rules to the current object.
Usage example:
We will change one of the examples above to local validation rule registration.
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gvalid"
"time"
)
type Request struct {
OrderId int64
ProductName string
Amount int64
// ...
}
func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error {
// SELECT COUNT(*) FROM `order` WHERE `id` = xxx
count, err := g.Model("order").
Ctx(ctx).
Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "",
Force: false,
}).
WhereNot("id", in.Value.Int64()).
Count()
if err != nil {
return err
}
if count == 0 {
return gerror.Newf(`invalid order id "%d"`, in.Value.Int64())
}
return nil
}
func main() {
var (
ctx = gctx.New()
req = &Request{
OrderId: 65535,
ProductName: "HikingShoe",
Amount: 10000,
}
)
err := g.Validator().RuleFunc("order-exist", RuleOrderExist).Data(req).Run(ctx)
fmt.Println(err)
}