diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 834d6a82c..24160e741 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -68,6 +68,12 @@ import ( // like: map[field] => string|map[rule]string type CustomMsg = map[string]interface{} +// fieldRule defined the alias name and rule string for specified field. +type fieldRule struct { + Name string // Alias name for the field. + Rule string // Rule string like: "max:6" +} + // apiNoValidation is an interface that marks current struct not validated by package `gvalid`. type apiNoValidation interface { NoValidation() @@ -228,7 +234,7 @@ var ( // nor error messages. markedRuleMap = map[string]bool{ bailRuleName: true, - "nullable": true, + //"nullable": true, } ) diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index fd113e989..4f4a93fef 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -31,14 +31,14 @@ type Error interface { // validationError is the validation error for validation result. type validationError struct { code int // Error code. - rules []string // Rules by sequence, which is used for keeping error sequence. + rules []fieldRule // Rules by sequence, which is used for keeping error sequence. errors map[string]map[string]string // Error map:map[field]map[rule]message firstKey string // The first error rule key(empty in default). firstItem map[string]string // The first error rule value(nil in default). } // newError creates and returns a validation error. -func newError(code int, rules []string, errors map[string]map[string]string) *validationError { +func newError(code int, rules []fieldRule, errors map[string]map[string]string) *validationError { for field, m := range errors { for k, v := range m { v = strings.Replace(v, ":attribute", field, -1) @@ -99,10 +99,9 @@ func (e *validationError) Items() (items []map[string]map[string]string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, _, _ := parseSequenceTag(v) - if errorItemMap, ok := e.errors[name]; ok { + if errorItemMap, ok := e.errors[v.Name]; ok { items = append(items, map[string]map[string]string{ - name: errorItemMap, + v.Name: errorItemMap, }) } } @@ -128,11 +127,10 @@ func (e *validationError) FirstItem() (key string, messages map[string]string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, _, _ := parseSequenceTag(v) - if errorItemMap, ok := e.errors[name]; ok { - e.firstKey = name + if errorItemMap, ok := e.errors[v.Name]; ok { + e.firstKey = v.Name e.firstItem = errorItemMap - return name, errorItemMap + return v.Name, errorItemMap } } } @@ -153,9 +151,8 @@ func (e *validationError) FirstRule() (rule string, err string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, ruleStr, _ := parseSequenceTag(v) - if errorItemMap, ok := e.errors[name]; ok { - for _, ruleItem := range strings.Split(ruleStr, "|") { + if errorItemMap, ok := e.errors[v.Name]; ok { + for _, ruleItem := range strings.Split(v.Rule, "|") { array := strings.Split(ruleItem, ":") ruleItem = strings.TrimSpace(array[0]) if err, ok = errorItemMap[ruleItem]; ok { @@ -218,10 +215,9 @@ func (e *validationError) Strings() (errs []string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, ruleStr, _ := parseSequenceTag(v) - if errorItemMap, ok := e.errors[name]; ok { + if errorItemMap, ok := e.errors[v.Name]; ok { // validation error checks. - for _, ruleItem := range strings.Split(ruleStr, "|") { + for _, ruleItem := range strings.Split(v.Rule, "|") { ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0]) if err, ok := errorItemMap[ruleItem]; ok { errs = append(errs, err) diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index e46c70295..33ea9dfd0 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -22,6 +22,7 @@ type Validator struct { 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`. + bail bool // Stop validation after the first validation error. } // New creates and returns a new Validator. @@ -54,6 +55,13 @@ func (v *Validator) Ctx(ctx context.Context) *Validator { return newValidator } +// Bail sets the mark for stopping validation after the first validation error. +func (v *Validator) Bail() *Validator { + newValidator := v.Clone() + newValidator.bail = true + return newValidator +} + // Data is a chaining operation function, which sets validation data for current operation. // The parameter `data` usually be type of map, which specifies the parameter map used in validation. // Calling this function also sets `useDataInsteadOfObjectAttributes` true no mather the `data` is nil or not. diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index d48b413e3..ccbc66bc9 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -24,16 +24,15 @@ func (v *Validator) doCheckMap(params interface{}) Error { return nil } var ( - checkRules = make(map[string]string) - customMsgs = make(CustomMsg) - errorRules = make([]string, 0) - errorMaps = make(map[string]map[string]string) + checkRules = make([]fieldRule, 0) + customMessage = make(CustomMsg) // map[RuleKey]ErrorMsg. + errorMaps = make(map[string]map[string]string) ) - switch v := v.rules.(type) { + switch assertValue := v.rules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. case []string: - for _, tag := range v { + for _, tag := range assertValue { name, rule, msg := parseSequenceTag(tag) if len(name) == 0 { continue @@ -43,7 +42,7 @@ func (v *Validator) doCheckMap(params interface{}) Error { msgArray = strings.Split(msg, "|") ruleArray = strings.Split(rule, "|") ) - for k, v := range ruleArray { + for k, ruleItem := range ruleArray { // If length of custom messages is lesser than length of rules, // the rest rules use the default error messages. if len(msgArray) <= k { @@ -52,20 +51,27 @@ func (v *Validator) doCheckMap(params interface{}) Error { if len(msgArray[k]) == 0 { continue } - array := strings.Split(v, ":") - if _, ok := customMsgs[name]; !ok { - customMsgs[name] = make(map[string]string) + array := strings.Split(ruleItem, ":") + if _, ok := customMessage[name]; !ok { + customMessage[name] = make(map[string]string) } - customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) + customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) } } - checkRules[name] = rule - errorRules = append(errorRules, name+"@"+rule) + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) } // No sequence rules: map[field]rule case map[string]string: - checkRules = v + for name, rule := range assertValue { + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } } // If there's no validation rules, it does nothing and returns quickly. if len(checkRules) == 0 { @@ -79,26 +85,35 @@ func (v *Validator) doCheckMap(params interface{}) Error { ) } if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { - if len(customMsgs) > 0 { + if len(customMessage) > 0 { for k, v := range msg { - customMsgs[k] = v + customMessage[k] = v } } else { - customMsgs = msg + customMessage = msg } } - var value interface{} - for key, rule := range checkRules { - if len(rule) == 0 { + var ( + value interface{} + ) + for _, checkRuleItem := range checkRules { + if len(checkRuleItem.Rule) == 0 { continue } value = nil - if v, ok := data[key]; ok { - value = v + if valueItem, ok := data[checkRuleItem.Name]; ok { + value = valueItem } // It checks each rule and its value in loop. - if e := v.doCheckValue(key, value, rule, customMsgs[key], params, data); e != nil { - _, item := e.FirstItem() + if validatedError := v.doCheckValue(doCheckValueInput{ + Name: checkRuleItem.Name, + Value: value, + Rule: checkRuleItem.Rule, + Messages: customMessage[checkRuleItem.Name], + DataRaw: params, + DataMap: data, + }); validatedError != nil { + _, errorItem := 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. @@ -106,14 +121,14 @@ func (v *Validator) doCheckMap(params interface{}) Error { if gconv.String(value) == "" { required := false // rule => error - for k := range item { + for ruleKey := range errorItem { // Default required rules. - if _, ok := mustCheckRulesEvenValueEmpty[k]; ok { + if _, ok := mustCheckRulesEvenValueEmpty[ruleKey]; ok { required = true break } // Custom rules are also required in default. - if f := v.getRuleFunc(k); f != nil { + if f := v.getRuleFunc(ruleKey); f != nil { required = true break } @@ -122,16 +137,19 @@ func (v *Validator) doCheckMap(params interface{}) Error { continue } } - if _, ok := errorMaps[key]; !ok { - errorMaps[key] = make(map[string]string) + if _, ok := errorMaps[checkRuleItem.Name]; !ok { + errorMaps[checkRuleItem.Name] = make(map[string]string) } - for k, v := range item { - errorMaps[key][k] = v + for ruleKey, errorItemMsgMap := range errorItem { + errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap + } + if v.bail { + break } } } if len(errorMaps) > 0 { - return newError(gerror.CodeValidationFailed, errorRules, errorMaps) + return newError(gerror.CodeValidationFailed, checkRules, errorMaps) } return nil } diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 50822e89e..6e5e1a568 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -62,20 +62,20 @@ func (v *Validator) doCheckStruct(object interface{}) Error { } var ( - 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. + inputParamMap map[string]interface{} + checkRules = make([]fieldRule, 0) + nameToRuleMap = make(map[string]string) // just for internally searching index purpose. + customMessage = make(CustomMsg) // Custom rule error message map. + checkValueData = v.data // Ready to be validated data, which can be type of . ) if checkValueData == nil { checkValueData = object } - switch v := v.rules.(type) { + switch assertValue := v.rules.(type) { // Sequence tag: []sequence tag // Sequence has order for error results. case []string: - for _, tag := range v { + for _, tag := range assertValue { name, rule, msg := parseSequenceTag(tag) if len(name) == 0 { continue @@ -101,17 +101,26 @@ func (v *Validator) doCheckStruct(object interface{}) Error { customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) } } - checkRuleStrMap[name] = rule - errorRules = append(errorRules, name+"@"+rule) + nameToRuleMap[name] = rule + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) } // Map type rules does not support sequence. // Format: map[key]rule case map[string]string: - checkRuleStrMap = v + nameToRuleMap = assertValue + for name, rule := range assertValue { + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } } // If there's no struct tag and validation rules, it does nothing and returns quickly. - if len(tagField) == 0 && len(checkRuleStrMap) == 0 { + if len(tagField) == 0 && len(checkRules) == 0 { return nil } // Input parameter map handling. @@ -129,6 +138,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { } } } + // Merge the custom validation rules with rules in struct tag. // The custom rules has the most high priority that can overwrite the struct tag rules. for _, field := range tagField { @@ -161,20 +171,32 @@ func (v *Validator) doCheckStruct(object interface{}) Error { } } } - if _, ok := checkRuleStrMap[name]; !ok { - if _, ok := checkRuleStrMap[fieldName]; ok { + + if _, ok := nameToRuleMap[name]; !ok { + if _, ok := nameToRuleMap[fieldName]; ok { // If there's alias name, // use alias name as its key and remove the field name key. - checkRuleStrMap[name] = checkRuleStrMap[fieldName] - delete(checkRuleStrMap, fieldName) + nameToRuleMap[name] = nameToRuleMap[fieldName] + delete(nameToRuleMap, fieldName) + for index, checkRuleItem := range checkRules { + if fieldName == checkRuleItem.Name { + checkRuleItem.Name = name + checkRules[index] = checkRuleItem + break + } + } } else { - checkRuleStrMap[name] = rule + nameToRuleMap[name] = rule + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) } - errorRules = append(errorRules, name+"@"+rule) } else { // The input rules can overwrite the rules in struct tag. continue } + if len(msg) > 0 { var ( msgArray = strings.Split(msg, "|") @@ -213,14 +235,20 @@ func (v *Validator) doCheckStruct(object interface{}) Error { // The following logic is the same as some of CheckMap but with sequence support. var ( - value interface{} - hasBailRule bool + value interface{} ) - for key, rule := range checkRuleStrMap { - _, value = gutil.MapPossibleItemByKey(inputParamMap, key) + for _, checkRuleItem := range checkRules { + _, value = gutil.MapPossibleItemByKey(inputParamMap, checkRuleItem.Name) // It checks each rule and its value in loop. - if validatedError := v.doCheckValue(key, value, rule, customMessage[key], checkValueData, inputParamMap); validatedError != nil { - _, item := validatedError.FirstItem() + if validatedError := v.doCheckValue(doCheckValueInput{ + Name: checkRuleItem.Name, + Value: value, + Rule: checkRuleItem.Rule, + Messages: customMessage[checkRuleItem.Name], + DataRaw: checkValueData, + DataMap: inputParamMap, + }); validatedError != nil { + _, errorItem := 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. @@ -228,14 +256,14 @@ func (v *Validator) doCheckStruct(object interface{}) Error { if value == nil || gconv.String(value) == "" { required := false // rule => error - for k := range item { + for ruleKey := range errorItem { // Default required rules. - if _, ok := mustCheckRulesEvenValueEmpty[k]; ok { + if _, ok := mustCheckRulesEvenValueEmpty[ruleKey]; ok { required = true break } // Custom rules are also required in default. - if f := v.getRuleFunc(k); f != nil { + if f := v.getRuleFunc(ruleKey); f != nil { required = true break } @@ -244,19 +272,19 @@ func (v *Validator) doCheckStruct(object interface{}) Error { continue } } - if _, ok := errorMaps[key]; !ok { - errorMaps[key] = make(map[string]string) + if _, ok := errorMaps[checkRuleItem.Name]; !ok { + errorMaps[checkRuleItem.Name] = make(map[string]string) } - for k, v := range item { - errorMaps[key][k] = v + for ruleKey, errorItemMsgMap := range errorItem { + errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap } - if hasBailRule { + if v.bail { break } } } if len(errorMaps) > 0 { - return newError(gerror.CodeValidationFailed, errorRules, errorMaps) + return newError(gerror.CodeValidationFailed, checkRules, errorMaps) } return nil } diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go index ff5c507f0..6d8d99684 100644 --- a/util/gvalid/gvalid_validator_check_value.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -29,27 +29,29 @@ 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, gconv.Map(v.data)) + return v.doCheckValue(doCheckValueInput{ + Name: "", + Value: value, + Rule: gconv.String(v.rules), + Messages: v.messages, + DataRaw: v.data, + DataMap: gconv.Map(v.data), + }) +} + +type doCheckValueInput struct { + Name string // Name specifies the name of parameter `value`. + Value interface{} // Value specifies the value for this rules to be validated. + Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc. + Messages interface{} // Messages specifies the custom error messages for this rule, which is usually type of map/slice. + DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value. + DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally } // doCheckSingleValue does the really rules validation for single key-value. -// -// 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( - name string, - value interface{}, - rules string, - messages interface{}, - dataRaw interface{}, - dataMap map[string]interface{}, -) Error { +func (v *Validator) doCheckValue(input doCheckValueInput) Error { // If there's no validation rules, it does nothing and returns quickly. - if rules == "" { + if input.Rule == "" { return nil } // It converts value to string and then does the validation. @@ -62,17 +64,17 @@ func (v *Validator) doCheckValue( msgArray = make([]string, 0) customMsgMap = make(map[string]string) ) - switch v := messages.(type) { + switch v := input.Messages.(type) { case string: msgArray = strings.Split(v, "|") default: - for k, v := range gconv.Map(messages) { + for k, v := range gconv.Map(input.Messages) { customMsgMap[k] = gconv.String(v) } } // Handle the char '|' in the rule, // which makes this rule separated into multiple rules. - ruleItems := strings.Split(strings.TrimSpace(rules), "|") + ruleItems := strings.Split(strings.TrimSpace(input.Rule), "|") for i := 0; ; { array := strings.Split(ruleItems[i], ":") _, ok := allSupportedRules[array[0]] @@ -83,7 +85,7 @@ func (v *Validator) doCheckValue( } else { return newErrorStr( internalRulesErrRuleName, - internalRulesErrRuleName+": "+rules, + internalRulesErrRuleName+": "+input.Rule, ) } } else { @@ -128,7 +130,7 @@ func (v *Validator) doCheckValue( if customRuleFunc != nil { // It checks custom validation rules with most priority. message := v.getErrorMessageByRule(ruleKey, customMsgMap) - if err := customRuleFunc(v.ctx, ruleItems[index], value, message, dataRaw); err != nil { + if err := customRuleFunc(v.ctx, ruleItems[index], input.Value, message, input.DataRaw); err != nil { match = false errorMsgArray[ruleKey] = err.Error() } else { @@ -136,7 +138,15 @@ func (v *Validator) doCheckValue( } } else { // It checks build-in validation rules if there's no custom rule. - match, err = v.doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, dataMap, customMsgMap) + match, err = v.doCheckBuildInRules(doCheckBuildInRulesInput{ + Index: index, + Value: input.Value, + RuleKey: ruleKey, + RulePattern: rulePattern, + RuleItems: ruleItems, + DataMap: input.DataMap, + CustomMsgMap: customMsgMap, + }) if !match && err != nil { errorMsgArray[ruleKey] = err.Error() } @@ -158,24 +168,26 @@ func (v *Validator) doCheckValue( index++ } if len(errorMsgArray) > 0 { - return newError(gerror.CodeValidationFailed, []string{rules}, map[string]map[string]string{ - name: errorMsgArray, + return newError(gerror.CodeValidationFailed, []fieldRule{{Name: input.Name, Rule: input.Rule}}, map[string]map[string]string{ + input.Name: errorMsgArray, }) } return nil } -func (v *Validator) doCheckBuildInRules( - index int, - value interface{}, - ruleKey string, - rulePattern string, - ruleItems []string, - dataMap map[string]interface{}, - customMsgMap map[string]string, -) (match bool, err error) { - valueStr := gconv.String(value) - switch ruleKey { +type doCheckBuildInRulesInput struct { + Index int + Value interface{} + RuleKey string + RulePattern string + RuleItems []string + DataMap map[string]interface{} + CustomMsgMap map[string]string +} + +func (v *Validator) doCheckBuildInRules(input doCheckBuildInRulesInput) (match bool, err error) { + valueStr := gconv.String(input.Value) + switch input.RuleKey { // Required rules. case "required", @@ -185,7 +197,7 @@ func (v *Validator) doCheckBuildInRules( "required-with-all", "required-without", "required-without-all": - match = v.checkRequired(value, ruleKey, rulePattern, dataMap) + match = v.checkRequired(input.Value, input.RuleKey, input.RulePattern, input.DataMap) // Length rules. // It also supports length of unicode string. @@ -194,7 +206,7 @@ func (v *Validator) doCheckBuildInRules( "min-length", "max-length", "size": - if msg := v.checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { + if msg := v.checkLength(valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { return match, gerror.NewOption(gerror.Option{ Text: msg, Code: gerror.CodeValidationFailed, @@ -208,7 +220,7 @@ func (v *Validator) doCheckBuildInRules( "min", "max", "between": - if msg := v.checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { + if msg := v.checkRange(valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { return match, gerror.NewOption(gerror.Option{ Text: msg, Code: gerror.CodeValidationFailed, @@ -220,18 +232,18 @@ func (v *Validator) doCheckBuildInRules( // Custom regular expression. case "regex": // It here should check the rule as there might be special char '|' in it. - for i := index + 1; i < len(ruleItems); i++ { - if !gregex.IsMatchString(singleRulePattern, ruleItems[i]) { - rulePattern += "|" + ruleItems[i] - index++ + for i := input.Index + 1; i < len(input.RuleItems); i++ { + if !gregex.IsMatchString(singleRulePattern, input.RuleItems[i]) { + input.RulePattern += "|" + input.RuleItems[i] + input.Index++ } } - match = gregex.IsMatchString(rulePattern, valueStr) + match = gregex.IsMatchString(input.RulePattern, valueStr) // Date rules. case "date": // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. - if v, ok := value.(apiTime); ok { + if v, ok := input.Value.(apiTime); ok { return !v.IsZero(), nil } match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr) @@ -239,15 +251,15 @@ func (v *Validator) doCheckBuildInRules( // Date rule with specified format. case "date-format": // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. - if v, ok := value.(apiTime); ok { + if v, ok := input.Value.(apiTime); ok { return !v.IsZero(), nil } - if _, err := gtime.StrToTimeFormat(valueStr, rulePattern); err == nil { + if _, err := gtime.StrToTimeFormat(valueStr, input.RulePattern); err == nil { match = true } else { var msg string - msg = v.getErrorMessageByRule(ruleKey, customMsgMap) - msg = strings.Replace(msg, ":format", rulePattern, -1) + msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) + msg = strings.Replace(msg, ":format", input.RulePattern, -1) return match, gerror.NewOption(gerror.Option{ Text: msg, Code: gerror.CodeValidationFailed, @@ -256,7 +268,7 @@ func (v *Validator) doCheckBuildInRules( // Values of two fields should be equal as string. case "same": - _, foundValue := gutil.MapPossibleItemByKey(dataMap, rulePattern) + _, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern) if foundValue != nil { if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { match = true @@ -264,8 +276,8 @@ func (v *Validator) doCheckBuildInRules( } if !match { var msg string - msg = v.getErrorMessageByRule(ruleKey, customMsgMap) - msg = strings.Replace(msg, ":field", rulePattern, -1) + msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) + msg = strings.Replace(msg, ":field", input.RulePattern, -1) return match, gerror.NewOption(gerror.Option{ Text: msg, Code: gerror.CodeValidationFailed, @@ -275,7 +287,7 @@ func (v *Validator) doCheckBuildInRules( // Values of two fields should not be equal as string. case "different": match = true - _, foundValue := gutil.MapPossibleItemByKey(dataMap, rulePattern) + _, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern) if foundValue != nil { if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { match = false @@ -283,8 +295,8 @@ func (v *Validator) doCheckBuildInRules( } if !match { var msg string - msg = v.getErrorMessageByRule(ruleKey, customMsgMap) - msg = strings.Replace(msg, ":field", rulePattern, -1) + msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) + msg = strings.Replace(msg, ":field", input.RulePattern, -1) return match, gerror.NewOption(gerror.Option{ Text: msg, Code: gerror.CodeValidationFailed, @@ -293,7 +305,7 @@ func (v *Validator) doCheckBuildInRules( // Field value should be in range of. case "in": - array := strings.Split(rulePattern, ",") + array := strings.Split(input.RulePattern, ",") for _, v := range array { if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 { match = true @@ -304,7 +316,7 @@ func (v *Validator) doCheckBuildInRules( // Field value should not be in range of. case "not-in": match = true - array := strings.Split(rulePattern, ",") + array := strings.Split(input.RulePattern, ",") for _, v := range array { if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 { match = false @@ -472,7 +484,7 @@ func (v *Validator) doCheckBuildInRules( default: return match, gerror.NewOption(gerror.Option{ - Text: "Invalid rule name: " + ruleKey, + Text: "Invalid rule name: " + input.RuleKey, Code: gerror.CodeInvalidParameter, }) } diff --git a/util/gvalid/gvalid_z_unit_checkmap_test.go b/util/gvalid/gvalid_z_unit_checkmap_test.go index dbb693e24..7750e923b 100755 --- a/util/gvalid/gvalid_z_unit_checkmap_test.go +++ b/util/gvalid/gvalid_z_unit_checkmap_test.go @@ -9,6 +9,7 @@ package gvalid_test import ( "context" "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/frame/g" "testing" "github.com/gogf/gf/test/gtest" @@ -210,3 +211,38 @@ func Test_Sequence(t *testing.T) { t.Assert(gerror.Current(err), "账号不能为空") }) } + +func Test_Map_Bail(t *testing.T) { + // global bail + gtest.C(t, func(t *gtest.T) { + params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := []string{ + "passport@required|length:6,16#账号不能为空|账号长度应当在:min到:max之间", + "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", + "password2@required|length:6,16#", + } + err := g.Validator().Bail().Rules(rules).CheckMap(params) + t.AssertNE(err, nil) + t.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间") + }) + // global bail with rule bail + gtest.C(t, func(t *gtest.T) { + params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := []string{ + "passport@bail|required|length:6,16#|账号不能为空|账号长度应当在:min到:max之间", + "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", + "password2@required|length:6,16#", + } + err := g.Validator().Bail().Rules(rules).CheckMap(params) + t.AssertNE(err, nil) + t.Assert(err.String(), "账号不能为空") + }) +}