mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@ -10,7 +10,7 @@ func main() {
|
||||
s := g.Server()
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
r.SetCtx(gi18n.WithLanguage(r.Context(), "zh-CN"))
|
||||
r.SetCtx(gi18n.WithLanguage(r.Context(), r.GetString("lang", "zh-CN")))
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.ALL("/", func(r *ghttp.Request) {
|
||||
|
||||
@ -1 +1,3 @@
|
||||
OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。"
|
||||
OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。"
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
@ -12,9 +12,9 @@ import "context"
|
||||
// The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc.
|
||||
// The parameter `value` specifies the value for this rule to validate.
|
||||
// The parameter `message` specifies the custom error message or configured i18n message for this rule.
|
||||
// The parameter `params` specifies all the parameters that needs. You can ignore parameter `params` if
|
||||
// you do not really need it in your custom validation rule.
|
||||
type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error
|
||||
// The parameter `data` specifies the `data` which is passed to the Validator. It might be 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.
|
||||
type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error
|
||||
|
||||
var (
|
||||
// customRuleFuncMap stores the custom rule functions.
|
||||
|
||||
@ -13,21 +13,23 @@ import (
|
||||
|
||||
// Validator is the validation manager for chaining operations.
|
||||
type Validator struct {
|
||||
ctx context.Context // Context containing custom context variables.
|
||||
i18nManager *gi18n.Manager // I18n manager for error message translation.
|
||||
key string // Single validation key.
|
||||
value interface{} // Single validation value.
|
||||
data interface{} // Validation data, which is usually a map.
|
||||
rules interface{} // Custom validation data.
|
||||
messages interface{} // Custom validation error messages, which can be string or type of CustomMsg.
|
||||
useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`.
|
||||
ctx context.Context // Context containing custom context variables.
|
||||
i18nManager *gi18n.Manager // I18n manager for error message translation.
|
||||
key string // Single validation key.
|
||||
value interface{} // Single validation value.
|
||||
data interface{} // Validation data, which is usually a map.
|
||||
rules interface{} // Custom validation data.
|
||||
messages interface{} // Custom validation error messages, which can be string or type of CustomMsg.
|
||||
ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator.
|
||||
useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`.
|
||||
}
|
||||
|
||||
// New creates and returns a new Validator.
|
||||
func New() *Validator {
|
||||
return &Validator{
|
||||
ctx: context.TODO(), // Initialize an empty context.
|
||||
i18nManager: gi18n.Instance(), // Use default i18n manager.
|
||||
ctx: context.TODO(), // Initialize an empty context.
|
||||
i18nManager: gi18n.Instance(), // Use default i18n manager.
|
||||
ruleFuncMap: make(map[string]RuleFunc), // Custom rule function storing map.
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,3 +79,28 @@ func (v *Validator) Messages(messages interface{}) *Validator {
|
||||
newValidator.messages = messages
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// RuleFunc registers one custom rule function to current Validator.
|
||||
func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator {
|
||||
newValidator := v.Clone()
|
||||
newValidator.ruleFuncMap[rule] = f
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// RuleFuncMap registers multiple custom rule functions to current Validator.
|
||||
func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator {
|
||||
newValidator := v.Clone()
|
||||
for k, v := range m {
|
||||
newValidator.ruleFuncMap[k] = v
|
||||
}
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// getRuleFunc retrieves and returns the custom rule function for specified rule.
|
||||
func (v *Validator) getRuleFunc(rule string) RuleFunc {
|
||||
ruleFunc := v.ruleFuncMap[rule]
|
||||
if ruleFunc == nil {
|
||||
ruleFunc = customRuleFuncMap[rule]
|
||||
}
|
||||
return ruleFunc
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ func (v *Validator) doCheckMap(params interface{}) Error {
|
||||
value = v
|
||||
}
|
||||
// It checks each rule and its value in loop.
|
||||
if e := v.doCheckValue(key, value, rule, customMsgs[key], data); e != nil {
|
||||
if e := v.doCheckValue(key, value, rule, customMsgs[key], params, data); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===========================================================
|
||||
// Only in map and struct validations, if value is nil or empty
|
||||
@ -112,7 +112,7 @@ func (v *Validator) doCheckMap(params interface{}) Error {
|
||||
break
|
||||
}
|
||||
// Custom rules are also required in default.
|
||||
if _, ok := customRuleFuncMap[k]; ok {
|
||||
if f := v.getRuleFunc(k); f != nil {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
|
||||
@ -57,12 +57,16 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
}
|
||||
|
||||
var (
|
||||
inputParamMap map[string]interface{}
|
||||
checkRules = make(map[string]string)
|
||||
customMessage = make(CustomMsg)
|
||||
fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names.
|
||||
errorRules = make([]string, 0) // Sequence rules.
|
||||
inputParamMap map[string]interface{}
|
||||
checkRules = make(map[string]string)
|
||||
customMessage = make(CustomMsg)
|
||||
checkValueData = v.data
|
||||
fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names.
|
||||
errorRules = make([]string, 0) // Sequence rules.
|
||||
)
|
||||
if checkValueData == nil {
|
||||
checkValueData = object
|
||||
}
|
||||
switch v := v.rules.(type) {
|
||||
// Sequence tag: []sequence tag
|
||||
// Sequence has order for error results.
|
||||
@ -194,7 +198,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
for key, rule := range checkRules {
|
||||
_, value = gutil.MapPossibleItemByKey(inputParamMap, key)
|
||||
// It checks each rule and its value in loop.
|
||||
if e := v.doCheckValue(key, value, rule, customMessage[key], inputParamMap); e != nil {
|
||||
if e := v.doCheckValue(key, value, rule, customMessage[key], checkValueData, inputParamMap); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===================================================================
|
||||
// Only in map and struct validations, if value is nil or empty string
|
||||
@ -210,7 +214,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
break
|
||||
}
|
||||
// Custom rules are also required in default.
|
||||
if _, ok := customRuleFuncMap[k]; ok {
|
||||
if f := v.getRuleFunc(k); f != nil {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
|
||||
@ -29,11 +29,24 @@ type apiTime interface {
|
||||
// CheckValue checks single value with specified rules.
|
||||
// It returns nil if successful validation.
|
||||
func (v *Validator) CheckValue(value interface{}) Error {
|
||||
return v.doCheckValue("", value, gconv.String(v.rules), v.messages, v.data)
|
||||
return v.doCheckValue("", value, gconv.String(v.rules), v.messages, v.data, gconv.Map(v.data))
|
||||
}
|
||||
|
||||
// doCheckSingleValue does the really rules validation for single key-value.
|
||||
func (v *Validator) doCheckValue(key string, value interface{}, rules string, messages interface{}, paramMap ...interface{}) Error {
|
||||
//
|
||||
// The parameter `rules` specifies the validation rules string, like "required", "required|between:1,100", etc.
|
||||
// The parameter `value` specifies the value for this rules to be validated.
|
||||
// The parameter `messages` specifies the custom error messages for this rule, which is usually type of map/slice.
|
||||
// The parameter `dataRaw` specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value.
|
||||
// The parameter `dataMap` specifies the map that is converted from `dataRaw`. It is usually used internally
|
||||
func (v *Validator) doCheckValue(
|
||||
key string,
|
||||
value interface{},
|
||||
rules string,
|
||||
messages interface{},
|
||||
dataRaw interface{},
|
||||
dataMap map[string]interface{},
|
||||
) Error {
|
||||
// If there's no validation rules, it does nothing and returns quickly.
|
||||
if rules == "" {
|
||||
return nil
|
||||
@ -41,12 +54,8 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me
|
||||
// It converts value to string and then does the validation.
|
||||
var (
|
||||
// Do not trim it as the space is also part of the value.
|
||||
data = make(map[string]interface{})
|
||||
errorMsgArray = make(map[string]string)
|
||||
)
|
||||
if len(paramMap) > 0 && paramMap[0] != nil {
|
||||
data = gconv.Map(paramMap[0])
|
||||
}
|
||||
// Custom error messages handling.
|
||||
var (
|
||||
msgArray = make([]string, 0)
|
||||
@ -66,7 +75,7 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me
|
||||
for i := 0; ; {
|
||||
array := strings.Split(ruleItems[i], ":")
|
||||
_, ok := allSupportedRules[array[0]]
|
||||
if !ok && customRuleFuncMap[array[0]] == nil {
|
||||
if !ok && v.getRuleFunc(array[0]) == nil {
|
||||
if i > 0 && ruleItems[i-1][:5] == "regex" {
|
||||
ruleItems[i-1] += "|" + ruleItems[i]
|
||||
ruleItems = append(ruleItems[:i], ruleItems[i+1:]...)
|
||||
@ -85,26 +94,26 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me
|
||||
}
|
||||
for index := 0; index < len(ruleItems); {
|
||||
var (
|
||||
err error
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(ruleItems[index])
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
rulePattern = strings.TrimSpace(results[2])
|
||||
err error
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(ruleItems[index])
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
rulePattern = strings.TrimSpace(results[2])
|
||||
customRuleFunc RuleFunc
|
||||
)
|
||||
if len(msgArray) > index {
|
||||
customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index])
|
||||
}
|
||||
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
// Custom rule handling.
|
||||
// 1. It firstly checks and uses the custom registered rules functions in the current Validator.
|
||||
// 2. It secondly checks and uses the globally registered rules functions.
|
||||
// 3. It finally checks and uses the build-in rules functions.
|
||||
customRuleFunc = v.getRuleFunc(ruleKey)
|
||||
if customRuleFunc != nil {
|
||||
// It checks custom validation rules with most priority.
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = v.getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(paramMap) > 0 && paramMap[0] != nil {
|
||||
dataMap = gconv.Map(paramMap[0])
|
||||
}
|
||||
if err := f(v.ctx, ruleItems[index], value, message, dataMap); err != nil {
|
||||
message := v.getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
if err := customRuleFunc(v.ctx, ruleItems[index], value, message, dataRaw); err != nil {
|
||||
match = false
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
} else {
|
||||
@ -112,7 +121,7 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me
|
||||
}
|
||||
} else {
|
||||
// It checks build-in validation rules if there's no custom rule.
|
||||
match, err = v.doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap)
|
||||
match, err = v.doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, dataMap, customMsgMap)
|
||||
if !match && err != nil {
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
}
|
||||
@ -115,21 +115,6 @@ func ExampleCheckStruct3() {
|
||||
}
|
||||
|
||||
func ExampleRegisterRule() {
|
||||
rule := "unique-name"
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
var (
|
||||
id = gconv.Int(params["Id"])
|
||||
name = gconv.String(value)
|
||||
)
|
||||
n, err := g.Table("user").Where("id != ? and name = ?", id, name).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"`
|
||||
@ -140,6 +125,22 @@ func ExampleRegisterRule() {
|
||||
Name: "john",
|
||||
Pass: "123456",
|
||||
}
|
||||
|
||||
rule := "unique-name"
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
var (
|
||||
id = data.(*User).Id
|
||||
name = gconv.String(value)
|
||||
)
|
||||
n, err := g.Model("user").Where("id != ? and name = ?", id, name).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err := gvalid.CheckStruct(context.TODO(), user, nil)
|
||||
fmt.Println(err.Error())
|
||||
// May Output:
|
||||
@ -148,7 +149,7 @@ func ExampleRegisterRule() {
|
||||
|
||||
func ExampleRegisterRule_OverwriteRequired() {
|
||||
rule := "required"
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
reflectValue := reflect.ValueOf(value)
|
||||
if reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
|
||||
@ -20,16 +20,20 @@ import (
|
||||
|
||||
func Test_CustomRule1(t *testing.T) {
|
||||
rule := "custom"
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
}
|
||||
if params["data"] != pass {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err := gvalid.RegisterRule(
|
||||
rule,
|
||||
func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
}
|
||||
m := gconv.Map(data)
|
||||
if m["data"] != pass {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gvalid.CheckValue(context.TODO(), "123456", rule, "custom message")
|
||||
@ -67,7 +71,7 @@ func Test_CustomRule1(t *testing.T) {
|
||||
|
||||
func Test_CustomRule2(t *testing.T) {
|
||||
rule := "required-map"
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
m := gconv.Map(value)
|
||||
if len(m) == 0 {
|
||||
return errors.New(message)
|
||||
@ -111,7 +115,7 @@ func Test_CustomRule2(t *testing.T) {
|
||||
|
||||
func Test_CustomRule_AllowEmpty(t *testing.T) {
|
||||
rule := "allow-empty-str"
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
s := gconv.String(value)
|
||||
if len(s) == 0 || s == "gf" {
|
||||
return nil
|
||||
@ -153,3 +157,118 @@ func Test_CustomRule_AllowEmpty(t *testing.T) {
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidator_RuleFunc(t *testing.T) {
|
||||
ruleName := "custom_1"
|
||||
ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
}
|
||||
if m := gconv.Map(data); m["data"] != pass {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := g.Validator().Rules(ruleName).Messages("custom message").RuleFunc(ruleName, ruleFunc).CheckValue("123456")
|
||||
t.Assert(err.String(), "custom message")
|
||||
err = g.Validator().
|
||||
Rules(ruleName).
|
||||
Messages("custom message").
|
||||
Data(g.Map{"data": "123456"}).
|
||||
RuleFunc(ruleName, ruleFunc).
|
||||
CheckValue("123456")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
// Error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value string `v:"uid@custom_1#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: "123",
|
||||
Data: "123456",
|
||||
}
|
||||
err := g.Validator().RuleFunc(ruleName, ruleFunc).CheckStruct(st)
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
// No error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value string `v:"uid@custom_1#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: "123456",
|
||||
Data: "123456",
|
||||
}
|
||||
err := g.Validator().RuleFunc(ruleName, ruleFunc).CheckStruct(st)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidator_RuleFuncMap(t *testing.T) {
|
||||
ruleName := "custom_1"
|
||||
ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
}
|
||||
if m := gconv.Map(data); m["data"] != pass {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := g.Validator().
|
||||
Rules(ruleName).
|
||||
Messages("custom message").
|
||||
RuleFuncMap(map[string]gvalid.RuleFunc{
|
||||
ruleName: ruleFunc,
|
||||
}).CheckValue("123456")
|
||||
t.Assert(err.String(), "custom message")
|
||||
err = g.Validator().
|
||||
Rules(ruleName).
|
||||
Messages("custom message").
|
||||
Data(g.Map{"data": "123456"}).
|
||||
RuleFuncMap(map[string]gvalid.RuleFunc{
|
||||
ruleName: ruleFunc,
|
||||
}).
|
||||
CheckValue("123456")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
// Error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value string `v:"uid@custom_1#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: "123",
|
||||
Data: "123456",
|
||||
}
|
||||
err := g.Validator().
|
||||
RuleFuncMap(map[string]gvalid.RuleFunc{
|
||||
ruleName: ruleFunc,
|
||||
}).CheckStruct(st)
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
// No error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value string `v:"uid@custom_1#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: "123456",
|
||||
Data: "123456",
|
||||
}
|
||||
err := g.Validator().
|
||||
RuleFuncMap(map[string]gvalid.RuleFunc{
|
||||
ruleName: ruleFunc,
|
||||
}).CheckStruct(st)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user