mirror of
https://gitee.com/johng/gf.git
synced 2024-11-29 18:57:44 +08:00
add recursive validation feature of struct attribute for package gvalid for #1165
This commit is contained in:
parent
0d4c1c47d5
commit
20f2a6c003
@ -6,6 +6,8 @@
|
||||
|
||||
package structs
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
@ -34,6 +36,24 @@ func (f *Field) Type() Type {
|
||||
}
|
||||
}
|
||||
|
||||
// Kind returns the reflect.Kind for Value of Field `f`.
|
||||
func (f *Field) Kind() reflect.Kind {
|
||||
return f.Value.Kind()
|
||||
}
|
||||
|
||||
// OriginalKind retrieves and returns the original reflect.Kind for Value of Field `f`.
|
||||
func (f *Field) OriginalKind() reflect.Kind {
|
||||
var (
|
||||
kind = f.Value.Kind()
|
||||
value = f.Value
|
||||
)
|
||||
for kind == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
kind = value.Kind()
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`.
|
||||
//
|
||||
// The parameter `pointer` should be type of struct/*struct.
|
||||
|
@ -341,7 +341,7 @@ func compareMap(value, expect interface{}) error {
|
||||
return fmt.Errorf(`[ASSERT] EXPECT MAP LENGTH %d == %d`, rvValue.Len(), rvExpect.Len())
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP`)
|
||||
return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP, BUT GIVEN "%s"`, rvValue.Kind())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -9,6 +9,7 @@ package gvalid
|
||||
import (
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -17,13 +18,31 @@ var (
|
||||
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
|
||||
)
|
||||
|
||||
// CheckStruct validates strcut and returns the error result.
|
||||
// CheckStruct validates struct and returns the error result.
|
||||
//
|
||||
// The parameter <object> should be type of struct/*struct.
|
||||
// The parameter <rules> can be type of []string/map[string]string. It supports sequence in error result
|
||||
// if <rules> is type of []string.
|
||||
// The optional parameter <messages> specifies the custom error messages for specified keys and rules.
|
||||
func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error {
|
||||
var (
|
||||
errorMaps = make(ErrorMap) // Returned error.
|
||||
)
|
||||
mapField, err := structs.FieldMap(object, aliasNameTagPriority)
|
||||
if err != nil {
|
||||
return newErrorStr("invalid_object", err.Error())
|
||||
}
|
||||
// It checks the struct recursively the its attribute is also a struct.
|
||||
for _, field := range mapField {
|
||||
if field.OriginalKind() == reflect.Struct {
|
||||
if err := v.CheckStruct(field.Value, rules, messages...); err != nil {
|
||||
// It merges the errors into single error map.
|
||||
for k, m := range err.errors {
|
||||
errorMaps[k] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// It here must use structs.TagFields not structs.FieldMap to ensure error sequence.
|
||||
tagField, err := structs.TagFields(object, structTagPriority)
|
||||
if err != nil {
|
||||
@ -39,7 +58,6 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
|
||||
customMessage = make(CustomMsg)
|
||||
fieldAliases = make(map[string]string) // Alias names for <messages> overwriting struct tag names.
|
||||
errorRules = make([]string, 0) // Sequence rules.
|
||||
errorMaps = make(ErrorMap) // Returned error
|
||||
)
|
||||
switch v := rules.(type) {
|
||||
// Sequence tag: []sequence tag
|
||||
@ -85,10 +103,6 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
|
||||
return nil
|
||||
}
|
||||
// Checks and extends the parameters map with struct alias tag.
|
||||
mapField, err := structs.FieldMap(object, aliasNameTagPriority)
|
||||
if err != nil {
|
||||
return newErrorStr("invalid_object", err.Error())
|
||||
}
|
||||
for nameOrTag, field := range mapField {
|
||||
params[nameOrTag] = field.Value.Interface()
|
||||
params[field.Name()] = field.Value.Interface()
|
||||
@ -167,10 +181,10 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
|
||||
// It checks each rule and its value in loop.
|
||||
if e := v.doCheck(key, value, rule, customMessage[key], params); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===========================================================
|
||||
// Only in map and struct validations, if value is nil or empty
|
||||
// string and has no required* rules, it clears the error message.
|
||||
// ===========================================================
|
||||
// ===================================================================
|
||||
// Only in map and struct validations, if value is nil or empty string
|
||||
// and has no required* rules, it clears the error message.
|
||||
// ===================================================================
|
||||
if value == nil || gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
|
@ -226,7 +226,7 @@ func Test_CheckStruct(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_With_EmbedObject(t *testing.T) {
|
||||
func Test_CheckStruct_With_EmbeddedObject(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
|
||||
@ -252,6 +252,32 @@ func Test_CheckStruct_With_EmbedObject(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_With_StructAttribute(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Pass struct {
|
||||
Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
|
||||
Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"`
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `valid:"name@required#请输入您的姓名"`
|
||||
Passwords Pass
|
||||
}
|
||||
user := &User{
|
||||
Name: "",
|
||||
Passwords: Pass{
|
||||
Pass1: "1",
|
||||
Pass2: "2",
|
||||
},
|
||||
}
|
||||
err := gvalid.CheckStruct(user, nil)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"})
|
||||
t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"})
|
||||
t.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckStruct_Optional(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
|
Loading…
Reference in New Issue
Block a user