github.com/gogf/gf@v1.16.9/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/gogf/gf. 6 7 package gvalid 8 9 import ( 10 "github.com/gogf/gf/errors/gcode" 11 "github.com/gogf/gf/internal/structs" 12 "github.com/gogf/gf/util/gconv" 13 "github.com/gogf/gf/util/gutil" 14 "strings" 15 ) 16 17 // CheckStruct validates struct and returns the error result. 18 // The parameter `object` should be type of struct/*struct. 19 func (v *Validator) CheckStruct(object interface{}) Error { 20 return v.doCheckStruct(object) 21 } 22 23 func (v *Validator) doCheckStruct(object interface{}) Error { 24 var ( 25 errorMaps = make(map[string]map[string]string) // Returning error. 26 fieldToAliasNameMap = make(map[string]string) // Field name to alias name map. 27 ) 28 fieldMap, err := structs.FieldMap(structs.FieldMapInput{ 29 Pointer: object, 30 PriorityTagArray: aliasNameTagPriority, 31 RecursiveOption: structs.RecursiveOptionEmbedded, 32 }) 33 if err != nil { 34 return newErrorStr(internalObjectErrRuleName, err.Error()) 35 } 36 // It checks the struct recursively the its attribute is an embedded struct. 37 for _, field := range fieldMap { 38 if field.IsEmbedded() { 39 // No validation interface implements check. 40 if _, ok := field.Value.Interface().(apiNoValidation); ok { 41 continue 42 } 43 if _, ok := field.TagLookup(noValidationTagName); ok { 44 continue 45 } 46 if err := v.doCheckStruct(field.Value); err != nil { 47 // It merges the errors into single error map. 48 for k, m := range err.(*validationError).errors { 49 errorMaps[k] = m 50 } 51 } 52 } else { 53 if field.TagValue != "" { 54 fieldToAliasNameMap[field.Name()] = field.TagValue 55 } 56 } 57 } 58 // It here must use structs.TagFields not structs.FieldMap to ensure error sequence. 59 tagField, err := structs.TagFields(object, structTagPriority) 60 if err != nil { 61 return newErrorStr(internalObjectErrRuleName, err.Error()) 62 } 63 // If there's no struct tag and validation rules, it does nothing and returns quickly. 64 if len(tagField) == 0 && v.messages == nil { 65 return nil 66 } 67 68 var ( 69 inputParamMap map[string]interface{} 70 checkRules = make([]fieldRule, 0) 71 nameToRuleMap = make(map[string]string) // just for internally searching index purpose. 72 customMessage = make(CustomMsg) // Custom rule error message map. 73 checkValueData = v.data // Ready to be validated data, which can be type of . 74 ) 75 if checkValueData == nil { 76 checkValueData = object 77 } 78 switch assertValue := v.rules.(type) { 79 // Sequence tag: []sequence tag 80 // Sequence has order for error results. 81 case []string: 82 for _, tag := range assertValue { 83 name, rule, msg := parseSequenceTag(tag) 84 if len(name) == 0 { 85 continue 86 } 87 if len(msg) > 0 { 88 var ( 89 msgArray = strings.Split(msg, "|") 90 ruleArray = strings.Split(rule, "|") 91 ) 92 for k, v := range ruleArray { 93 // If length of custom messages is lesser than length of rules, 94 // the rest rules use the default error messages. 95 if len(msgArray) <= k { 96 continue 97 } 98 if len(msgArray[k]) == 0 { 99 continue 100 } 101 array := strings.Split(v, ":") 102 if _, ok := customMessage[name]; !ok { 103 customMessage[name] = make(map[string]string) 104 } 105 customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) 106 } 107 } 108 nameToRuleMap[name] = rule 109 checkRules = append(checkRules, fieldRule{ 110 Name: name, 111 Rule: rule, 112 }) 113 } 114 115 // Map type rules does not support sequence. 116 // Format: map[key]rule 117 case map[string]string: 118 nameToRuleMap = assertValue 119 for name, rule := range assertValue { 120 checkRules = append(checkRules, fieldRule{ 121 Name: name, 122 Rule: rule, 123 }) 124 } 125 } 126 // If there's no struct tag and validation rules, it does nothing and returns quickly. 127 if len(tagField) == 0 && len(checkRules) == 0 { 128 return nil 129 } 130 // Input parameter map handling. 131 if v.data == nil || !v.useDataInsteadOfObjectAttributes { 132 inputParamMap = make(map[string]interface{}) 133 } else { 134 inputParamMap = gconv.Map(v.data) 135 } 136 // Checks and extends the parameters map with struct alias tag. 137 if !v.useDataInsteadOfObjectAttributes { 138 for nameOrTag, field := range fieldMap { 139 inputParamMap[nameOrTag] = field.Value.Interface() 140 if nameOrTag != field.Name() { 141 inputParamMap[field.Name()] = field.Value.Interface() 142 } 143 } 144 } 145 146 // Merge the custom validation rules with rules in struct tag. 147 // The custom rules has the most high priority that can overwrite the struct tag rules. 148 for _, field := range tagField { 149 var ( 150 fieldName = field.Name() // Attribute name. 151 name, rule, msg = parseSequenceTag(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only. 152 ) 153 if len(name) == 0 { 154 if v, ok := fieldToAliasNameMap[fieldName]; ok { 155 // It uses alias name of the attribute if its alias name tag exists. 156 name = v 157 } else { 158 // It or else uses the attribute name directly. 159 name = fieldName 160 } 161 } else { 162 // It uses the alias name from validation rule. 163 fieldToAliasNameMap[fieldName] = name 164 } 165 // It here extends the params map using alias names. 166 // Note that the variable `name` might be alias name or attribute name. 167 if _, ok := inputParamMap[name]; !ok { 168 if !v.useDataInsteadOfObjectAttributes { 169 inputParamMap[name] = field.Value.Interface() 170 } else { 171 if name != fieldName { 172 if foundKey, foundValue := gutil.MapPossibleItemByKey(inputParamMap, fieldName); foundKey != "" { 173 inputParamMap[name] = foundValue 174 } 175 } 176 } 177 } 178 179 if _, ok := nameToRuleMap[name]; !ok { 180 if _, ok := nameToRuleMap[fieldName]; ok { 181 // If there's alias name, 182 // use alias name as its key and remove the field name key. 183 nameToRuleMap[name] = nameToRuleMap[fieldName] 184 delete(nameToRuleMap, fieldName) 185 for index, checkRuleItem := range checkRules { 186 if fieldName == checkRuleItem.Name { 187 checkRuleItem.Name = name 188 checkRules[index] = checkRuleItem 189 break 190 } 191 } 192 } else { 193 nameToRuleMap[name] = rule 194 checkRules = append(checkRules, fieldRule{ 195 Name: name, 196 Rule: rule, 197 }) 198 } 199 } else { 200 // The input rules can overwrite the rules in struct tag. 201 continue 202 } 203 204 if len(msg) > 0 { 205 var ( 206 msgArray = strings.Split(msg, "|") 207 ruleArray = strings.Split(rule, "|") 208 ) 209 for k, v := range ruleArray { 210 // If length of custom messages is lesser than length of rules, 211 // the rest rules use the default error messages. 212 if len(msgArray) <= k { 213 continue 214 } 215 if len(msgArray[k]) == 0 { 216 continue 217 } 218 array := strings.Split(v, ":") 219 if _, ok := customMessage[name]; !ok { 220 customMessage[name] = make(map[string]string) 221 } 222 customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) 223 } 224 } 225 } 226 227 // Custom error messages, 228 // which have the most priority than `rules` and struct tag. 229 if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { 230 for k, v := range msg { 231 if a, ok := fieldToAliasNameMap[k]; ok { 232 // Overwrite the key of field name. 233 customMessage[a] = v 234 } else { 235 customMessage[k] = v 236 } 237 } 238 } 239 240 // The following logic is the same as some of CheckMap but with sequence support. 241 var ( 242 value interface{} 243 ) 244 for _, checkRuleItem := range checkRules { 245 _, value = gutil.MapPossibleItemByKey(inputParamMap, checkRuleItem.Name) 246 // It checks each rule and its value in loop. 247 if validatedError := v.doCheckValue(doCheckValueInput{ 248 Name: checkRuleItem.Name, 249 Value: value, 250 Rule: checkRuleItem.Rule, 251 Messages: customMessage[checkRuleItem.Name], 252 DataRaw: checkValueData, 253 DataMap: inputParamMap, 254 }); validatedError != nil { 255 _, errorItem := validatedError.FirstItem() 256 // =================================================================== 257 // Only in map and struct validations, if value is nil or empty string 258 // and has no required* rules, it clears the error message. 259 // =================================================================== 260 if value == nil || gconv.String(value) == "" { 261 required := false 262 // rule => error 263 for ruleKey := range errorItem { 264 // Default required rules. 265 if _, ok := mustCheckRulesEvenValueEmpty[ruleKey]; ok { 266 required = true 267 break 268 } 269 // Custom rules are also required in default. 270 if f := v.getRuleFunc(ruleKey); f != nil { 271 required = true 272 break 273 } 274 } 275 if !required { 276 continue 277 } 278 } 279 if _, ok := errorMaps[checkRuleItem.Name]; !ok { 280 errorMaps[checkRuleItem.Name] = make(map[string]string) 281 } 282 for ruleKey, errorItemMsgMap := range errorItem { 283 errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap 284 } 285 if v.bail { 286 break 287 } 288 } 289 } 290 if len(errorMaps) > 0 { 291 return newError(gcode.CodeValidationFailed, checkRules, errorMaps) 292 } 293 return nil 294 }