mirror of
https://gitee.com/johng/gf
synced 2026-07-07 06:15:15 +08:00
recursive validation feature for package gvalid
This commit is contained in:
@ -22,6 +22,11 @@ type iString interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// iError is used for type assert api for Error().
|
||||
type iError interface {
|
||||
Error() string
|
||||
}
|
||||
|
||||
// iMarshalJSON is the interface for custom Json marshaling.
|
||||
type iMarshalJSON interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
@ -231,6 +236,7 @@ func doDumpMap(in doDumpInternalInput) {
|
||||
} else {
|
||||
mapKeyStr = fmt.Sprintf(`%v`, mapKey.Interface())
|
||||
}
|
||||
// Map key and indent string dump.
|
||||
if !in.Option.WithType {
|
||||
in.Buffer.WriteString(fmt.Sprintf(
|
||||
"%s%v:%s",
|
||||
@ -247,6 +253,7 @@ func doDumpMap(in doDumpInternalInput) {
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
}
|
||||
// Map value dump.
|
||||
doDump(in.ReflectValue.MapIndex(mapKey).Interface(), in.NewIndent, in.Buffer, in.Option)
|
||||
in.Buffer.WriteString(",\n")
|
||||
}
|
||||
@ -265,6 +272,8 @@ func doDumpStruct(in doDumpInternalInput) {
|
||||
)
|
||||
if v, ok := in.Value.(iString); ok {
|
||||
structContentStr = v.String()
|
||||
} else if v, ok := in.Value.(iError); ok {
|
||||
structContentStr = v.Error()
|
||||
} else if v, ok := in.Value.(iMarshalJSON); ok {
|
||||
b, _ := v.MarshalJSON()
|
||||
structContentStr = string(b)
|
||||
|
||||
@ -9,9 +9,11 @@ package gvalid
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@ -22,8 +24,7 @@ func (v *Validator) CheckMap(ctx context.Context, params interface{}) Error {
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
// If there's no validation rules, it does nothing and returns quickly.
|
||||
if params == nil || v.rules == nil {
|
||||
if params == nil {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
@ -76,12 +77,8 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
})
|
||||
}
|
||||
}
|
||||
// If there's no validation rules, it does nothing and returns quickly.
|
||||
if len(checkRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
data := gconv.Map(params)
|
||||
if data == nil {
|
||||
inputParamMap := gconv.Map(params)
|
||||
if inputParamMap == nil {
|
||||
return newValidationErrorByStr(
|
||||
internalParamsErrRuleName,
|
||||
errors.New("invalid params type: convert to map failed"),
|
||||
@ -97,14 +94,41 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
}
|
||||
}
|
||||
var (
|
||||
value interface{}
|
||||
value interface{}
|
||||
validator = v.Clone()
|
||||
)
|
||||
|
||||
// It checks the struct recursively if its attribute is an embedded struct.
|
||||
// Ignore inputParamMap, rules and messages from parent.
|
||||
validator.rules = nil
|
||||
validator.messages = nil
|
||||
for _, item := range inputParamMap {
|
||||
originTypeAndKind := utils.OriginTypeAndKind(item)
|
||||
switch originTypeAndKind.OriginKind {
|
||||
case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array:
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: item,
|
||||
Type: originTypeAndKind.InputType,
|
||||
OriginKind: originTypeAndKind.OriginKind,
|
||||
ErrorMaps: errorMaps,
|
||||
})
|
||||
}
|
||||
// Bail feature.
|
||||
if v.bail && len(errorMaps) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if v.bail && len(errorMaps) > 0 {
|
||||
return newValidationError(gcode.CodeValidationFailed, nil, errorMaps)
|
||||
}
|
||||
|
||||
// The following logic is the same as some of CheckStruct but without sequence support.
|
||||
for _, checkRuleItem := range checkRules {
|
||||
if len(checkRuleItem.Rule) == 0 {
|
||||
continue
|
||||
}
|
||||
value = nil
|
||||
if valueItem, ok := data[checkRuleItem.Name]; ok {
|
||||
if valueItem, ok := inputParamMap[checkRuleItem.Name]; ok {
|
||||
value = valueItem
|
||||
}
|
||||
// It checks each rule and its value in loop.
|
||||
@ -114,7 +138,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
||||
Rule: checkRuleItem.Rule,
|
||||
Messages: customMessage[checkRuleItem.Name],
|
||||
DataRaw: params,
|
||||
DataMap: data,
|
||||
DataMap: inputParamMap,
|
||||
}); validatedError != nil {
|
||||
_, errorItem := validatedError.FirstItem()
|
||||
// ===========================================================
|
||||
|
||||
@ -8,13 +8,13 @@ package gvalid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/internal/structs"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/internal/structs"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
// CheckStruct validates struct and returns the error result.
|
||||
@ -23,80 +23,6 @@ func (v *Validator) CheckStruct(ctx context.Context, object interface{}) Error {
|
||||
return v.doCheckStruct(ctx, object)
|
||||
}
|
||||
|
||||
type doCheckAttributeRecursivelyInput struct {
|
||||
Field *structs.Field
|
||||
ErrorMaps map[string]map[string]error
|
||||
ResultSequenceRules []fieldRule
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckAttributeRecursively(ctx context.Context, in doCheckAttributeRecursivelyInput) {
|
||||
switch in.Field.OriginalKind() {
|
||||
case reflect.Struct:
|
||||
var (
|
||||
dataValue interface{}
|
||||
fieldValue = in.Field.Value.Interface()
|
||||
)
|
||||
if v.data != nil {
|
||||
dataMap := gconv.Map(v.data)
|
||||
if value, ok := dataMap[in.Field.TagValue]; ok {
|
||||
dataValue = value
|
||||
}
|
||||
if dataValue == nil {
|
||||
if value, ok := dataMap[in.Field.Name()]; ok {
|
||||
dataValue = value
|
||||
}
|
||||
}
|
||||
}
|
||||
// No validation interface implements check.
|
||||
if _, ok := fieldValue.(iNoValidation); ok {
|
||||
return
|
||||
}
|
||||
// No validation field tag check.
|
||||
if _, ok := in.Field.TagLookup(noValidationTagName); ok {
|
||||
return
|
||||
}
|
||||
// Ignore rules and messages from parent.
|
||||
validator := v.Clone()
|
||||
validator.rules = nil
|
||||
validator.messages = nil
|
||||
if err := validator.Data(dataValue).doCheckStruct(ctx, fieldValue); err != nil {
|
||||
// It merges the errors into single error map.
|
||||
for k, m := range err.(*validationError).errors {
|
||||
in.ErrorMaps[k] = m
|
||||
if rules := err.(*validationError).rules; len(rules) > 0 {
|
||||
in.ResultSequenceRules = append(in.ResultSequenceRules, rules...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
var (
|
||||
dataValue interface{}
|
||||
dataArray = gconv.Interfaces(v.data)
|
||||
sliceObjects = make([]interface{}, 0)
|
||||
)
|
||||
if in.Field.Value.Len() == 0 {
|
||||
sliceObjects = append(sliceObjects, reflect.New(in.Field.Value.Elem().Type()))
|
||||
} else {
|
||||
for i := 0; i < in.Field.Value.Len(); i++ {
|
||||
sliceObjects = append(sliceObjects, in.Field.Value.Index(i).Interface())
|
||||
}
|
||||
}
|
||||
for index, obj := range sliceObjects {
|
||||
dataValue = nil
|
||||
if index < len(dataArray) {
|
||||
dataValue = dataArray[index]
|
||||
}
|
||||
originValueAndKind := utils.OriginValueAndKind(obj)
|
||||
switch originValueAndKind.OriginKind {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error {
|
||||
var (
|
||||
errorMaps = make(map[string]map[string]error) // Returning error.
|
||||
@ -111,42 +37,14 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
if err != nil {
|
||||
return newValidationErrorByStr(internalObjectErrRuleName, err)
|
||||
}
|
||||
// It checks the struct recursively if its attribute is an embedded struct.
|
||||
for _, field := range fieldMap {
|
||||
if field.IsEmbedded() {
|
||||
// No validation interface implements check.
|
||||
if _, ok := field.Value.Interface().(iNoValidation); ok {
|
||||
continue
|
||||
}
|
||||
// No validation field tag check.
|
||||
if _, ok := field.TagLookup(noValidationTagName); ok {
|
||||
continue
|
||||
}
|
||||
if err = v.doCheckStruct(ctx, field.Value); err != nil {
|
||||
// It merges the errors into single error map.
|
||||
for k, m := range err.(*validationError).errors {
|
||||
errorMaps[k] = m
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if field.TagValue != "" {
|
||||
fieldToAliasNameMap[field.Name()] = field.TagValue
|
||||
}
|
||||
// Recursively check attribute struct/[]string/map/[]map.
|
||||
v.doCheckAttributeRecursively(ctx, doCheckAttributeRecursivelyInput{
|
||||
Field: field,
|
||||
ErrorMaps: errorMaps,
|
||||
ResultSequenceRules: resultSequenceRules,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// It here must use structs.TagFields not structs.FieldMap to ensure error sequence.
|
||||
tagField, err := structs.TagFields(object, structTagPriority)
|
||||
tagFields, err := structs.TagFields(object, structTagPriority)
|
||||
if err != nil {
|
||||
return newValidationErrorByStr(internalObjectErrRuleName, err)
|
||||
}
|
||||
// If there's no struct tag and validation rules, it does nothing and returns quickly.
|
||||
if len(tagField) == 0 && v.messages == nil {
|
||||
if len(tagFields) == 0 && v.messages == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -174,7 +72,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
msgArray = strings.Split(msg, "|")
|
||||
ruleArray = strings.Split(rule, "|")
|
||||
)
|
||||
for k, v := range ruleArray {
|
||||
for k, ruleKey := range ruleArray {
|
||||
// If length of custom messages is lesser than length of rules,
|
||||
// the rest rules use the default error messages.
|
||||
if len(msgArray) <= k {
|
||||
@ -183,7 +81,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(v, ":")
|
||||
array := strings.Split(ruleKey, ":")
|
||||
if _, ok := customMessage[name]; !ok {
|
||||
customMessage[name] = make(map[string]string)
|
||||
}
|
||||
@ -209,7 +107,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
}
|
||||
}
|
||||
// If there's no struct tag and validation rules, it does nothing and returns quickly.
|
||||
if len(tagField) == 0 && len(checkRules) == 0 {
|
||||
if len(tagFields) == 0 && len(checkRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Input parameter map handling.
|
||||
@ -230,15 +128,15 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
|
||||
// Merge the custom validation rules with rules in struct tag.
|
||||
// The custom rules has the most high priority that can overwrite the struct tag rules.
|
||||
for _, field := range tagField {
|
||||
for _, field := range tagFields {
|
||||
var (
|
||||
fieldName = field.Name() // Attribute name.
|
||||
name, rule, msg = parseSequenceTag(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only.
|
||||
)
|
||||
if len(name) == 0 {
|
||||
if v, ok := fieldToAliasNameMap[fieldName]; ok {
|
||||
if value, ok := fieldToAliasNameMap[fieldName]; ok {
|
||||
// It uses alias name of the attribute if its alias name tag exists.
|
||||
name = v
|
||||
name = value
|
||||
} else {
|
||||
// It or else uses the attribute name directly.
|
||||
name = fieldName
|
||||
@ -291,7 +189,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
msgArray = strings.Split(msg, "|")
|
||||
ruleArray = strings.Split(rule, "|")
|
||||
)
|
||||
for k, v := range ruleArray {
|
||||
for k, ruleKey := range ruleArray {
|
||||
// If length of custom messages is lesser than length of rules,
|
||||
// the rest rules use the default error messages.
|
||||
if len(msgArray) <= k {
|
||||
@ -300,7 +198,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
if len(msgArray[k]) == 0 {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(v, ":")
|
||||
array := strings.Split(ruleKey, ":")
|
||||
if _, ok := customMessage[name]; !ok {
|
||||
customMessage[name] = make(map[string]string)
|
||||
}
|
||||
@ -312,22 +210,71 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
// Custom error messages,
|
||||
// which have the most priority than `rules` and struct tag.
|
||||
if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 {
|
||||
for k, v := range msg {
|
||||
for k, msgName := range msg {
|
||||
if a, ok := fieldToAliasNameMap[k]; ok {
|
||||
// Overwrite the key of field name.
|
||||
customMessage[a] = v
|
||||
customMessage[a] = msgName
|
||||
} else {
|
||||
customMessage[k] = v
|
||||
customMessage[k] = msgName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The following logic is the same as some of CheckMap but with sequence support.
|
||||
// Temporary variable for value.
|
||||
var (
|
||||
value interface{}
|
||||
)
|
||||
|
||||
// It checks the struct recursively if its attribute is an embedded struct.
|
||||
for _, field := range fieldMap {
|
||||
// No validation interface implements check.
|
||||
if _, ok := field.Value.Interface().(iNoValidation); ok {
|
||||
continue
|
||||
}
|
||||
// No validation field tag check.
|
||||
if _, ok := field.TagLookup(noValidationTagName); ok {
|
||||
continue
|
||||
}
|
||||
if field.IsEmbedded() {
|
||||
if err = v.doCheckStruct(ctx, field.Value); err != nil {
|
||||
// It merges the errors into single error map.
|
||||
for k, m := range err.(*validationError).errors {
|
||||
errorMaps[k] = m
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if field.TagValue != "" {
|
||||
fieldToAliasNameMap[field.Name()] = field.TagValue
|
||||
}
|
||||
switch field.OriginalKind() {
|
||||
case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array:
|
||||
// Recursively check attribute struct/[]string/map/[]map.
|
||||
_, value = gutil.MapPossibleItemByKey(inputParamMap, field.Name())
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: value,
|
||||
OriginKind: field.OriginalKind(),
|
||||
Type: field.Type().Type,
|
||||
ErrorMaps: errorMaps,
|
||||
ResultSequenceRules: &resultSequenceRules,
|
||||
})
|
||||
}
|
||||
}
|
||||
if v.bail && len(errorMaps) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if v.bail && len(errorMaps) > 0 {
|
||||
return newValidationError(gcode.CodeValidationFailed, resultSequenceRules, errorMaps)
|
||||
}
|
||||
|
||||
// The following logic is the same as some of CheckMap but with sequence support.
|
||||
for _, checkRuleItem := range checkRules {
|
||||
_, value = gutil.MapPossibleItemByKey(inputParamMap, checkRuleItem.Name)
|
||||
if value == nil {
|
||||
if aliasName := fieldToAliasNameMap[checkRuleItem.Name]; aliasName != "" {
|
||||
_, value = gutil.MapPossibleItemByKey(inputParamMap, aliasName)
|
||||
}
|
||||
}
|
||||
// It checks each rule and its value in loop.
|
||||
if validatedError := v.doCheckValue(ctx, doCheckValueInput{
|
||||
Name: checkRuleItem.Name,
|
||||
@ -363,6 +310,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
|
||||
for ruleKey, errorItemMsgMap := range errorItem {
|
||||
errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap
|
||||
}
|
||||
// Bail feature.
|
||||
if v.bail {
|
||||
break
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ package gvalid
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -17,6 +18,7 @@ import (
|
||||
"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/utils"
|
||||
"github.com/gogf/gf/v2/net/gipv4"
|
||||
"github.com/gogf/gf/v2/net/gipv6"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
@ -48,7 +50,7 @@ type doCheckValueInput struct {
|
||||
Name string // Name specifies the name of parameter `value`.
|
||||
Value interface{} // Value specifies the value for the rules to be validated.
|
||||
Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc.
|
||||
Messages interface{} // Messages specifies the custom error messages for this rule, which is usually type of map/slice.
|
||||
Messages interface{} // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice.
|
||||
DataRaw interface{} // 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]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally
|
||||
}
|
||||
@ -72,6 +74,7 @@ func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Erro
|
||||
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)
|
||||
@ -544,3 +547,73 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, in doCheckBui
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
||||
type doCheckValueRecursivelyInput struct {
|
||||
Value interface{}
|
||||
Type reflect.Type
|
||||
OriginKind reflect.Kind
|
||||
ErrorMaps map[string]map[string]error
|
||||
ResultSequenceRules *[]fieldRule
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValueRecursivelyInput) {
|
||||
switch in.OriginKind {
|
||||
case reflect.Struct:
|
||||
// Ignore data, rules and messages from parent.
|
||||
validator := v.Clone()
|
||||
validator.rules = nil
|
||||
validator.messages = nil
|
||||
if err := validator.Data(in.Value).doCheckStruct(ctx, reflect.New(in.Type).Interface()); 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)
|
||||
validator = v.Clone()
|
||||
)
|
||||
// Ignore data, rules and messages from parent.
|
||||
validator.rules = nil
|
||||
validator.messages = nil
|
||||
for _, item := range dataMap {
|
||||
originTypeAndKind := utils.OriginTypeAndKind(item)
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: item,
|
||||
Type: originTypeAndKind.InputType,
|
||||
OriginKind: originTypeAndKind.OriginKind,
|
||||
ErrorMaps: in.ErrorMaps,
|
||||
ResultSequenceRules: in.ResultSequenceRules,
|
||||
})
|
||||
// Bail feature.
|
||||
if v.bail && len(in.ErrorMaps) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
array := gconv.Interfaces(in.Value)
|
||||
if len(array) == 0 {
|
||||
return
|
||||
}
|
||||
for _, item := range array {
|
||||
originTypeAndKind := utils.OriginTypeAndKind(item)
|
||||
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
|
||||
Value: item,
|
||||
Type: originTypeAndKind.InputType,
|
||||
OriginKind: originTypeAndKind.OriginKind,
|
||||
ErrorMaps: in.ErrorMaps,
|
||||
ResultSequenceRules: in.ResultSequenceRules,
|
||||
})
|
||||
// Bail feature.
|
||||
if v.bail && len(in.ErrorMaps) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CheckStruct_Recursive_Struct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Pass Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Pass: Pass{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(context.TODO(), user, nil)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], g.Map{"required": "The Name field is required"})
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `1` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `2` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_Struct_WithData(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Pass Pass
|
||||
}
|
||||
user := &User{}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Pass": g.Map{
|
||||
"Pass1": 100,
|
||||
"Pass2": 200,
|
||||
},
|
||||
}
|
||||
err := g.Validator().Data(data).CheckStruct(context.TODO(), user)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], nil)
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `100` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `200` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_SliceStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Passes []Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Passes: []Pass{
|
||||
{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
{
|
||||
Pass1: "3",
|
||||
Pass2: "4",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(context.TODO(), user, nil)
|
||||
g.Dump(err.Items())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], g.Map{"required": "The Name field is required"})
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `1` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `2` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
217
util/gvalid/gvalid_z_unit_feature_recursive_test.go
Executable file
217
util/gvalid/gvalid_z_unit_feature_recursive_test.go
Executable file
@ -0,0 +1,217 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CheckStruct_Recursive_Struct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Pass Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Pass: Pass{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(ctx, user, nil)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], g.Map{"required": "The Name field is required"})
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `1` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `2` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_Struct_WithData(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Pass Pass
|
||||
}
|
||||
user := &User{}
|
||||
data := g.Map{
|
||||
"Name": "john",
|
||||
"Pass": g.Map{
|
||||
"Pass1": 100,
|
||||
"Pass2": 200,
|
||||
},
|
||||
}
|
||||
err := g.Validator().Data(data).CheckStruct(ctx, user)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], nil)
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `100` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `200` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_SliceStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Passes []Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Passes: []Pass{
|
||||
{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
{
|
||||
Pass1: "3",
|
||||
Pass2: "4",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(ctx, user, nil)
|
||||
g.Dump(err.Items())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], g.Map{"required": "The Name field is required"})
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `3` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `4` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_SliceStruct_Bail(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Passes []Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Passes: []Pass{
|
||||
{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
{
|
||||
Pass1: "3",
|
||||
Pass2: "4",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := g.Validator().Bail().CheckStruct(ctx, user)
|
||||
g.Dump(err.Items())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], nil)
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `1` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_SliceStruct_Required(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Passes []Pass
|
||||
}
|
||||
user := &User{}
|
||||
err := gvalid.CheckStruct(ctx, user, nil)
|
||||
g.Dump(err.Items())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], g.Map{"required": "The Name field is required"})
|
||||
t.Assert(err.Maps()["Pass1"], nil)
|
||||
t.Assert(err.Maps()["Pass2"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Recursive_MapStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required"`
|
||||
Passes map[string]Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Passes: map[string]Pass{
|
||||
"test1": {
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
"test2": {
|
||||
Pass1: "3",
|
||||
Pass2: "4",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(ctx, user, nil)
|
||||
g.Dump(err.Items())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], g.Map{"required": "The Name field is required"})
|
||||
t.AssertNE(err.Maps()["Pass1"], nil)
|
||||
t.AssertNE(err.Maps()["Pass2"], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckMap_Recursive_SliceStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `v:"required|same:Pass2"`
|
||||
Pass2 string `v:"required|same:Pass1"`
|
||||
}
|
||||
user := g.Map{
|
||||
"Name": "",
|
||||
"Pass": []Pass{
|
||||
{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
{
|
||||
Pass1: "3",
|
||||
Pass2: "4",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckMap(ctx, user, nil)
|
||||
g.Dump(err.Items())
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["Name"], nil)
|
||||
t.Assert(err.Maps()["Pass1"], g.Map{"same": "The Pass1 value `3` must be the same as field Pass2"})
|
||||
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `4` must be the same as field Pass1"})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user