github.com/wangyougui/gf/v2@v2.6.5/util/gvalid/gvalid_validator_check_struct.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 "reflect" 12 "strings" 13 14 "github.com/wangyougui/gf/v2/errors/gcode" 15 "github.com/wangyougui/gf/v2/internal/empty" 16 "github.com/wangyougui/gf/v2/os/gstructs" 17 "github.com/wangyougui/gf/v2/util/gconv" 18 "github.com/wangyougui/gf/v2/util/gmeta" 19 "github.com/wangyougui/gf/v2/util/gutil" 20 ) 21 22 func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error { 23 var ( 24 errorMaps = make(map[string]map[string]error) // Returning error. 25 fieldToAliasNameMap = make(map[string]string) // Field names to alias name map. 26 resultSequenceRules = make([]fieldRule, 0) 27 isEmptyData = empty.IsEmpty(v.data) 28 isEmptyAssoc = empty.IsEmpty(v.assoc) 29 ) 30 fieldMap, err := gstructs.FieldMap(gstructs.FieldMapInput{ 31 Pointer: object, 32 PriorityTagArray: aliasNameTagPriority, 33 RecursiveOption: gstructs.RecursiveOptionEmbedded, 34 }) 35 if err != nil { 36 return newValidationErrorByStr(internalObjectErrRuleName, err) 37 } 38 39 // It here must use gstructs.TagFields not gstructs.FieldMap to ensure error sequence. 40 tagFields, err := gstructs.TagFields(object, structTagPriority) 41 if err != nil { 42 return newValidationErrorByStr(internalObjectErrRuleName, err) 43 } 44 // If there's no struct tag and validation rules, it does nothing and returns quickly. 45 if len(tagFields) == 0 && v.messages == nil && isEmptyData && isEmptyAssoc { 46 return nil 47 } 48 49 var ( 50 inputParamMap map[string]interface{} 51 checkRules = make([]fieldRule, 0) 52 nameToRuleMap = make(map[string]string) // just for internally searching index purpose. 53 customMessage = make(CustomMsg) // Custom rule error message map. 54 checkValueData = v.assoc // Ready to be validated data. 55 ) 56 if checkValueData == nil { 57 checkValueData = object 58 } 59 switch assertValue := v.rules.(type) { 60 // Sequence tag: []sequence tag 61 // Sequence has order for error results. 62 case []string: 63 for _, tag := range assertValue { 64 name, rule, msg := ParseTagValue(tag) 65 if len(name) == 0 { 66 continue 67 } 68 if len(msg) > 0 { 69 var ( 70 msgArray = strings.Split(msg, "|") 71 ruleArray = strings.Split(rule, "|") 72 ) 73 for k, ruleKey := range ruleArray { 74 // If length of custom messages is lesser than length of rules, 75 // the rest rules use the default error messages. 76 if len(msgArray) <= k { 77 continue 78 } 79 if len(msgArray[k]) == 0 { 80 continue 81 } 82 array := strings.Split(ruleKey, ":") 83 if _, ok := customMessage[name]; !ok { 84 customMessage[name] = make(map[string]string) 85 } 86 customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) 87 } 88 } 89 nameToRuleMap[name] = rule 90 checkRules = append(checkRules, fieldRule{ 91 Name: name, 92 Rule: rule, 93 }) 94 } 95 96 // Map type rules does not support sequence. 97 // Format: map[key]rule 98 case map[string]string: 99 nameToRuleMap = assertValue 100 for name, rule := range assertValue { 101 checkRules = append(checkRules, fieldRule{ 102 Name: name, 103 Rule: rule, 104 }) 105 } 106 } 107 // If there's no struct tag and validation rules, it does nothing and returns quickly. 108 if len(tagFields) == 0 && len(checkRules) == 0 && isEmptyData && isEmptyAssoc { 109 return nil 110 } 111 // Input parameter map handling. 112 if v.assoc == nil || !v.useAssocInsteadOfObjectAttributes { 113 inputParamMap = make(map[string]interface{}) 114 } else { 115 inputParamMap = gconv.Map(v.assoc) 116 } 117 // Checks and extends the parameters map with struct alias tag. 118 if !v.useAssocInsteadOfObjectAttributes { 119 for nameOrTag, field := range fieldMap { 120 inputParamMap[nameOrTag] = field.Value.Interface() 121 if nameOrTag != field.Name() { 122 inputParamMap[field.Name()] = field.Value.Interface() 123 } 124 } 125 } 126 127 // Merge the custom validation rules with rules in struct tag. 128 // The custom rules has the most high priority that can overwrite the struct tag rules. 129 for _, field := range tagFields { 130 var ( 131 isMeta bool 132 fieldName = field.Name() // Attribute name. 133 name, rule, msg = ParseTagValue(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only. 134 ) 135 if len(name) == 0 { 136 if value, ok := fieldToAliasNameMap[fieldName]; ok { 137 // It uses alias name of the attribute if its alias name tag exists. 138 name = value 139 } else { 140 // It or else uses the attribute name directly. 141 name = fieldName 142 } 143 } else { 144 // It uses the alias name from validation rule. 145 fieldToAliasNameMap[fieldName] = name 146 } 147 // It here extends the params map using alias names. 148 // Note that the variable `name` might be alias name or attribute name. 149 if _, ok := inputParamMap[name]; !ok { 150 if !v.useAssocInsteadOfObjectAttributes { 151 inputParamMap[name] = field.Value.Interface() 152 } else { 153 if name != fieldName { 154 if foundKey, foundValue := gutil.MapPossibleItemByKey(inputParamMap, fieldName); foundKey != "" { 155 inputParamMap[name] = foundValue 156 } 157 } 158 } 159 } 160 161 if _, ok := nameToRuleMap[name]; !ok { 162 if _, ok = nameToRuleMap[fieldName]; ok { 163 // If there's alias name, 164 // use alias name as its key and remove the field name key. 165 nameToRuleMap[name] = nameToRuleMap[fieldName] 166 delete(nameToRuleMap, fieldName) 167 for index, checkRuleItem := range checkRules { 168 if fieldName == checkRuleItem.Name { 169 checkRuleItem.Name = name 170 checkRules[index] = checkRuleItem 171 break 172 } 173 } 174 } else { 175 nameToRuleMap[name] = rule 176 if fieldValue := field.Value.Interface(); fieldValue != nil { 177 _, isMeta = fieldValue.(gmeta.Meta) 178 } 179 checkRules = append(checkRules, fieldRule{ 180 Name: name, 181 Rule: rule, 182 IsMeta: isMeta, 183 FieldKind: field.OriginalKind(), 184 FieldType: field.Type(), 185 }) 186 } 187 } else { 188 // The input rules can overwrite the rules in struct tag. 189 continue 190 } 191 192 if len(msg) > 0 { 193 var ( 194 msgArray = strings.Split(msg, "|") 195 ruleArray = strings.Split(rule, "|") 196 ) 197 for k, ruleKey := range ruleArray { 198 // If length of custom messages is lesser than length of rules, 199 // the rest rules use the default error messages. 200 if len(msgArray) <= k { 201 continue 202 } 203 if len(msgArray[k]) == 0 { 204 continue 205 } 206 array := strings.Split(ruleKey, ":") 207 if _, ok := customMessage[name]; !ok { 208 customMessage[name] = make(map[string]string) 209 } 210 customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) 211 } 212 } 213 } 214 215 // Custom error messages, 216 // which have the most priority than `rules` and struct tag. 217 if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { 218 for k, msgName := range msg { 219 if aliasName, ok := fieldToAliasNameMap[k]; ok { 220 // Overwrite the key of field name. 221 customMessage[aliasName] = msgName 222 } else { 223 customMessage[k] = msgName 224 } 225 } 226 } 227 228 // Temporary variable for value. 229 var value interface{} 230 231 // It checks the struct recursively if its attribute is a struct/struct slice. 232 for _, field := range fieldMap { 233 // No validation interface implements check. 234 if _, ok := field.Value.Interface().(iNoValidation); ok { 235 continue 236 } 237 // No validation field tag check. 238 if _, ok := field.TagLookup(noValidationTagName); ok { 239 continue 240 } 241 if field.IsEmbedded() { 242 // The attributes of embedded struct are considered as direct attributes of its parent struct. 243 if err = v.doCheckStruct(ctx, field.Value); err != nil { 244 // It merges the errors into single error map. 245 for k, m := range err.(*validationError).errors { 246 errorMaps[k] = m 247 } 248 } 249 } else { 250 // The `field.TagValue` is the alias name of field.Name(). 251 // Eg, value from struct tag `p`. 252 if field.TagValue != "" { 253 fieldToAliasNameMap[field.Name()] = field.TagValue 254 } 255 switch field.OriginalKind() { 256 case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array: 257 // Recursively check attribute slice/map. 258 value = getPossibleValueFromMap( 259 inputParamMap, field.Name(), fieldToAliasNameMap[field.Name()], 260 ) 261 if empty.IsNil(value) { 262 switch field.Kind() { 263 case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Array: 264 // Nothing to do. 265 continue 266 } 267 } 268 v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{ 269 Value: value, 270 Kind: field.OriginalKind(), 271 Type: field.Type().Type, 272 ErrorMaps: errorMaps, 273 ResultSequenceRules: &resultSequenceRules, 274 }) 275 } 276 } 277 if v.bail && len(errorMaps) > 0 { 278 break 279 } 280 } 281 if v.bail && len(errorMaps) > 0 { 282 return newValidationError(gcode.CodeValidationFailed, resultSequenceRules, errorMaps) 283 } 284 285 // The following logic is the same as some of CheckMap but with sequence support. 286 for _, checkRuleItem := range checkRules { 287 if !checkRuleItem.IsMeta { 288 value = getPossibleValueFromMap( 289 inputParamMap, checkRuleItem.Name, fieldToAliasNameMap[checkRuleItem.Name], 290 ) 291 } 292 // Empty json string checks according to mapping field kind. 293 if value != nil { 294 switch checkRuleItem.FieldKind { 295 case reflect.Struct, reflect.Map: 296 if gconv.String(value) == emptyJsonObjectStr { 297 value = "" 298 } 299 case reflect.Slice, reflect.Array: 300 if gconv.String(value) == emptyJsonArrayStr { 301 value = "" 302 } 303 } 304 } 305 // It checks each rule and its value in loop. 306 if validatedError := v.doCheckValue(ctx, doCheckValueInput{ 307 Name: checkRuleItem.Name, 308 Value: value, 309 ValueType: checkRuleItem.FieldType, 310 Rule: checkRuleItem.Rule, 311 Messages: customMessage[checkRuleItem.Name], 312 DataRaw: checkValueData, 313 DataMap: inputParamMap, 314 }); validatedError != nil { 315 _, errorItem := validatedError.FirstItem() 316 // ============================================================ 317 // Only in map and struct validations: 318 // If value is nil or empty string and has no required* rules, 319 // it clears the error message. 320 // ============================================================ 321 if !checkRuleItem.IsMeta && (value == nil || gconv.String(value) == "") { 322 required := false 323 // rule => error 324 for ruleKey := range errorItem { 325 if required = v.checkRuleRequired(ruleKey); required { 326 break 327 } 328 } 329 if !required { 330 continue 331 } 332 } 333 if _, ok := errorMaps[checkRuleItem.Name]; !ok { 334 errorMaps[checkRuleItem.Name] = make(map[string]error) 335 } 336 for ruleKey, errorItemMsgMap := range errorItem { 337 errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap 338 } 339 // Bail feature. 340 if v.bail { 341 break 342 } 343 } 344 } 345 if len(errorMaps) > 0 { 346 return newValidationError( 347 gcode.CodeValidationFailed, 348 append(checkRules, resultSequenceRules...), 349 errorMaps, 350 ) 351 } 352 return nil 353 } 354 355 func getPossibleValueFromMap(inputParamMap map[string]interface{}, fieldName, aliasName string) (value interface{}) { 356 _, value = gutil.MapPossibleItemByKey(inputParamMap, fieldName) 357 if value == nil && aliasName != "" { 358 _, value = gutil.MapPossibleItemByKey(inputParamMap, aliasName) 359 } 360 return 361 }