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  }