Skip to content

Commit 20f2a6c

Browse files
committed
add recursive validation feature of struct attribute for package gvalid for gogf#1165
1 parent 0d4c1c4 commit 20f2a6c

File tree

4 files changed

+72
-12
lines changed

4 files changed

+72
-12
lines changed

internal/structs/structs_field.go

+20
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
package structs
88

9+
import "reflect"
10+
911
// Tag returns the value associated with key in the tag string. If there is no
1012
// such key in the tag, Tag returns the empty string.
1113
func (f *Field) Tag(key string) string {
@@ -34,6 +36,24 @@ func (f *Field) Type() Type {
3436
}
3537
}
3638

39+
// Kind returns the reflect.Kind for Value of Field `f`.
40+
func (f *Field) Kind() reflect.Kind {
41+
return f.Value.Kind()
42+
}
43+
44+
// OriginalKind retrieves and returns the original reflect.Kind for Value of Field `f`.
45+
func (f *Field) OriginalKind() reflect.Kind {
46+
var (
47+
kind = f.Value.Kind()
48+
value = f.Value
49+
)
50+
for kind == reflect.Ptr {
51+
value = value.Elem()
52+
kind = value.Kind()
53+
}
54+
return kind
55+
}
56+
3757
// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`.
3858
//
3959
// The parameter `pointer` should be type of struct/*struct.

test/gtest/gtest_util.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ func compareMap(value, expect interface{}) error {
341341
return fmt.Errorf(`[ASSERT] EXPECT MAP LENGTH %d == %d`, rvValue.Len(), rvExpect.Len())
342342
}
343343
} else {
344-
return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP`)
344+
return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP, BUT GIVEN "%s"`, rvValue.Kind())
345345
}
346346
}
347347
return nil

util/gvalid/gvalid_validator_check_struct.go

+24-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package gvalid
99
import (
1010
"github.com/gogf/gf/internal/structs"
1111
"github.com/gogf/gf/util/gconv"
12+
"reflect"
1213
"strings"
1314
)
1415

@@ -17,13 +18,31 @@ var (
1718
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
1819
)
1920

20-
// CheckStruct validates strcut and returns the error result.
21+
// CheckStruct validates struct and returns the error result.
2122
//
2223
// The parameter <object> should be type of struct/*struct.
2324
// The parameter <rules> can be type of []string/map[string]string. It supports sequence in error result
2425
// if <rules> is type of []string.
2526
// The optional parameter <messages> specifies the custom error messages for specified keys and rules.
2627
func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error {
28+
var (
29+
errorMaps = make(ErrorMap) // Returned error.
30+
)
31+
mapField, err := structs.FieldMap(object, aliasNameTagPriority)
32+
if err != nil {
33+
return newErrorStr("invalid_object", err.Error())
34+
}
35+
// It checks the struct recursively the its attribute is also a struct.
36+
for _, field := range mapField {
37+
if field.OriginalKind() == reflect.Struct {
38+
if err := v.CheckStruct(field.Value, rules, messages...); err != nil {
39+
// It merges the errors into single error map.
40+
for k, m := range err.errors {
41+
errorMaps[k] = m
42+
}
43+
}
44+
}
45+
}
2746
// It here must use structs.TagFields not structs.FieldMap to ensure error sequence.
2847
tagField, err := structs.TagFields(object, structTagPriority)
2948
if err != nil {
@@ -39,7 +58,6 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
3958
customMessage = make(CustomMsg)
4059
fieldAliases = make(map[string]string) // Alias names for <messages> overwriting struct tag names.
4160
errorRules = make([]string, 0) // Sequence rules.
42-
errorMaps = make(ErrorMap) // Returned error
4361
)
4462
switch v := rules.(type) {
4563
// Sequence tag: []sequence tag
@@ -85,10 +103,6 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
85103
return nil
86104
}
87105
// Checks and extends the parameters map with struct alias tag.
88-
mapField, err := structs.FieldMap(object, aliasNameTagPriority)
89-
if err != nil {
90-
return newErrorStr("invalid_object", err.Error())
91-
}
92106
for nameOrTag, field := range mapField {
93107
params[nameOrTag] = field.Value.Interface()
94108
params[field.Name()] = field.Value.Interface()
@@ -167,10 +181,10 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
167181
// It checks each rule and its value in loop.
168182
if e := v.doCheck(key, value, rule, customMessage[key], params); e != nil {
169183
_, item := e.FirstItem()
170-
// ===========================================================
171-
// Only in map and struct validations, if value is nil or empty
172-
// string and has no required* rules, it clears the error message.
173-
// ===========================================================
184+
// ===================================================================
185+
// Only in map and struct validations, if value is nil or empty string
186+
// and has no required* rules, it clears the error message.
187+
// ===================================================================
174188
if value == nil || gconv.String(value) == "" {
175189
required := false
176190
// rule => error

util/gvalid/gvalid_z_unit_checkstruct_test.go

+27-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ func Test_CheckStruct(t *testing.T) {
226226
})
227227
}
228228

229-
func Test_CheckStruct_With_EmbedObject(t *testing.T) {
229+
func Test_CheckStruct_With_EmbeddedObject(t *testing.T) {
230230
gtest.C(t, func(t *gtest.T) {
231231
type Pass struct {
232232
Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
@@ -252,6 +252,32 @@ func Test_CheckStruct_With_EmbedObject(t *testing.T) {
252252
})
253253
}
254254

255+
func Test_CheckStruct_With_StructAttribute(t *testing.T) {
256+
gtest.C(t, func(t *gtest.T) {
257+
type Pass struct {
258+
Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"`
259+
Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"`
260+
}
261+
type User struct {
262+
Id int
263+
Name string `valid:"name@required#请输入您的姓名"`
264+
Passwords Pass
265+
}
266+
user := &User{
267+
Name: "",
268+
Passwords: Pass{
269+
Pass1: "1",
270+
Pass2: "2",
271+
},
272+
}
273+
err := gvalid.CheckStruct(user, nil)
274+
t.AssertNE(err, nil)
275+
t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"})
276+
t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"})
277+
t.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"})
278+
})
279+
}
280+
255281
func Test_CheckStruct_Optional(t *testing.T) {
256282
gtest.C(t, func(t *gtest.T) {
257283
type Params struct {

0 commit comments

Comments
 (0)