mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
improve recursilve validation feature for package gvalid
This commit is contained in:
@ -9,6 +9,7 @@ package gconv
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
)
|
||||
|
||||
@ -62,9 +63,13 @@ func Interfaces(any interface{}) []interface{} {
|
||||
array[k] = v
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]interface{}, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = v
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]interface{}, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = v
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]interface{}, len(value))
|
||||
|
||||
@ -9,6 +9,7 @@ package gconv
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
)
|
||||
|
||||
@ -81,9 +82,13 @@ func Float32s(any interface{}) []float32 {
|
||||
array = append(array, Float32(v))
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]float32, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = Float32(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]float32, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = Float32(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]float32, len(value))
|
||||
@ -201,9 +206,13 @@ func Float64s(any interface{}) []float64 {
|
||||
array = append(array, Float64(v))
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]float64, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = Float64(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]float64, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = Float64(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]float64, len(value))
|
||||
|
||||
@ -9,6 +9,7 @@ package gconv
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
)
|
||||
|
||||
@ -69,9 +70,13 @@ func Ints(any interface{}) []int {
|
||||
array[k] = int(v)
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]int, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = int(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]int, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = int(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]int, len(value))
|
||||
@ -194,9 +199,13 @@ func Int32s(any interface{}) []int32 {
|
||||
array[k] = int32(v)
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]int32, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = int32(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]int32, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = int32(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]int32, len(value))
|
||||
@ -319,9 +328,13 @@ func Int64s(any interface{}) []int64 {
|
||||
array[k] = int64(v)
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]int64, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = int64(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]int64, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = int64(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]int64, len(value))
|
||||
|
||||
@ -9,6 +9,7 @@ package gconv
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
)
|
||||
|
||||
@ -57,9 +58,13 @@ func Strings(any interface{}) []string {
|
||||
array[k] = String(v)
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]string, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = String(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]string, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = String(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]string, len(value))
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
)
|
||||
@ -76,9 +77,13 @@ func Uints(any interface{}) []uint {
|
||||
case []uint:
|
||||
array = value
|
||||
case []uint8:
|
||||
array = make([]uint, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = uint(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]uint, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = uint(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]uint, len(value))
|
||||
@ -210,9 +215,13 @@ func Uint32s(any interface{}) []uint32 {
|
||||
array[k] = uint32(v)
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]uint32, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = uint32(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]uint32, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = uint32(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]uint32, len(value))
|
||||
@ -341,9 +350,13 @@ func Uint64s(any interface{}) []uint64 {
|
||||
array[k] = uint64(v)
|
||||
}
|
||||
case []uint8:
|
||||
array = make([]uint64, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = uint64(v)
|
||||
if json.Valid(value) {
|
||||
_ = json.UnmarshalUseNumber(value, &array)
|
||||
} else {
|
||||
array = make([]uint64, len(value))
|
||||
for k, v := range value {
|
||||
array[k] = uint64(v)
|
||||
}
|
||||
}
|
||||
case []uint16:
|
||||
array = make([]uint64, len(value))
|
||||
|
||||
@ -9,6 +9,7 @@ package gvalid
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@ -32,7 +33,7 @@ type Error interface {
|
||||
// validationError is the validation error for validation result.
|
||||
type validationError struct {
|
||||
code gcode.Code // Error code.
|
||||
rules []fieldRule // Rules by sequence, which is used for keeping error sequence.
|
||||
rules []fieldRule // Rules by sequence, which is used for keeping error sequence only.
|
||||
errors map[string]map[string]error // Error map:map[field]map[rule]message
|
||||
firstKey string // The first error rule key(empty in default).
|
||||
firstItem map[string]error // The first error rule value(nil in default).
|
||||
@ -52,6 +53,16 @@ func newValidationError(code gcode.Code, rules []fieldRule, fieldRuleErrorMap ma
|
||||
}
|
||||
fieldRuleErrorMap[field] = ruleErrorMap
|
||||
}
|
||||
// Filter repeated sequence rules.
|
||||
var ruleNameSet = gset.NewStrSet()
|
||||
for i := 0; i < len(rules); {
|
||||
if !ruleNameSet.AddIfNotExist(rules[i].Name) {
|
||||
// Delete repeated rule.
|
||||
rules = append(rules[:i], rules[i+1:]...)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
}
|
||||
return &validationError{
|
||||
code: code,
|
||||
rules: rules,
|
||||
|
||||
@ -19,15 +19,15 @@ import (
|
||||
|
||||
// Validator is the validation manager for chaining operations.
|
||||
type Validator struct {
|
||||
i18nManager *gi18n.Manager // I18n manager for error message translation.
|
||||
data interface{} // Validation data, which can be a map, struct or a certain value to be validated.
|
||||
assoc interface{} // Associated data, which is usually a map, for union validation.
|
||||
rules interface{} // Custom validation data.
|
||||
messages interface{} // Custom validation error messages, which can be string or type of CustomMsg.
|
||||
ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator.
|
||||
useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`.
|
||||
bail bool // Stop validation after the first validation error.
|
||||
caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
|
||||
i18nManager *gi18n.Manager // I18n manager for error message translation.
|
||||
data interface{} // Validation data, which can be a map, struct or a certain value to be validated.
|
||||
assoc interface{} // Associated data, which is usually a map, for union validation.
|
||||
rules interface{} // Custom validation data.
|
||||
messages interface{} // Custom validation error messages, which can be string or type of CustomMsg.
|
||||
ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator.
|
||||
useAssocInsteadOfObjectAttributes bool // Using `assoc` as its validation source instead of attribute values from `Object`.
|
||||
bail bool // Stop validation after the first validation error.
|
||||
caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
|
||||
}
|
||||
|
||||
// New creates and returns a new Validator.
|
||||
@ -125,14 +125,14 @@ func (v *Validator) Data(data interface{}) *Validator {
|
||||
|
||||
// Assoc is a chaining operation function, which sets associated validation data for current operation.
|
||||
// The optional parameter `assoc` is usually type of map, which specifies the parameter map used in union validation.
|
||||
// Calling this function with `assoc` also sets `useDataInsteadOfObjectAttributes` true
|
||||
// Calling this function with `assoc` also sets `useAssocInsteadOfObjectAttributes` true
|
||||
func (v *Validator) Assoc(assoc interface{}) *Validator {
|
||||
if assoc == nil {
|
||||
return v
|
||||
}
|
||||
newValidator := v.Clone()
|
||||
newValidator.assoc = assoc
|
||||
newValidator.useDataInsteadOfObjectAttributes = true
|
||||
newValidator.useAssocInsteadOfObjectAttributes = true
|
||||
return newValidator
|
||||
}
|
||||
|
||||
|
||||
@ -109,13 +109,13 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
return nil
|
||||
}
|
||||
// Input parameter map handling.
|
||||
if v.assoc == nil || !v.useDataInsteadOfObjectAttributes {
|
||||
if v.assoc == nil || !v.useAssocInsteadOfObjectAttributes {
|
||||
inputParamMap = make(map[string]interface{})
|
||||
} else {
|
||||
inputParamMap = gconv.Map(v.assoc)
|
||||
}
|
||||
// Checks and extends the parameters map with struct alias tag.
|
||||
if !v.useDataInsteadOfObjectAttributes {
|
||||
if !v.useAssocInsteadOfObjectAttributes {
|
||||
for nameOrTag, field := range fieldMap {
|
||||
inputParamMap[nameOrTag] = field.Value.Interface()
|
||||
if nameOrTag != field.Name() {
|
||||
@ -147,7 +147,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
// It here extends the params map using alias names.
|
||||
// Note that the variable `name` might be alias name or attribute name.
|
||||
if _, ok := inputParamMap[name]; !ok {
|
||||
if !v.useDataInsteadOfObjectAttributes {
|
||||
if !v.useAssocInsteadOfObjectAttributes {
|
||||
inputParamMap[name] = field.Value.Interface()
|
||||
} else {
|
||||
if name != fieldName {
|
||||
@ -226,7 +226,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
// Temporary variable for value.
|
||||
var value interface{}
|
||||
|
||||
// It checks the struct recursively if its attribute is an embedded struct.
|
||||
// It checks the struct recursively if its attribute is a struct/struct slice.
|
||||
for _, field := range fieldMap {
|
||||
// No validation interface implements check.
|
||||
if _, ok := field.Value.Interface().(iNoValidation); ok {
|
||||
|
||||
@ -15,10 +15,10 @@ import (
|
||||
"time"
|
||||
|
||||
"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/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/reflection"
|
||||
"github.com/gogf/gf/v2/net/gipv4"
|
||||
"github.com/gogf/gf/v2/net/gipv6"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
@ -538,11 +538,11 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, in doCheckBui
|
||||
}
|
||||
|
||||
type doCheckValueRecursivelyInput struct {
|
||||
Value interface{}
|
||||
Type reflect.Type
|
||||
OriginKind reflect.Kind
|
||||
ErrorMaps map[string]map[string]error
|
||||
ResultSequenceRules *[]fieldRule
|
||||
Value interface{} // Value to be validated.
|
||||
Type reflect.Type // Struct/map/slice type which to be recursively validated.
|
||||
OriginKind 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) {
|
||||
@ -563,13 +563,16 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
var dataMap = gconv.Map(in.Value)
|
||||
var (
|
||||
dataMap = gconv.Map(in.Value)
|
||||
mapTypeElem = in.Type.Elem()
|
||||
mapTypeKind = mapTypeElem.Kind()
|
||||
)
|
||||
for _, item := range dataMap {
|
||||
originTypeAndKind := reflection.OriginTypeAndKind(item)
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: item,
|
||||
Type: originTypeAndKind.InputType,
|
||||
OriginKind: originTypeAndKind.OriginKind,
|
||||
Type: mapTypeElem,
|
||||
OriginKind: mapTypeKind,
|
||||
ErrorMaps: in.ErrorMaps,
|
||||
ResultSequenceRules: in.ResultSequenceRules,
|
||||
})
|
||||
@ -580,16 +583,20 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
array := gconv.Interfaces(in.Value)
|
||||
var array []interface{}
|
||||
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 {
|
||||
originTypeAndKind := reflection.OriginTypeAndKind(item)
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: item,
|
||||
Type: originTypeAndKind.InputType,
|
||||
OriginKind: originTypeAndKind.OriginKind,
|
||||
Type: in.Type.Elem(),
|
||||
OriginKind: in.Type.Elem().Kind(),
|
||||
ErrorMaps: in.ErrorMaps,
|
||||
ResultSequenceRules: in.ResultSequenceRules,
|
||||
})
|
||||
|
||||
@ -404,6 +404,50 @@ func Test_CheckStruct_InvalidRule(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursively_SliceAttribute(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Student struct {
|
||||
Name string `v:"required#Student Name is required"`
|
||||
Age int `v:"required"`
|
||||
}
|
||||
type Teacher struct {
|
||||
Name string `v:"required#Teacher Name is required"`
|
||||
Students []Student `v:"required"`
|
||||
}
|
||||
var (
|
||||
teacher = Teacher{}
|
||||
data = g.Map{
|
||||
"name": "john",
|
||||
"students": `[{"age":2}, {"name":"jack", "age":4}]`,
|
||||
}
|
||||
)
|
||||
err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
|
||||
t.Assert(err, `Student Name is required`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursively_MapAttribute(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Student struct {
|
||||
Name string `v:"required#Student Name is required"`
|
||||
Age int `v:"required"`
|
||||
}
|
||||
type Teacher struct {
|
||||
Name string `v:"required#Teacher Name is required"`
|
||||
Students map[string]Student `v:"required"`
|
||||
}
|
||||
var (
|
||||
teacher = Teacher{}
|
||||
data = g.Map{
|
||||
"name": "john",
|
||||
"students": `{"john":{"age":18}}`,
|
||||
}
|
||||
)
|
||||
err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
|
||||
t.Assert(err, `Student Name is required`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidator_CheckStructWithData(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
|
||||
Reference in New Issue
Block a user