github.com/gogf/gf@v1.16.9/util/gvalid/gvalid_validator_check_value.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gvalid 8 9 import ( 10 "github.com/gogf/gf/errors/gcode" 11 "github.com/gogf/gf/errors/gerror" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/gogf/gf/internal/json" 17 "github.com/gogf/gf/net/gipv4" 18 "github.com/gogf/gf/net/gipv6" 19 "github.com/gogf/gf/os/gtime" 20 "github.com/gogf/gf/text/gregex" 21 "github.com/gogf/gf/util/gconv" 22 "github.com/gogf/gf/util/gutil" 23 ) 24 25 type apiTime interface { 26 Date() (year int, month time.Month, day int) 27 IsZero() bool 28 } 29 30 // CheckValue checks single value with specified rules. 31 // It returns nil if successful validation. 32 func (v *Validator) CheckValue(value interface{}) Error { 33 return v.doCheckValue(doCheckValueInput{ 34 Name: "", 35 Value: value, 36 Rule: gconv.String(v.rules), 37 Messages: v.messages, 38 DataRaw: v.data, 39 DataMap: gconv.Map(v.data), 40 }) 41 } 42 43 type doCheckValueInput struct { 44 Name string // Name specifies the name of parameter `value`. 45 Value interface{} // Value specifies the value for this rules to be validated. 46 Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc. 47 Messages interface{} // Messages specifies the custom error messages for this rule, which is usually type of map/slice. 48 DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value. 49 DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally 50 } 51 52 // doCheckSingleValue does the really rules validation for single key-value. 53 func (v *Validator) doCheckValue(input doCheckValueInput) Error { 54 // If there's no validation rules, it does nothing and returns quickly. 55 if input.Rule == "" { 56 return nil 57 } 58 // It converts value to string and then does the validation. 59 var ( 60 // Do not trim it as the space is also part of the value. 61 errorMsgArray = make(map[string]string) 62 ) 63 // Custom error messages handling. 64 var ( 65 msgArray = make([]string, 0) 66 customMsgMap = make(map[string]string) 67 ) 68 switch v := input.Messages.(type) { 69 case string: 70 msgArray = strings.Split(v, "|") 71 default: 72 for k, v := range gconv.Map(input.Messages) { 73 customMsgMap[k] = gconv.String(v) 74 } 75 } 76 // Handle the char '|' in the rule, 77 // which makes this rule separated into multiple rules. 78 ruleItems := strings.Split(strings.TrimSpace(input.Rule), "|") 79 for i := 0; ; { 80 array := strings.Split(ruleItems[i], ":") 81 _, ok := allSupportedRules[array[0]] 82 if !ok && v.getRuleFunc(array[0]) == nil { 83 if i > 0 && ruleItems[i-1][:5] == "regex" { 84 ruleItems[i-1] += "|" + ruleItems[i] 85 ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) 86 } else { 87 return newErrorStr( 88 internalRulesErrRuleName, 89 internalRulesErrRuleName+": "+input.Rule, 90 ) 91 } 92 } else { 93 i++ 94 } 95 if i == len(ruleItems) { 96 break 97 } 98 } 99 var ( 100 hasBailRule = false 101 ) 102 for index := 0; index < len(ruleItems); { 103 var ( 104 err error 105 match = false // whether this rule is matched(has no error) 106 results = ruleRegex.FindStringSubmatch(ruleItems[index]) // split single rule. 107 ruleKey = strings.TrimSpace(results[1]) // rule name like "max" in rule "max: 6" 108 rulePattern = strings.TrimSpace(results[2]) // rule value if any like "6" in rule:"max:6" 109 customRuleFunc RuleFunc 110 ) 111 112 if !hasBailRule && ruleKey == bailRuleName { 113 hasBailRule = true 114 } 115 116 // Ignore logic executing for marked rules. 117 if markedRuleMap[ruleKey] { 118 index++ 119 continue 120 } 121 122 if len(msgArray) > index { 123 customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index]) 124 } 125 126 // Custom rule handling. 127 // 1. It firstly checks and uses the custom registered rules functions in the current Validator. 128 // 2. It secondly checks and uses the globally registered rules functions. 129 // 3. It finally checks and uses the build-in rules functions. 130 customRuleFunc = v.getRuleFunc(ruleKey) 131 if customRuleFunc != nil { 132 // It checks custom validation rules with most priority. 133 message := v.getErrorMessageByRule(ruleKey, customMsgMap) 134 if err := customRuleFunc(v.ctx, ruleItems[index], input.Value, message, input.DataRaw); err != nil { 135 match = false 136 errorMsgArray[ruleKey] = err.Error() 137 } else { 138 match = true 139 } 140 } else { 141 // It checks build-in validation rules if there's no custom rule. 142 match, err = v.doCheckBuildInRules(doCheckBuildInRulesInput{ 143 Index: index, 144 Value: input.Value, 145 RuleKey: ruleKey, 146 RulePattern: rulePattern, 147 RuleItems: ruleItems, 148 DataMap: input.DataMap, 149 CustomMsgMap: customMsgMap, 150 }) 151 if !match && err != nil { 152 errorMsgArray[ruleKey] = err.Error() 153 } 154 } 155 156 // Error message handling. 157 if !match { 158 // It does nothing if the error message for this rule 159 // is already set in previous validation. 160 if _, ok := errorMsgArray[ruleKey]; !ok { 161 errorMsgArray[ruleKey] = v.getErrorMessageByRule(ruleKey, customMsgMap) 162 } 163 // If it is with error and there's bail rule, 164 // it then does not continue validating for left rules. 165 if hasBailRule { 166 break 167 } 168 } 169 index++ 170 } 171 if len(errorMsgArray) > 0 { 172 return newError(gcode.CodeValidationFailed, []fieldRule{{Name: input.Name, Rule: input.Rule}}, map[string]map[string]string{ 173 input.Name: errorMsgArray, 174 }) 175 } 176 return nil 177 } 178 179 type doCheckBuildInRulesInput struct { 180 Index int 181 Value interface{} 182 RuleKey string 183 RulePattern string 184 RuleItems []string 185 DataMap map[string]interface{} 186 CustomMsgMap map[string]string 187 } 188 189 func (v *Validator) doCheckBuildInRules(input doCheckBuildInRulesInput) (match bool, err error) { 190 valueStr := gconv.String(input.Value) 191 switch input.RuleKey { 192 // Required rules. 193 case 194 "required", 195 "required-if", 196 "required-unless", 197 "required-with", 198 "required-with-all", 199 "required-without", 200 "required-without-all": 201 match = v.checkRequired(input.Value, input.RuleKey, input.RulePattern, input.DataMap) 202 203 // Length rules. 204 // It also supports length of unicode string. 205 case 206 "length", 207 "min-length", 208 "max-length", 209 "size": 210 if msg := v.checkLength(valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { 211 return match, gerror.NewOption(gerror.Option{ 212 Text: msg, 213 Code: gcode.CodeValidationFailed, 214 }) 215 } else { 216 match = true 217 } 218 219 // Range rules. 220 case 221 "min", 222 "max", 223 "between": 224 if msg := v.checkRange(valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { 225 return match, gerror.NewOption(gerror.Option{ 226 Text: msg, 227 Code: gcode.CodeValidationFailed, 228 }) 229 } else { 230 match = true 231 } 232 233 // Custom regular expression. 234 case "regex": 235 // It here should check the rule as there might be special char '|' in it. 236 for i := input.Index + 1; i < len(input.RuleItems); i++ { 237 if !gregex.IsMatchString(singleRulePattern, input.RuleItems[i]) { 238 input.RulePattern += "|" + input.RuleItems[i] 239 input.Index++ 240 } 241 } 242 match = gregex.IsMatchString(input.RulePattern, valueStr) 243 244 // Date rules. 245 case "date": 246 // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. 247 if v, ok := input.Value.(apiTime); ok { 248 return !v.IsZero(), nil 249 } 250 match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr) 251 252 // Date rule with specified format. 253 case "date-format": 254 // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. 255 if v, ok := input.Value.(apiTime); ok { 256 return !v.IsZero(), nil 257 } 258 if _, err := gtime.StrToTimeFormat(valueStr, input.RulePattern); err == nil { 259 match = true 260 } else { 261 var msg string 262 msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) 263 msg = strings.Replace(msg, ":format", input.RulePattern, -1) 264 return match, gerror.NewOption(gerror.Option{ 265 Text: msg, 266 Code: gcode.CodeValidationFailed, 267 }) 268 } 269 270 // Values of two fields should be equal as string. 271 case "same": 272 _, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern) 273 if foundValue != nil { 274 if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { 275 match = true 276 } 277 } 278 if !match { 279 var msg string 280 msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) 281 msg = strings.Replace(msg, ":field", input.RulePattern, -1) 282 return match, gerror.NewOption(gerror.Option{ 283 Text: msg, 284 Code: gcode.CodeValidationFailed, 285 }) 286 } 287 288 // Values of two fields should not be equal as string. 289 case "different": 290 match = true 291 _, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern) 292 if foundValue != nil { 293 if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { 294 match = false 295 } 296 } 297 if !match { 298 var msg string 299 msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) 300 msg = strings.Replace(msg, ":field", input.RulePattern, -1) 301 return match, gerror.NewOption(gerror.Option{ 302 Text: msg, 303 Code: gcode.CodeValidationFailed, 304 }) 305 } 306 307 // Field value should be in range of. 308 case "in": 309 array := strings.Split(input.RulePattern, ",") 310 for _, v := range array { 311 if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 { 312 match = true 313 break 314 } 315 } 316 317 // Field value should not be in range of. 318 case "not-in": 319 match = true 320 array := strings.Split(input.RulePattern, ",") 321 for _, v := range array { 322 if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 { 323 match = false 324 break 325 } 326 } 327 328 // Phone format validation. 329 // 1. China Mobile: 330 // 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188, 331 // 178(4G), 147(Net); 332 // 172 333 // 334 // 2. China Unicom: 335 // 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175 336 // 337 // 3. China Telecom: 338 // 133, 153, 180, 181, 189, 177(4G) 339 // 340 // 4. Satelite: 341 // 1349 342 // 343 // 5. Virtual: 344 // 170, 173 345 // 346 // 6. 2018: 347 // 16x, 19x 348 case "phone": 349 match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,2,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, valueStr) 350 351 // Loose mobile phone number verification(宽松的手机号验证) 352 // As long as the 11 digit numbers beginning with 353 // 13, 14, 15, 16, 17, 18, 19 can pass the verification (只要满足 13、14、15、16、17、18、19开头的11位数字都可以通过验证) 354 case "phone-loose": 355 match = gregex.IsMatchString(`^1(3|4|5|6|7|8|9)\d{9}$`, valueStr) 356 357 // Telephone number: 358 // "XXXX-XXXXXXX" 359 // "XXXX-XXXXXXXX" 360 // "XXX-XXXXXXX" 361 // "XXX-XXXXXXXX" 362 // "XXXXXXX" 363 // "XXXXXXXX" 364 case "telephone": 365 match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, valueStr) 366 367 // QQ number: from 10000. 368 case "qq": 369 match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, valueStr) 370 371 // Postcode number. 372 case "postcode": 373 match = gregex.IsMatchString(`^\d{6}$`, valueStr) 374 375 // China resident id number. 376 // 377 // xxxxxx yyyy MM dd 375 0 十八位 378 // xxxxxx yy MM dd 75 0 十五位 379 // 380 // 地区: [1-9]\d{5} 381 // 年的前两位:(18|19|([23]\d)) 1800-2399 382 // 年的后两位:\d{2} 383 // 月份: ((0[1-9])|(10|11|12)) 384 // 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+ 385 // 386 // 三位顺序码:\d{3} 387 // 两位顺序码:\d{2} 388 // 校验码: [0-9Xx] 389 // 390 // 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$ 391 // 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$ 392 // 393 // 总: 394 // (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$) 395 case "resident-id": 396 match = v.checkResidentId(valueStr) 397 398 // Bank card number using LUHN algorithm. 399 case "bank-card": 400 match = v.checkLuHn(valueStr) 401 402 // Universal passport format rule: 403 // Starting with letter, containing only numbers or underscores, length between 6 and 18. 404 case "passport": 405 match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, valueStr) 406 407 // Universal password format rule1: 408 // Containing any visible chars, length between 6 and 18. 409 case "password": 410 match = gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) 411 412 // Universal password format rule2: 413 // Must meet password rule1, must contain lower and upper letters and numbers. 414 case "password2": 415 if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) && 416 gregex.IsMatchString(`[a-z]+`, valueStr) && 417 gregex.IsMatchString(`[A-Z]+`, valueStr) && 418 gregex.IsMatchString(`\d+`, valueStr) { 419 match = true 420 } 421 422 // Universal password format rule3: 423 // Must meet password rule1, must contain lower and upper letters, numbers and special chars. 424 case "password3": 425 if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) && 426 gregex.IsMatchString(`[a-z]+`, valueStr) && 427 gregex.IsMatchString(`[A-Z]+`, valueStr) && 428 gregex.IsMatchString(`\d+`, valueStr) && 429 gregex.IsMatchString(`[^a-zA-Z0-9]+`, valueStr) { 430 match = true 431 } 432 433 // Json. 434 case "json": 435 if json.Valid([]byte(valueStr)) { 436 match = true 437 } 438 439 // Integer. 440 case "integer": 441 if _, err := strconv.Atoi(valueStr); err == nil { 442 match = true 443 } 444 445 // Float. 446 case "float": 447 if _, err := strconv.ParseFloat(valueStr, 10); err == nil { 448 match = true 449 } 450 451 // Boolean(1,true,on,yes:true | 0,false,off,no,"":false). 452 case "boolean": 453 match = false 454 if _, ok := boolMap[strings.ToLower(valueStr)]; ok { 455 match = true 456 } 457 458 // Email. 459 case "email": 460 match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, valueStr) 461 462 // URL 463 case "url": 464 match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, valueStr) 465 466 // Domain 467 case "domain": 468 match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, valueStr) 469 470 // IP(IPv4/IPv6). 471 case "ip": 472 match = gipv4.Validate(valueStr) || gipv6.Validate(valueStr) 473 474 // IPv4. 475 case "ipv4": 476 match = gipv4.Validate(valueStr) 477 478 // IPv6. 479 case "ipv6": 480 match = gipv6.Validate(valueStr) 481 482 // MAC. 483 case "mac": 484 match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr) 485 486 default: 487 return match, gerror.NewOption(gerror.Option{ 488 Text: "Invalid rule name: " + input.RuleKey, 489 Code: gcode.CodeInvalidParameter, 490 }) 491 } 492 return match, nil 493 }