mirror of
https://gitee.com/johng/gf.git
synced 2024-12-02 04:07:47 +08:00
add rule ci for package gvalid
This commit is contained in:
parent
f5920fff68
commit
eaef2d865e
@ -15,56 +15,6 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// required-if format: required-if:field,value,... brief: Required unless all given field and its value are equal.
|
||||
// required-unless format: required-unless:field,value,... brief: Required unless all given field and its value are not equal.
|
||||
// required-with format: required-with:field1,field2,... brief: Required if any of given fields are not empty.
|
||||
// required-with-all format: required-with-all:field1,field2,... brief: Required if all 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 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
|
||||
// datetime format: datetime brief: Standard datetime, like: 2006-01-02 12:00:00
|
||||
// date-format format: date-format:format brief: Custom date format.
|
||||
// email format: email brief: Email address.
|
||||
// phone format: phone brief: Phone number.
|
||||
// telephone format: telephone brief: Telephone number, like: "XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
||||
// passport format: passport brief: Universal passport format rule: Starting with letter, containing only numbers or underscores, length between 6 and 18
|
||||
// password format: password brief: Universal password format rule1: Containing any visible chars, length between 6 and 18.
|
||||
// password2 format: password2 brief: Universal password format rule2: Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
// password3 format: password3 brief: Universal password format rule3: Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
// postcode format: postcode brief: Postcode number.
|
||||
// resident-id format: resident-id brief: Resident id number.
|
||||
// bank-card format: bank-card brief: Bank card nunber.
|
||||
// qq format: qq brief: Tencent QQ number.
|
||||
// ip format: ip brief: IPv4/IPv6.
|
||||
// ipv4 format: ipv4 brief: IPv4.
|
||||
// ipv6 format: ipv6 brief: IPv6.
|
||||
// mac format: mac brief: MAC.
|
||||
// url format: url brief: URL.
|
||||
// domain format: domain brief: Domain.
|
||||
// length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// size format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// between format: between:min,max brief: Range between :min and :max. It supports both integer and float.
|
||||
// min format: min:min brief: Equal or greater than :min. It supports both integer and float.
|
||||
// max format: max:max brief: Equal or lesser than :max. It supports both integer and float.
|
||||
// json format: json brief: JSON.
|
||||
// integer format: integer brief: Integer.
|
||||
// float format: float brief: Float. Note that an integer is actually a float number.
|
||||
// boolean format: boolean brief: Boolean(1,true,on,yes:true | 0,false,off,no,"":false)
|
||||
// same format: same:field brief: Value should be the same as value of field.
|
||||
// different format: different:field brief: Value should be different from value of field.
|
||||
// in format: in:value1,value2,... brief: Value should be in: value1,value2,...
|
||||
// not-in format: not-in:value1,value2,... brief: Value should not be in: value1,value2,...
|
||||
// regex format: regex:pattern brief: Value should match custom regular expression pattern.
|
||||
|
||||
// CustomMsg is the custom error message type,
|
||||
// like: map[field] => string|map[rule]string
|
||||
type CustomMsg = map[string]interface{}
|
||||
@ -89,100 +39,64 @@ 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"
|
||||
ruleNameBail = "bail" // the name for rule "bail"
|
||||
ruleNameCi = "ci" // the name for rule "ci"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultValidator = New() // defaultValidator is the default validator for package functions.
|
||||
structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array.
|
||||
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
|
||||
|
||||
// all internal error keys.
|
||||
internalErrKeyMap = map[string]string{
|
||||
internalRulesErrRuleName: internalRulesErrRuleName,
|
||||
internalParamsErrRuleName: internalParamsErrRuleName,
|
||||
internalObjectErrRuleName: internalObjectErrRuleName,
|
||||
}
|
||||
// regular expression object for single rule
|
||||
// which is compiled just once and of repeatable usage.
|
||||
ruleRegex, _ = regexp.Compile(singleRulePattern)
|
||||
|
||||
// mustCheckRulesEvenValueEmpty specifies some rules that must be validated
|
||||
// even the value is empty (nil or empty).
|
||||
mustCheckRulesEvenValueEmpty = map[string]struct{}{
|
||||
"required": {},
|
||||
"required-if": {},
|
||||
"required-unless": {},
|
||||
"required-with": {},
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
//"same": {},
|
||||
//"different": {},
|
||||
//"in": {},
|
||||
//"not-in": {},
|
||||
//"regex": {},
|
||||
}
|
||||
// allSupportedRules defines all supported rules that is used for quick checks.
|
||||
// Refer to Laravel validation:
|
||||
// https://laravel.com/docs/5.5/validation#available-validation-rules
|
||||
// https://learnku.com/docs/laravel/5.4/validation
|
||||
allSupportedRules = map[string]struct{}{
|
||||
"required": {},
|
||||
"required-if": {},
|
||||
"required-unless": {},
|
||||
"required-with": {},
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
"bail": {},
|
||||
"date": {},
|
||||
"datetime": {},
|
||||
"date-format": {},
|
||||
"email": {},
|
||||
"phone": {},
|
||||
"phone-loose": {},
|
||||
"telephone": {},
|
||||
"passport": {},
|
||||
"password": {},
|
||||
"password2": {},
|
||||
"password3": {},
|
||||
"postcode": {},
|
||||
"resident-id": {},
|
||||
"bank-card": {},
|
||||
"qq": {},
|
||||
"ip": {},
|
||||
"ipv4": {},
|
||||
"ipv6": {},
|
||||
"mac": {},
|
||||
"url": {},
|
||||
"domain": {},
|
||||
"length": {},
|
||||
"min-length": {},
|
||||
"max-length": {},
|
||||
"size": {},
|
||||
"between": {},
|
||||
"min": {},
|
||||
"max": {},
|
||||
"json": {},
|
||||
"integer": {},
|
||||
"float": {},
|
||||
"boolean": {},
|
||||
"same": {},
|
||||
"different": {},
|
||||
"in": {},
|
||||
"not-in": {},
|
||||
"regex": {},
|
||||
}
|
||||
// boolMap defines the boolean values.
|
||||
boolMap = map[string]struct{}{
|
||||
"1": {},
|
||||
"true": {},
|
||||
"on": {},
|
||||
"yes": {},
|
||||
"": {},
|
||||
"0": {},
|
||||
"false": {},
|
||||
"off": {},
|
||||
"no": {},
|
||||
"required": {}, // format: required brief: Required.
|
||||
"required-if": {}, // format: required-if:field,value,... brief: Required unless all given field and its value are equal.
|
||||
"required-unless": {}, // format: required-unless:field,value,... brief: Required unless all given field and its value are not equal.
|
||||
"required-with": {}, // format: required-with:field1,field2,... brief: Required if any of given fields are not empty.
|
||||
"required-with-all": {}, // format: required-with-all:field1,field2,... brief: Required if all 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 given fields are empty.
|
||||
"bail": {}, // format: bail brief: Stop validating when this field's validation failed.
|
||||
"ci": {}, // format: ci brief: Case-Insensitive configuration for those rules that need value comparison like: same, different, in, not-in, etc.
|
||||
"date": {}, // format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02
|
||||
"datetime": {}, // format: datetime brief: Standard datetime, like: 2006-01-02 12:00:00
|
||||
"date-format": {}, // format: date-format:format brief: Custom date format.
|
||||
"email": {}, // format: email brief: Email address.
|
||||
"phone": {}, // format: phone brief: Phone number.
|
||||
"phone-loose": {}, // format: phone-loose brief: Loose phone number validation.
|
||||
"telephone": {}, // format: telephone brief: Telephone number, like: "XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
||||
"passport": {}, // format: passport brief: Universal passport format rule: Starting with letter, containing only numbers or underscores, length between 6 and 18
|
||||
"password": {}, // format: password brief: Universal password format rule1: Containing any visible chars, length between 6 and 18.
|
||||
"password2": {}, // format: password2 brief: Universal password format rule2: Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
"password3": {}, // format: password3 brief: Universal password format rule3: Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
"postcode": {}, // format: postcode brief: Postcode number.
|
||||
"resident-id": {}, // format: resident-id brief: Resident id number.
|
||||
"bank-card": {}, // format: bank-card brief: Bank card number.
|
||||
"qq": {}, // format: qq brief: Tencent QQ number.
|
||||
"ip": {}, // format: ip brief: IPv4/IPv6.
|
||||
"ipv4": {}, // format: ipv4 brief: IPv4.
|
||||
"ipv6": {}, // format: ipv6 brief: IPv6.
|
||||
"mac": {}, // format: mac brief: MAC.
|
||||
"url": {}, // format: url brief: URL.
|
||||
"domain": {}, // format: domain brief: Domain.
|
||||
"length": {}, // format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"min-length": {}, // format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"max-length": {}, // format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"size": {}, // format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"between": {}, // format: between:min,max brief: Range between :min and :max. It supports both integer and float.
|
||||
"min": {}, // format: min:min brief: Equal or greater than :min. It supports both integer and float.
|
||||
"max": {}, // format: max:max brief: Equal or lesser than :max. It supports both integer and float.
|
||||
"json": {}, // format: json brief: JSON.
|
||||
"integer": {}, // format: integer brief: Integer.
|
||||
"float": {}, // format: float brief: Float. Note that an integer is actually a float number.
|
||||
"boolean": {}, // format: boolean brief: Boolean(1,true,on,yes:true | 0,false,off,no,"":false)
|
||||
"same": {}, // format: same:field brief: Value should be the same as value of field.
|
||||
"different": {}, // format: different:field brief: Value should be different from value of field.
|
||||
"in": {}, // format: in:value1,value2,... brief: Value should be in: value1,value2,...
|
||||
"not-in": {}, // format: not-in:value1,value2,... brief: Value should not be in: value1,value2,...
|
||||
"regex": {}, // format: regex:pattern brief: Value should match custom regular expression pattern.
|
||||
}
|
||||
|
||||
// defaultMessages is the default error messages.
|
||||
// Note that these messages are synchronized from ./i18n/en/validation.toml .
|
||||
defaultMessages = map[string]string{
|
||||
@ -232,11 +146,56 @@ var (
|
||||
"regex": "The {attribute} value `{value}` must be in regex of: {pattern}",
|
||||
internalDefaultRuleName: "The {attribute} value `{value}` is invalid",
|
||||
}
|
||||
|
||||
// mustCheckRulesEvenValueEmpty specifies some rules that must be validated
|
||||
// even the value is empty (nil or empty).
|
||||
mustCheckRulesEvenValueEmpty = map[string]struct{}{
|
||||
"required": {},
|
||||
"required-if": {},
|
||||
"required-unless": {},
|
||||
"required-with": {},
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
//"same": {},
|
||||
//"different": {},
|
||||
//"in": {},
|
||||
//"not-in": {},
|
||||
//"regex": {},
|
||||
}
|
||||
|
||||
// boolMap defines the boolean values.
|
||||
boolMap = map[string]struct{}{
|
||||
"1": {},
|
||||
"true": {},
|
||||
"on": {},
|
||||
"yes": {},
|
||||
"": {},
|
||||
"0": {},
|
||||
"false": {},
|
||||
"off": {},
|
||||
"no": {},
|
||||
}
|
||||
|
||||
defaultValidator = New() // defaultValidator is the default validator for package functions.
|
||||
structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array.
|
||||
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
|
||||
|
||||
// all internal error keys.
|
||||
internalErrKeyMap = map[string]string{
|
||||
internalRulesErrRuleName: internalRulesErrRuleName,
|
||||
internalParamsErrRuleName: internalParamsErrRuleName,
|
||||
internalObjectErrRuleName: internalObjectErrRuleName,
|
||||
}
|
||||
// regular expression object for single rule
|
||||
// which is compiled just once and of repeatable usage.
|
||||
ruleRegex, _ = regexp.Compile(singleRulePattern)
|
||||
|
||||
// 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,
|
||||
ruleNameBail: true,
|
||||
ruleNameCi: true,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -21,6 +21,7 @@ type Validator struct {
|
||||
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.
|
||||
caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
|
||||
}
|
||||
|
||||
// New creates and returns a new Validator.
|
||||
@ -52,6 +53,13 @@ func (v *Validator) Bail() *Validator {
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// CaseInsensitive sets the mark for Case-Insensitive for those rules that need value comparison.
|
||||
func (v *Validator) CaseInsensitive() *Validator {
|
||||
newValidator := v.Clone()
|
||||
newValidator.caseInsensitive = true
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// Data is a chaining operation function, which sets validation data for current operation.
|
||||
// The parameter `data` is usually 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.
|
||||
|
@ -54,9 +54,9 @@ type doCheckValueInput struct {
|
||||
}
|
||||
|
||||
// doCheckSingleValue does the really rules validation for single key-value.
|
||||
func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) Error {
|
||||
func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Error {
|
||||
// If there's no validation rules, it does nothing and returns quickly.
|
||||
if input.Rule == "" {
|
||||
if in.Rule == "" {
|
||||
return nil
|
||||
}
|
||||
// It converts value to string and then does the validation.
|
||||
@ -69,17 +69,17 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
msgArray = make([]string, 0)
|
||||
customMsgMap = make(map[string]string)
|
||||
)
|
||||
switch messages := input.Messages.(type) {
|
||||
switch messages := in.Messages.(type) {
|
||||
case string:
|
||||
msgArray = strings.Split(messages, "|")
|
||||
default:
|
||||
for k, message := range gconv.Map(input.Messages) {
|
||||
for k, message := range gconv.Map(in.Messages) {
|
||||
customMsgMap[k] = gconv.String(message)
|
||||
}
|
||||
}
|
||||
// Handle the char '|' in the rule,
|
||||
// which makes this rule separated into multiple rules.
|
||||
ruleItems := strings.Split(strings.TrimSpace(input.Rule), "|")
|
||||
ruleItems := strings.Split(strings.TrimSpace(in.Rule), "|")
|
||||
for i := 0; ; {
|
||||
array := strings.Split(ruleItems[i], ":")
|
||||
_, ok := allSupportedRules[array[0]]
|
||||
@ -90,7 +90,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
} else {
|
||||
return newValidationErrorByStr(
|
||||
internalRulesErrRuleName,
|
||||
errors.New(internalRulesErrRuleName+": "+input.Rule),
|
||||
errors.New(internalRulesErrRuleName+": "+in.Rule),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -101,7 +101,8 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
}
|
||||
}
|
||||
var (
|
||||
hasBailRule = false
|
||||
hasBailRule = v.bail
|
||||
hasCaseInsensitive = v.caseInsensitive
|
||||
)
|
||||
for index := 0; index < len(ruleItems); {
|
||||
var (
|
||||
@ -113,10 +114,14 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
customRuleFunc RuleFunc
|
||||
)
|
||||
|
||||
if !hasBailRule && ruleKey == bailRuleName {
|
||||
if !hasBailRule && ruleKey == ruleNameBail {
|
||||
hasBailRule = true
|
||||
}
|
||||
|
||||
if !hasCaseInsensitive && ruleKey == ruleNameCi {
|
||||
hasCaseInsensitive = true
|
||||
}
|
||||
|
||||
// Ignore logic executing for marked rules.
|
||||
if markedRuleMap[ruleKey] {
|
||||
index++
|
||||
@ -138,8 +143,8 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if err = customRuleFunc(ctx, RuleFuncInput{
|
||||
Rule: ruleItems[index],
|
||||
Message: message,
|
||||
Value: gvar.New(input.Value),
|
||||
Data: gvar.New(input.DataRaw),
|
||||
Value: gvar.New(in.Value),
|
||||
Data: gvar.New(in.DataRaw),
|
||||
}); err != nil {
|
||||
match = false
|
||||
// The error should have stack info to indicate the error position.
|
||||
@ -161,13 +166,14 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
match, err = v.doCheckSingleBuildInRules(
|
||||
ctx,
|
||||
doCheckBuildInRulesInput{
|
||||
Index: index,
|
||||
Value: input.Value,
|
||||
RuleKey: ruleKey,
|
||||
RulePattern: rulePattern,
|
||||
RuleItems: ruleItems,
|
||||
DataMap: input.DataMap,
|
||||
CustomMsgMap: customMsgMap,
|
||||
Index: index,
|
||||
Value: in.Value,
|
||||
RuleKey: ruleKey,
|
||||
RulePattern: rulePattern,
|
||||
RuleItems: ruleItems,
|
||||
DataMap: in.DataMap,
|
||||
CustomMsgMap: customMsgMap,
|
||||
CaseInsensitive: hasCaseInsensitive,
|
||||
},
|
||||
)
|
||||
if !match && err != nil {
|
||||
@ -187,9 +193,9 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if err = ruleErrorMap[ruleKey]; !gerror.HasStack(err) {
|
||||
var s string
|
||||
s = gstr.ReplaceByMap(err.Error(), map[string]string{
|
||||
"{value}": gconv.String(input.Value),
|
||||
"{value}": gconv.String(in.Value),
|
||||
"{pattern}": rulePattern,
|
||||
"{attribute}": input.Name,
|
||||
"{attribute}": in.Name,
|
||||
})
|
||||
s, _ = gregex.ReplaceString(`\s{2,}`, ` `, s)
|
||||
ruleErrorMap[ruleKey] = errors.New(s)
|
||||
@ -206,9 +212,9 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if len(ruleErrorMap) > 0 {
|
||||
return newValidationError(
|
||||
gcode.CodeValidationFailed,
|
||||
[]fieldRule{{Name: input.Name, Rule: input.Rule}},
|
||||
[]fieldRule{{Name: in.Name, Rule: in.Rule}},
|
||||
map[string]map[string]error{
|
||||
input.Name: ruleErrorMap,
|
||||
in.Name: ruleErrorMap,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -216,18 +222,19 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
}
|
||||
|
||||
type doCheckBuildInRulesInput struct {
|
||||
Index int // Index of RuleKey in RuleItems.
|
||||
Value interface{} // Value to be validated.
|
||||
RuleKey string // RuleKey is like the "max" in rule "max: 6"
|
||||
RulePattern string // RulePattern is like "6" in rule:"max:6"
|
||||
RuleItems []string // RuleItems are all the rules that should be validated on single field, like: []string{"required", "min:1"}
|
||||
DataMap map[string]interface{} // Parameter map.
|
||||
CustomMsgMap map[string]string // Custom error message map.
|
||||
Index int // Index of RuleKey in RuleItems.
|
||||
Value interface{} // Value to be validated.
|
||||
RuleKey string // RuleKey is like the "max" in rule "max: 6"
|
||||
RulePattern string // RulePattern is like "6" in rule:"max:6"
|
||||
RuleItems []string // RuleItems are all the rules that should be validated on single field, like: []string{"required", "min:1"}
|
||||
DataMap map[string]interface{} // Parameter map.
|
||||
CustomMsgMap map[string]string // Custom error message map.
|
||||
CaseInsensitive bool // Case-Insensitive comparison.
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheckBuildInRulesInput) (match bool, err error) {
|
||||
valueStr := gconv.String(input.Value)
|
||||
switch input.RuleKey {
|
||||
func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, in doCheckBuildInRulesInput) (match bool, err error) {
|
||||
valueStr := gconv.String(in.Value)
|
||||
switch in.RuleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
@ -237,7 +244,13 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = v.checkRequired(input.Value, input.RuleKey, input.RulePattern, input.DataMap)
|
||||
match = v.checkRequired(checkRequiredInput{
|
||||
Value: in.Value,
|
||||
RuleKey: in.RuleKey,
|
||||
RulePattern: in.RulePattern,
|
||||
DataMap: in.DataMap,
|
||||
CaseInsensitive: in.CaseInsensitive,
|
||||
})
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
@ -246,7 +259,7 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
"min-length",
|
||||
"max-length",
|
||||
"size":
|
||||
if msg := v.checkLength(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" {
|
||||
if msg := v.checkLength(ctx, valueStr, in.RuleKey, in.RulePattern, in.CustomMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
@ -257,7 +270,7 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := v.checkRange(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" {
|
||||
if msg := v.checkRange(ctx, valueStr, in.RuleKey, in.RulePattern, in.CustomMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
@ -266,27 +279,27 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := input.Index + 1; i < len(input.RuleItems); i++ {
|
||||
if !gregex.IsMatchString(singleRulePattern, input.RuleItems[i]) {
|
||||
input.RulePattern += "|" + input.RuleItems[i]
|
||||
input.Index++
|
||||
for i := in.Index + 1; i < len(in.RuleItems); i++ {
|
||||
if !gregex.IsMatchString(singleRulePattern, in.RuleItems[i]) {
|
||||
in.RulePattern += "|" + in.RuleItems[i]
|
||||
in.Index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(input.RulePattern, valueStr)
|
||||
match = gregex.IsMatchString(in.RulePattern, valueStr)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time.
|
||||
if v, ok := input.Value.(iTime); ok {
|
||||
return !v.IsZero(), nil
|
||||
if value, ok := in.Value.(iTime); ok {
|
||||
return !value.IsZero(), nil
|
||||
}
|
||||
match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr)
|
||||
|
||||
// Datetime rule.
|
||||
case "datetime":
|
||||
// support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time.
|
||||
if v, ok := input.Value.(iTime); ok {
|
||||
return !v.IsZero(), nil
|
||||
if value, ok := in.Value.(iTime); ok {
|
||||
return !value.IsZero(), nil
|
||||
}
|
||||
if _, err = gtime.StrToTimeFormat(valueStr, `Y-m-d H:i:s`); err == nil {
|
||||
match = true
|
||||
@ -295,54 +308,61 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
// support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time.
|
||||
if v, ok := input.Value.(iTime); ok {
|
||||
return !v.IsZero(), nil
|
||||
if value, ok := in.Value.(iTime); ok {
|
||||
return !value.IsZero(), nil
|
||||
}
|
||||
if _, err = gtime.StrToTimeFormat(valueStr, input.RulePattern); err == nil {
|
||||
if _, err = gtime.StrToTimeFormat(valueStr, in.RulePattern); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var (
|
||||
msg string
|
||||
)
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = v.getErrorMessageByRule(ctx, in.RuleKey, in.CustomMsgMap)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
_, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern)
|
||||
_, foundValue := gutil.MapPossibleItemByKey(in.DataMap, in.RulePattern)
|
||||
if foundValue != nil {
|
||||
if strings.Compare(valueStr, gconv.String(foundValue)) == 0 {
|
||||
match = true
|
||||
if in.CaseInsensitive {
|
||||
match = strings.EqualFold(valueStr, gconv.String(foundValue))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, gconv.String(foundValue)) == 0
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = v.getErrorMessageByRule(ctx, in.RuleKey, in.CustomMsgMap)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
_, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern)
|
||||
_, foundValue := gutil.MapPossibleItemByKey(in.DataMap, in.RulePattern)
|
||||
if foundValue != nil {
|
||||
if strings.Compare(valueStr, gconv.String(foundValue)) == 0 {
|
||||
match = false
|
||||
if in.CaseInsensitive {
|
||||
match = !strings.EqualFold(valueStr, gconv.String(foundValue))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, gconv.String(foundValue)) != 0
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = v.getErrorMessageByRule(ctx, in.RuleKey, in.CustomMsgMap)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := gstr.SplitAndTrim(input.RulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
for _, value := range gstr.SplitAndTrim(in.RulePattern, ",") {
|
||||
if in.CaseInsensitive {
|
||||
match = strings.EqualFold(valueStr, strings.TrimSpace(value))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, strings.TrimSpace(value)) == 0
|
||||
}
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -350,10 +370,13 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := gstr.SplitAndTrim(input.RulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
for _, value := range gstr.SplitAndTrim(in.RulePattern, ",") {
|
||||
if in.CaseInsensitive {
|
||||
match = !strings.EqualFold(valueStr, strings.TrimSpace(value))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, strings.TrimSpace(value)) != 0
|
||||
}
|
||||
if !match {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -517,7 +540,7 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr)
|
||||
|
||||
default:
|
||||
return match, errors.New("Invalid rule name: " + input.RuleKey)
|
||||
return match, errors.New("Invalid rule name: " + in.RuleKey)
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
@ -15,11 +15,19 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
type checkRequiredInput struct {
|
||||
Value interface{} // Value to be validated.
|
||||
RuleKey string // RuleKey is like the "max" in rule "max: 6"
|
||||
RulePattern string // RulePattern is like "6" in rule:"max:6"
|
||||
DataMap map[string]interface{} // Parameter map.
|
||||
CaseInsensitive bool // Case-Insensitive comparison.
|
||||
}
|
||||
|
||||
// checkRequired checks `value` using required rules.
|
||||
// It also supports require checks for `value` of type: slice, map.
|
||||
func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, dataMap map[string]interface{}) bool {
|
||||
func (v *Validator) checkRequired(in checkRequiredInput) bool {
|
||||
required := false
|
||||
switch ruleKey {
|
||||
switch in.RuleKey {
|
||||
// Required.
|
||||
case "required":
|
||||
required = true
|
||||
@ -29,7 +37,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-if":
|
||||
required = false
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
// It supports multiple field and value pairs.
|
||||
@ -37,9 +45,13 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, tk)
|
||||
if strings.Compare(tv, gconv.String(foundValue)) == 0 {
|
||||
required = true
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, tk)
|
||||
if in.CaseInsensitive {
|
||||
required = strings.EqualFold(tv, gconv.String(foundValue))
|
||||
} else {
|
||||
required = strings.Compare(tv, gconv.String(foundValue)) == 0
|
||||
}
|
||||
if required {
|
||||
break
|
||||
}
|
||||
i += 2
|
||||
@ -51,7 +63,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-unless":
|
||||
required = true
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
// It supports multiple field and value pairs.
|
||||
@ -59,12 +71,15 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, tk)
|
||||
if strings.Compare(tv, gconv.String(foundValue)) == 0 {
|
||||
required = false
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, tk)
|
||||
if in.CaseInsensitive {
|
||||
required = !strings.EqualFold(tv, gconv.String(foundValue))
|
||||
} else {
|
||||
required = strings.Compare(tv, gconv.String(foundValue)) != 0
|
||||
}
|
||||
if !required {
|
||||
break
|
||||
}
|
||||
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
@ -74,11 +89,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-with":
|
||||
required = false
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if !empty.IsEmpty(foundValue) {
|
||||
required = true
|
||||
break
|
||||
@ -90,11 +105,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-with-all":
|
||||
required = true
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if empty.IsEmpty(foundValue) {
|
||||
required = false
|
||||
break
|
||||
@ -106,11 +121,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-without":
|
||||
required = false
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if empty.IsEmpty(foundValue) {
|
||||
required = true
|
||||
break
|
||||
@ -122,11 +137,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-without-all":
|
||||
required = true
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if !empty.IsEmpty(foundValue) {
|
||||
required = false
|
||||
break
|
||||
@ -134,7 +149,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
}
|
||||
}
|
||||
if required {
|
||||
reflectValue := reflect.ValueOf(value)
|
||||
reflectValue := reflect.ValueOf(in.Value)
|
||||
for reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
@ -142,7 +157,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
|
||||
return reflectValue.Len() != 0
|
||||
}
|
||||
return gconv.String(value) != ""
|
||||
return gconv.String(in.Value) != ""
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ func Test_Map_Bail(t *testing.T) {
|
||||
}
|
||||
err := g.Validator().Bail().Rules(rules).CheckMap(ctx, params)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间")
|
||||
t.Assert(err.String(), "账号不能为空")
|
||||
})
|
||||
// global bail with rule bail
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
30
util/gvalid/gvalid_z_unit_feature_ci_test.go
Normal file
30
util/gvalid/gvalid_z_unit_feature_ci_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gvalid_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CI(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gvalid.CheckValue(ctx, "id", "in:Id,Name", nil)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gvalid.CheckValue(ctx, "id", "ci|in:Id,Name", nil)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := g.Validator().CaseInsensitive().Rules("in:Id,Name").CheckValue(ctx, "id")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user