recursive validation feature for package gvalid

This commit is contained in:
John Guo
2021-11-22 20:28:47 +08:00
parent 86a1721326
commit 3be19e58e1
6 changed files with 405 additions and 237 deletions

View File

@ -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)

View File

@ -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()
// ===========================================================

View File

@ -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
}

View File

@ -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
}
}
}
}

View File

@ -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"})
})
}

View 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"})
})
}