Skip to main content
Version: 2.8.x(Latest)

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:

  1. Context parameter ctx is required.
  2. 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 a map or struct, particularly in joint validation. This value is runtime input and may be nil.
tip

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.

warning

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)
}