mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.
**Type alias migration to `any`:**
* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)
**Generated code and templates:**
* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
**Container and utility updates:**
* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)
These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
328 lines
10 KiB
Go
328 lines
10 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gvalid
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/v2/container/gvar"
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/text/gregex"
|
|
"github.com/gogf/gf/v2/text/gstr"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
"github.com/gogf/gf/v2/util/gvalid/internal/builtin"
|
|
)
|
|
|
|
type doCheckValueInput struct {
|
|
Name string // Name specifies the name of parameter `value`, which might be the custom tag name of the parameter.
|
|
Value any // Value specifies the value for the rules to be validated.
|
|
ValueType reflect.Type // ValueType specifies the type of the value, mainly used for value type id retrieving.
|
|
Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc.
|
|
Messages any // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice.
|
|
DataRaw any // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value.
|
|
DataMap map[string]any // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally
|
|
}
|
|
|
|
// doCheckValue does the really rules validation for single key-value.
|
|
func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Error {
|
|
// If there's no validation rules, it does nothing and returns quickly.
|
|
if in.Rule == "" {
|
|
return nil
|
|
}
|
|
// It converts value to string and then does the validation.
|
|
var (
|
|
// Do not trim it as the space is also part of the value.
|
|
ruleErrorMap = make(map[string]error)
|
|
)
|
|
// Custom error messages handling.
|
|
var (
|
|
msgArray = make([]string, 0)
|
|
customMsgMap = make(map[string]string)
|
|
)
|
|
switch messages := in.Messages.(type) {
|
|
case string:
|
|
msgArray = strings.Split(messages, "|")
|
|
|
|
default:
|
|
for k, message := range gconv.Map(in.Messages) {
|
|
customMsgMap[k] = gconv.String(message)
|
|
}
|
|
}
|
|
// Handle the char '|' in the rule,
|
|
// which makes this rule separated into multiple rules.
|
|
ruleItems := strings.Split(strings.TrimSpace(in.Rule), "|")
|
|
for i := 0; ; {
|
|
array := strings.Split(ruleItems[i], ":")
|
|
if builtin.GetRule(array[0]) == nil && v.getCustomRuleFunc(array[0]) == nil {
|
|
// ============================ SPECIAL ============================
|
|
// Special `regex` and `not-regex` rules.
|
|
// Merge the regex pattern if there are special chars, like ':', '|', in pattern.
|
|
// ============================ SPECIAL ============================
|
|
var (
|
|
ruleNameRegexLengthMatch bool
|
|
ruleNameNotRegexLengthMatch bool
|
|
)
|
|
if i > 0 {
|
|
ruleItem := ruleItems[i-1]
|
|
if len(ruleItem) >= len(ruleNameRegex) && ruleItem[:len(ruleNameRegex)] == ruleNameRegex {
|
|
ruleNameRegexLengthMatch = true
|
|
}
|
|
if len(ruleItem) >= len(ruleNameNotRegex) && ruleItem[:len(ruleNameNotRegex)] == ruleNameNotRegex {
|
|
ruleNameNotRegexLengthMatch = true
|
|
}
|
|
}
|
|
if i > 0 && (ruleNameRegexLengthMatch || ruleNameNotRegexLengthMatch) {
|
|
ruleItems[i-1] += "|" + ruleItems[i]
|
|
ruleItems = append(ruleItems[:i], ruleItems[i+1:]...)
|
|
} else {
|
|
return newValidationErrorByStr(
|
|
internalRulesErrRuleName,
|
|
errors.New(internalRulesErrRuleName+": "+ruleItems[i]),
|
|
)
|
|
}
|
|
} else {
|
|
i++
|
|
}
|
|
if i == len(ruleItems) {
|
|
break
|
|
}
|
|
}
|
|
var (
|
|
hasBailRule = v.bail
|
|
hasForeachRule = v.foreach
|
|
hasCaseInsensitive = v.caseInsensitive
|
|
)
|
|
for index := 0; index < len(ruleItems); {
|
|
var (
|
|
err error
|
|
results = ruleRegex.FindStringSubmatch(ruleItems[index]) // split single rule.
|
|
ruleKey = gstr.Trim(results[1]) // rule key like "max" in rule "max: 6"
|
|
rulePattern = gstr.Trim(results[2]) // rule pattern is like "6" in rule:"max:6"
|
|
)
|
|
|
|
if !hasBailRule && ruleKey == ruleNameBail {
|
|
hasBailRule = true
|
|
}
|
|
if !hasForeachRule && ruleKey == ruleNameForeach {
|
|
hasForeachRule = true
|
|
}
|
|
if !hasCaseInsensitive && ruleKey == ruleNameCi {
|
|
hasCaseInsensitive = true
|
|
}
|
|
|
|
// Ignore logic executing for marked rules.
|
|
if decorativeRuleMap[ruleKey] {
|
|
index++
|
|
continue
|
|
}
|
|
|
|
if len(msgArray) > index {
|
|
customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index])
|
|
}
|
|
|
|
var (
|
|
message = v.getErrorMessageByRule(ctx, ruleKey, customMsgMap)
|
|
customRuleFunc = v.getCustomRuleFunc(ruleKey)
|
|
builtinRule = builtin.GetRule(ruleKey)
|
|
foreachValues = []any{in.Value}
|
|
)
|
|
if hasForeachRule {
|
|
// As it marks `foreach`, so it converts the value to slice.
|
|
foreachValues = gconv.Interfaces(in.Value)
|
|
// Reset `foreach` rule as it only takes effect just once for next rule.
|
|
hasForeachRule = false
|
|
}
|
|
|
|
for _, value := range foreachValues {
|
|
switch {
|
|
// Custom validation rules.
|
|
case customRuleFunc != nil:
|
|
err = customRuleFunc(ctx, RuleFuncInput{
|
|
Rule: ruleItems[index],
|
|
Message: message,
|
|
Field: in.Name,
|
|
ValueType: in.ValueType,
|
|
Value: gvar.New(value),
|
|
Data: gvar.New(in.DataRaw),
|
|
})
|
|
|
|
// Builtin validation rules.
|
|
case customRuleFunc == nil && builtinRule != nil:
|
|
err = builtinRule.Run(builtin.RunInput{
|
|
RuleKey: ruleKey,
|
|
RulePattern: rulePattern,
|
|
Field: in.Name,
|
|
ValueType: in.ValueType,
|
|
Value: gvar.New(value),
|
|
Data: gvar.New(in.DataRaw),
|
|
Message: message,
|
|
Option: builtin.RunOption{
|
|
CaseInsensitive: hasCaseInsensitive,
|
|
},
|
|
})
|
|
|
|
default:
|
|
// It never comes across here.
|
|
}
|
|
|
|
// Error handling.
|
|
if err != nil {
|
|
// Error variable replacement for error message.
|
|
if errMsg := err.Error(); gstr.Contains(errMsg, "{") {
|
|
errMsg = gstr.ReplaceByMap(errMsg, map[string]string{
|
|
"{field}": in.Name, // Field name of the `value`.
|
|
"{value}": gconv.String(value), // Current validating value.
|
|
"{pattern}": rulePattern, // The variable part of the rule.
|
|
"{attribute}": in.Name, // The same as `{field}`. It is deprecated.
|
|
})
|
|
errMsg, _ = gregex.ReplaceString(`\s{2,}`, ` `, errMsg)
|
|
err = errors.New(errMsg)
|
|
}
|
|
// The error should have stack info to indicate the error position.
|
|
if !gerror.HasStack(err) {
|
|
err = gerror.NewCode(gcode.CodeValidationFailed, err.Error())
|
|
}
|
|
// The error should have error code that is `gcode.CodeValidationFailed`.
|
|
if gerror.Code(err) == gcode.CodeNil {
|
|
// TODO it's better using interface?
|
|
if e, ok := err.(*gerror.Error); ok {
|
|
e.SetCode(gcode.CodeValidationFailed)
|
|
}
|
|
}
|
|
ruleErrorMap[ruleKey] = err
|
|
|
|
// If it is with error and there's bail rule,
|
|
// it then does not continue validating for left rules.
|
|
if hasBailRule {
|
|
goto CheckDone
|
|
}
|
|
}
|
|
}
|
|
index++
|
|
}
|
|
|
|
CheckDone:
|
|
if len(ruleErrorMap) > 0 {
|
|
return newValidationError(
|
|
gcode.CodeValidationFailed,
|
|
[]fieldRule{{Name: in.Name, Rule: in.Rule}},
|
|
map[string]map[string]error{
|
|
in.Name: ruleErrorMap,
|
|
},
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type doCheckValueRecursivelyInput struct {
|
|
Value any // Value to be validated.
|
|
Type reflect.Type // Struct/map/slice type which to be recursively validated.
|
|
Kind reflect.Kind // Struct/map/slice kind to be asserted in following switch case.
|
|
ErrorMaps map[string]map[string]error // The validated failed error map.
|
|
ResultSequenceRules *[]fieldRule // The validated failed rule in sequence.
|
|
}
|
|
|
|
func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValueRecursivelyInput) {
|
|
switch in.Kind {
|
|
case reflect.Pointer:
|
|
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
|
Value: in.Value,
|
|
Type: in.Type.Elem(),
|
|
Kind: in.Type.Elem().Kind(),
|
|
ErrorMaps: in.ErrorMaps,
|
|
ResultSequenceRules: in.ResultSequenceRules,
|
|
})
|
|
|
|
case reflect.Struct:
|
|
// Ignore data, assoc, rules and messages from parent.
|
|
var (
|
|
validator = v.Clone()
|
|
toBeValidatedObject any
|
|
)
|
|
if in.Type.Kind() == reflect.Pointer {
|
|
toBeValidatedObject = reflect.New(in.Type.Elem()).Interface()
|
|
} else {
|
|
toBeValidatedObject = reflect.New(in.Type).Interface()
|
|
}
|
|
validator.assoc = nil
|
|
validator.rules = nil
|
|
validator.messages = nil
|
|
if err := validator.Data(toBeValidatedObject).Assoc(in.Value).Run(ctx); err != nil {
|
|
// It merges the errors into single error map.
|
|
for k, m := range err.(*validationError).errors {
|
|
in.ErrorMaps[k] = m
|
|
}
|
|
if in.ResultSequenceRules != nil {
|
|
*in.ResultSequenceRules = append(*in.ResultSequenceRules, err.(*validationError).rules...)
|
|
}
|
|
}
|
|
|
|
case reflect.Map:
|
|
var (
|
|
dataMap = gconv.Map(in.Value)
|
|
mapTypeElem = in.Type.Elem()
|
|
mapTypeKind = mapTypeElem.Kind()
|
|
)
|
|
for _, item := range dataMap {
|
|
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
|
Value: item,
|
|
Type: mapTypeElem,
|
|
Kind: mapTypeKind,
|
|
ErrorMaps: in.ErrorMaps,
|
|
ResultSequenceRules: in.ResultSequenceRules,
|
|
})
|
|
// Bail feature.
|
|
if v.bail && len(in.ErrorMaps) > 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
case reflect.Slice, reflect.Array:
|
|
loop := false
|
|
switch in.Type.Elem().Kind() {
|
|
// []struct []map
|
|
case reflect.Struct, reflect.Map:
|
|
loop = true
|
|
case reflect.Pointer:
|
|
loop = true
|
|
}
|
|
// When it is a base type array,
|
|
// there is no need for recursive loop validation,
|
|
// otherwise it will cause memory leakage
|
|
// https://github.com/gogf/gf/issues/4092
|
|
if loop {
|
|
var array []any
|
|
if gjson.Valid(in.Value) {
|
|
array = gconv.Interfaces(gconv.Bytes(in.Value))
|
|
} else {
|
|
array = gconv.Interfaces(in.Value)
|
|
}
|
|
if len(array) == 0 {
|
|
return
|
|
}
|
|
for _, item := range array {
|
|
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
|
Value: item,
|
|
Type: in.Type.Elem(),
|
|
Kind: in.Type.Elem().Kind(),
|
|
ErrorMaps: in.ErrorMaps,
|
|
ResultSequenceRules: in.ResultSequenceRules,
|
|
})
|
|
// Bail feature.
|
|
if v.bail && len(in.ErrorMaps) > 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|