improve package gvalid

This commit is contained in:
John Guo
2021-08-01 23:50:44 +08:00
parent ab5f809074
commit f02372cf58
7 changed files with 242 additions and 138 deletions

View File

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

View File

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

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -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(), "账号不能为空")
})
}