github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/gf. 6 7 package gvalid 8 9 import ( 10 "context" 11 "errors" 12 "reflect" 13 "strings" 14 15 "github.com/wangyougui/gf/v2/container/gvar" 16 "github.com/wangyougui/gf/v2/encoding/gjson" 17 "github.com/wangyougui/gf/v2/errors/gcode" 18 "github.com/wangyougui/gf/v2/errors/gerror" 19 "github.com/wangyougui/gf/v2/text/gregex" 20 "github.com/wangyougui/gf/v2/text/gstr" 21 "github.com/wangyougui/gf/v2/util/gconv" 22 "github.com/wangyougui/gf/v2/util/gvalid/internal/builtin" 23 ) 24 25 type doCheckValueInput struct { 26 Name string // Name specifies the name of parameter `value`, which might be the custom tag name of the parameter. 27 Value interface{} // Value specifies the value for the rules to be validated. 28 ValueType reflect.Type // ValueType specifies the type of the value, mainly used for value type id retrieving. 29 Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc. 30 Messages interface{} // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice. 31 DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value. 32 DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally 33 } 34 35 // doCheckValue does the really rules validation for single key-value. 36 func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Error { 37 // If there's no validation rules, it does nothing and returns quickly. 38 if in.Rule == "" { 39 return nil 40 } 41 // It converts value to string and then does the validation. 42 var ( 43 // Do not trim it as the space is also part of the value. 44 ruleErrorMap = make(map[string]error) 45 ) 46 // Custom error messages handling. 47 var ( 48 msgArray = make([]string, 0) 49 customMsgMap = make(map[string]string) 50 ) 51 switch messages := in.Messages.(type) { 52 case string: 53 msgArray = strings.Split(messages, "|") 54 55 default: 56 for k, message := range gconv.Map(in.Messages) { 57 customMsgMap[k] = gconv.String(message) 58 } 59 } 60 // Handle the char '|' in the rule, 61 // which makes this rule separated into multiple rules. 62 ruleItems := strings.Split(strings.TrimSpace(in.Rule), "|") 63 for i := 0; ; { 64 array := strings.Split(ruleItems[i], ":") 65 if builtin.GetRule(array[0]) == nil && v.getCustomRuleFunc(array[0]) == nil { 66 // ============================ SPECIAL ============================ 67 // Special `regex` and `not-regex` rules. 68 // Merge the regex pattern if there are special chars, like ':', '|', in pattern. 69 // ============================ SPECIAL ============================ 70 var ( 71 ruleNameRegexLengthMatch bool 72 ruleNameNotRegexLengthMatch bool 73 ) 74 if i > 0 { 75 ruleItem := ruleItems[i-1] 76 if len(ruleItem) >= len(ruleNameRegex) && ruleItem[:len(ruleNameRegex)] == ruleNameRegex { 77 ruleNameRegexLengthMatch = true 78 } 79 if len(ruleItem) >= len(ruleNameNotRegex) && ruleItem[:len(ruleNameNotRegex)] == ruleNameNotRegex { 80 ruleNameNotRegexLengthMatch = true 81 } 82 } 83 if i > 0 && (ruleNameRegexLengthMatch || ruleNameNotRegexLengthMatch) { 84 ruleItems[i-1] += "|" + ruleItems[i] 85 ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) 86 } else { 87 return newValidationErrorByStr( 88 internalRulesErrRuleName, 89 errors.New(internalRulesErrRuleName+": "+ruleItems[i]), 90 ) 91 } 92 } else { 93 i++ 94 } 95 if i == len(ruleItems) { 96 break 97 } 98 } 99 var ( 100 hasBailRule = v.bail 101 hasForeachRule = v.foreach 102 hasCaseInsensitive = v.caseInsensitive 103 ) 104 for index := 0; index < len(ruleItems); { 105 var ( 106 err error 107 results = ruleRegex.FindStringSubmatch(ruleItems[index]) // split single rule. 108 ruleKey = gstr.Trim(results[1]) // rule key like "max" in rule "max: 6" 109 rulePattern = gstr.Trim(results[2]) // rule pattern is like "6" in rule:"max:6" 110 ) 111 112 if !hasBailRule && ruleKey == ruleNameBail { 113 hasBailRule = true 114 } 115 if !hasForeachRule && ruleKey == ruleNameForeach { 116 hasForeachRule = true 117 } 118 if !hasCaseInsensitive && ruleKey == ruleNameCi { 119 hasCaseInsensitive = true 120 } 121 122 // Ignore logic executing for marked rules. 123 if decorativeRuleMap[ruleKey] { 124 index++ 125 continue 126 } 127 128 if len(msgArray) > index { 129 customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index]) 130 } 131 132 var ( 133 message = v.getErrorMessageByRule(ctx, ruleKey, customMsgMap) 134 customRuleFunc = v.getCustomRuleFunc(ruleKey) 135 builtinRule = builtin.GetRule(ruleKey) 136 foreachValues = []interface{}{in.Value} 137 ) 138 if hasForeachRule { 139 // As it marks `foreach`, so it converts the value to slice. 140 foreachValues = gconv.Interfaces(in.Value) 141 // Reset `foreach` rule as it only takes effect just once for next rule. 142 hasForeachRule = false 143 } 144 145 for _, value := range foreachValues { 146 switch { 147 // Custom validation rules. 148 case customRuleFunc != nil: 149 err = customRuleFunc(ctx, RuleFuncInput{ 150 Rule: ruleItems[index], 151 Message: message, 152 Field: in.Name, 153 ValueType: in.ValueType, 154 Value: gvar.New(value), 155 Data: gvar.New(in.DataRaw), 156 }) 157 158 // Builtin validation rules. 159 case customRuleFunc == nil && builtinRule != nil: 160 err = builtinRule.Run(builtin.RunInput{ 161 RuleKey: ruleKey, 162 RulePattern: rulePattern, 163 Field: in.Name, 164 ValueType: in.ValueType, 165 Value: gvar.New(value), 166 Data: gvar.New(in.DataRaw), 167 Message: message, 168 Option: builtin.RunOption{ 169 CaseInsensitive: hasCaseInsensitive, 170 }, 171 }) 172 173 default: 174 // It never comes across here. 175 } 176 177 // Error handling. 178 if err != nil { 179 // Error variable replacement for error message. 180 if errMsg := err.Error(); gstr.Contains(errMsg, "{") { 181 errMsg = gstr.ReplaceByMap(errMsg, map[string]string{ 182 "{field}": in.Name, // Field name of the `value`. 183 "{value}": gconv.String(value), // Current validating value. 184 "{pattern}": rulePattern, // The variable part of the rule. 185 "{attribute}": in.Name, // The same as `{field}`. It is deprecated. 186 }) 187 errMsg, _ = gregex.ReplaceString(`\s{2,}`, ` `, errMsg) 188 err = errors.New(errMsg) 189 } 190 // The error should have stack info to indicate the error position. 191 if !gerror.HasStack(err) { 192 err = gerror.NewCode(gcode.CodeValidationFailed, err.Error()) 193 } 194 // The error should have error code that is `gcode.CodeValidationFailed`. 195 if gerror.Code(err) == gcode.CodeNil { 196 // TODO it's better using interface? 197 if e, ok := err.(*gerror.Error); ok { 198 e.SetCode(gcode.CodeValidationFailed) 199 } 200 } 201 ruleErrorMap[ruleKey] = err 202 203 // If it is with error and there's bail rule, 204 // it then does not continue validating for left rules. 205 if hasBailRule { 206 goto CheckDone 207 } 208 } 209 } 210 index++ 211 } 212 213 CheckDone: 214 if len(ruleErrorMap) > 0 { 215 return newValidationError( 216 gcode.CodeValidationFailed, 217 []fieldRule{{Name: in.Name, Rule: in.Rule}}, 218 map[string]map[string]error{ 219 in.Name: ruleErrorMap, 220 }, 221 ) 222 } 223 return nil 224 } 225 226 type doCheckValueRecursivelyInput struct { 227 Value interface{} // Value to be validated. 228 Type reflect.Type // Struct/map/slice type which to be recursively validated. 229 Kind reflect.Kind // Struct/map/slice kind to be asserted in following switch case. 230 ErrorMaps map[string]map[string]error // The validated failed error map. 231 ResultSequenceRules *[]fieldRule // The validated failed rule in sequence. 232 } 233 234 func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValueRecursivelyInput) { 235 switch in.Kind { 236 case reflect.Ptr: 237 v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{ 238 Value: in.Value, 239 Type: in.Type.Elem(), 240 Kind: in.Type.Elem().Kind(), 241 ErrorMaps: in.ErrorMaps, 242 ResultSequenceRules: in.ResultSequenceRules, 243 }) 244 245 case reflect.Struct: 246 // Ignore data, assoc, rules and messages from parent. 247 var ( 248 validator = v.Clone() 249 toBeValidatedObject interface{} 250 ) 251 if in.Type.Kind() == reflect.Ptr { 252 toBeValidatedObject = reflect.New(in.Type.Elem()).Interface() 253 } else { 254 toBeValidatedObject = reflect.New(in.Type).Interface() 255 } 256 validator.assoc = nil 257 validator.rules = nil 258 validator.messages = nil 259 if err := validator.Data(toBeValidatedObject).Assoc(in.Value).Run(ctx); err != nil { 260 // It merges the errors into single error map. 261 for k, m := range err.(*validationError).errors { 262 in.ErrorMaps[k] = m 263 } 264 if in.ResultSequenceRules != nil { 265 *in.ResultSequenceRules = append(*in.ResultSequenceRules, err.(*validationError).rules...) 266 } 267 } 268 269 case reflect.Map: 270 var ( 271 dataMap = gconv.Map(in.Value) 272 mapTypeElem = in.Type.Elem() 273 mapTypeKind = mapTypeElem.Kind() 274 ) 275 for _, item := range dataMap { 276 v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{ 277 Value: item, 278 Type: mapTypeElem, 279 Kind: mapTypeKind, 280 ErrorMaps: in.ErrorMaps, 281 ResultSequenceRules: in.ResultSequenceRules, 282 }) 283 // Bail feature. 284 if v.bail && len(in.ErrorMaps) > 0 { 285 break 286 } 287 } 288 289 case reflect.Slice, reflect.Array: 290 var array []interface{} 291 if gjson.Valid(in.Value) { 292 array = gconv.Interfaces(gconv.Bytes(in.Value)) 293 } else { 294 array = gconv.Interfaces(in.Value) 295 } 296 if len(array) == 0 { 297 return 298 } 299 for _, item := range array { 300 v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{ 301 Value: item, 302 Type: in.Type.Elem(), 303 Kind: in.Type.Elem().Kind(), 304 ErrorMaps: in.ErrorMaps, 305 ResultSequenceRules: in.ResultSequenceRules, 306 }) 307 // Bail feature. 308 if v.bail && len(in.ErrorMaps) > 0 { 309 break 310 } 311 } 312 } 313 }