mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
add bail rule for package gvalid
This commit is contained in:
@ -15,7 +15,9 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
// Refer to Laravel validation: https://laravel.com/docs/5.5/validation#available-validation-rules
|
||||
// Refer to Laravel validation:
|
||||
// https://laravel.com/docs/5.5/validation#available-validation-rules
|
||||
// https://learnku.com/docs/laravel/5.4/validation
|
||||
//
|
||||
// All supported rules:
|
||||
// required format: required brief: Required.
|
||||
@ -25,6 +27,7 @@ import (
|
||||
// required-with-all format: required-with-all:field1,field2,... brief: Required if all of given fields are not empty.
|
||||
// required-without format: required-without:field1,field2,... brief: Required if any of given fields are empty.
|
||||
// required-without-all format: required-without-all:field1,field2,...brief: Required if all of given fields are empty.
|
||||
// bail format: bail brief: Stop validating when this field's validation failed.
|
||||
// date format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02
|
||||
// date-format format: date-format:format brief: Custom date format.
|
||||
// email format: email brief: Email address.
|
||||
@ -79,6 +82,7 @@ const (
|
||||
internalDefaultRuleName = "__default__" // default rule name for i18n error message format if no i18n message found for specified error rule.
|
||||
ruleMessagePrefixForI18n = "gf.gvalid.rule." // prefix string for each rule configuration in i18n content.
|
||||
noValidationTagName = "nv" // no validation tag name for struct attribute.
|
||||
bailRuleName = "bail" // the name for rule "bail"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -121,6 +125,7 @@ var (
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
"bail": {},
|
||||
"date": {},
|
||||
"date-format": {},
|
||||
"email": {},
|
||||
@ -219,6 +224,12 @@ var (
|
||||
"regex": "The :attribute value is invalid",
|
||||
internalDefaultRuleName: "The :attribute value is invalid",
|
||||
}
|
||||
// markedRuleMap defines all rules that are just marked rules which have neither functional meaning
|
||||
// nor error messages.
|
||||
markedRuleMap = map[string]bool{
|
||||
bailRuleName: true,
|
||||
"nullable": true,
|
||||
}
|
||||
)
|
||||
|
||||
// CheckValue checks single value with specified rules.
|
||||
|
||||
@ -62,11 +62,11 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
}
|
||||
|
||||
var (
|
||||
inputParamMap map[string]interface{}
|
||||
checkRules = make(map[string]string)
|
||||
customMessage = make(CustomMsg)
|
||||
checkValueData = v.data
|
||||
errorRules = make([]string, 0) // Sequence rules.
|
||||
inputParamMap map[string]interface{}
|
||||
checkRuleStrMap = make(map[string]string) // Complete rules map of struct: map[name]rule, the rule is complete pattern like: Name@RuleStr#Message
|
||||
customMessage = make(CustomMsg) // Custom rule error message map.
|
||||
checkValueData = v.data // Ready to be validated data, which can be type of .
|
||||
errorRules = make([]string, 0) // Sequence rules.
|
||||
)
|
||||
if checkValueData == nil {
|
||||
checkValueData = object
|
||||
@ -101,17 +101,17 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k])
|
||||
}
|
||||
}
|
||||
checkRules[name] = rule
|
||||
checkRuleStrMap[name] = rule
|
||||
errorRules = append(errorRules, name+"@"+rule)
|
||||
}
|
||||
|
||||
// Map type rules does not support sequence.
|
||||
// Format: map[key]rule
|
||||
case map[string]string:
|
||||
checkRules = v
|
||||
checkRuleStrMap = v
|
||||
}
|
||||
// If there's no struct tag and validation rules, it does nothing and returns quickly.
|
||||
if len(tagField) == 0 && len(checkRules) == 0 {
|
||||
if len(tagField) == 0 && len(checkRuleStrMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Input parameter map handling.
|
||||
@ -161,14 +161,14 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := checkRules[name]; !ok {
|
||||
if _, ok := checkRules[fieldName]; ok {
|
||||
if _, ok := checkRuleStrMap[name]; !ok {
|
||||
if _, ok := checkRuleStrMap[fieldName]; ok {
|
||||
// If there's alias name,
|
||||
// use alias name as its key and remove the field name key.
|
||||
checkRules[name] = checkRules[fieldName]
|
||||
delete(checkRules, fieldName)
|
||||
checkRuleStrMap[name] = checkRuleStrMap[fieldName]
|
||||
delete(checkRuleStrMap, fieldName)
|
||||
} else {
|
||||
checkRules[name] = rule
|
||||
checkRuleStrMap[name] = rule
|
||||
}
|
||||
errorRules = append(errorRules, name+"@"+rule)
|
||||
} else {
|
||||
@ -211,13 +211,16 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
}
|
||||
}
|
||||
|
||||
// The following logic is the same as some of CheckMap.
|
||||
var value interface{}
|
||||
for key, rule := range checkRules {
|
||||
// The following logic is the same as some of CheckMap but with sequence support.
|
||||
var (
|
||||
value interface{}
|
||||
hasBailRule bool
|
||||
)
|
||||
for key, rule := range checkRuleStrMap {
|
||||
_, value = gutil.MapPossibleItemByKey(inputParamMap, key)
|
||||
// It checks each rule and its value in loop.
|
||||
if e := v.doCheckValue(key, value, rule, customMessage[key], checkValueData, inputParamMap); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
if validatedError := v.doCheckValue(key, value, rule, customMessage[key], checkValueData, inputParamMap); validatedError != nil {
|
||||
_, item := validatedError.FirstItem()
|
||||
// ===================================================================
|
||||
// Only in map and struct validations, if value is nil or empty string
|
||||
// and has no required* rules, it clears the error message.
|
||||
@ -247,6 +250,9 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
for k, v := range item {
|
||||
errorMaps[key][k] = v
|
||||
}
|
||||
if hasBailRule {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errorMaps) > 0 {
|
||||
|
||||
@ -34,13 +34,14 @@ func (v *Validator) CheckValue(value interface{}) Error {
|
||||
|
||||
// doCheckSingleValue does the really rules validation for single key-value.
|
||||
//
|
||||
// The parameter `rules` specifies the validation rules string, like "required", "required|between:1,100", etc.
|
||||
// The parameter `name` specifies the name of parameter `value`.
|
||||
// The parameter `value` specifies the value for this rules to be validated.
|
||||
// The parameter `rules` specifies the validation rules string, like "required", "required|between:1,100", etc.
|
||||
// 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,
|
||||
name string,
|
||||
value interface{},
|
||||
rules string,
|
||||
messages interface{},
|
||||
@ -92,15 +93,29 @@ func (v *Validator) doCheckValue(
|
||||
break
|
||||
}
|
||||
}
|
||||
var (
|
||||
hasBailRule = false
|
||||
)
|
||||
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])
|
||||
match = false // whether this rule is matched(has no error)
|
||||
results = ruleRegex.FindStringSubmatch(ruleItems[index]) // split single rule.
|
||||
ruleKey = strings.TrimSpace(results[1]) // rule name like "max" in rule "max: 6"
|
||||
rulePattern = strings.TrimSpace(results[2]) // rule value if any like "6" in rule:"max:6"
|
||||
customRuleFunc RuleFunc
|
||||
)
|
||||
|
||||
if !hasBailRule && ruleKey == bailRuleName {
|
||||
hasBailRule = true
|
||||
}
|
||||
|
||||
// Ignore logic executing for marked rules.
|
||||
if markedRuleMap[ruleKey] {
|
||||
index++
|
||||
continue
|
||||
}
|
||||
|
||||
if len(msgArray) > index {
|
||||
customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index])
|
||||
}
|
||||
@ -134,12 +149,17 @@ func (v *Validator) doCheckValue(
|
||||
if _, ok := errorMsgArray[ruleKey]; !ok {
|
||||
errorMsgArray[ruleKey] = v.getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
}
|
||||
// If it is with error and there's bail rule,
|
||||
// it then does not continue validating for left rules.
|
||||
if hasBailRule {
|
||||
break
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgArray) > 0 {
|
||||
return newError(gerror.CodeValidationFailed, []string{rules}, map[string]map[string]string{
|
||||
key: errorMsgArray,
|
||||
name: errorMsgArray,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -1027,3 +1027,54 @@ func Test_Code(t *testing.T) {
|
||||
t.Assert(gerror.Code(err), gerror.CodeInternalError)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Bail(t *testing.T) {
|
||||
// check value with no bail
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := g.Validator().
|
||||
Rules("required|min:1|between:1,100").
|
||||
Messages("|min number is 1|size is between 1 and 100").
|
||||
CheckValue(-1)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Error(), "min number is 1; size is between 1 and 100")
|
||||
})
|
||||
|
||||
// check value with bail
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := g.Validator().
|
||||
Rules("bail|required|min:1|between:1,100").
|
||||
Messages("||min number is 1|size is between 1 and 100").
|
||||
CheckValue(-1)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Error(), "min number is 1")
|
||||
})
|
||||
|
||||
// struct with no bail
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1"`
|
||||
Size int `v:"required|min:1|between:1,100 # |min number is 1|size is between 1 and 100"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: -1,
|
||||
}
|
||||
err := g.Validator().CheckStruct(obj)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Error(), "min number is 1; size is between 1 and 100")
|
||||
})
|
||||
// struct with bail
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1"`
|
||||
Size int `v:"bail|required|min:1|between:1,100 # ||min number is 1|size is between 1 and 100"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: -1,
|
||||
}
|
||||
err := g.Validator().CheckStruct(obj)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Error(), "min number is 1")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user