mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
improve package gvalid
This commit is contained in:
@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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(), "账号不能为空")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user