Skip to content

Commit

Permalink
feat: support validate array/slice items
Browse files Browse the repository at this point in the history
- close: #124
  • Loading branch information
inhere committed May 7, 2022
1 parent 4378d5f commit f48988b
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 50 deletions.
62 changes: 40 additions & 22 deletions issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
)

func TestIssue2(t *testing.T) {
func TestIssue_2(t *testing.T) {
type Fl struct {
A float64 `validate:"float"`
}
Expand Down Expand Up @@ -53,7 +53,7 @@ func TestIssue2(t *testing.T) {
}

// https://github.com/gookit/validate/issues/19
func TestIssues19(t *testing.T) {
func TestIssues_19(t *testing.T) {
is := assert.New(t)

// use tag name: country_code
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestIssues19(t *testing.T) {
}

// https://github.com/gookit/validate/issues/20
func TestIssues20(t *testing.T) {
func TestIssues_20(t *testing.T) {
is := assert.New(t)
type setProfileReq struct {
Nickname string `json:"nickname" validate:"string" filter:"trim"`
Expand Down Expand Up @@ -134,7 +134,7 @@ func TestIssues20(t *testing.T) {
}

// https://github.com/gookit/validate/issues/22
func TestIssues22(t *testing.T) {
func TestIssues_22(t *testing.T) {
type userInfo0 struct {
Nickname string `validate:"minLen:6" message:"OO! nickname min len is 6"`
Avatar string `validate:"maxLen:6" message:"OO! avatar max len is %d"`
Expand Down Expand Up @@ -173,7 +173,7 @@ func TestIssues22(t *testing.T) {
}

// https://github.com/gookit/validate/issues/30
func TestIssues30(t *testing.T) {
func TestIssues_30(t *testing.T) {
v := validate.JSON(`{
"cost_type": 10
}`)
Expand All @@ -185,7 +185,7 @@ func TestIssues30(t *testing.T) {
}

// https://github.com/gookit/validate/issues/34
func TestIssues34(t *testing.T) {
func TestIssues_34(t *testing.T) {
type STATUS int32
var s1 STATUS = 1

Expand All @@ -199,7 +199,6 @@ func TestIssues34(t *testing.T) {
v.StringRule("age", "required|checkAge:1,2,3,4")
assert.True(t, v.Validate())

// TODO refer https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
v = validate.New(validate.M{
"age": s1,
})
Expand All @@ -208,11 +207,10 @@ func TestIssues34(t *testing.T) {
})

assert.NotContains(t, []int{1, 2, 3, 4}, s1)

dump.Println(validate.Enum(s1, []int{1, 2, 3, 4}), validate.Enum(int32(s1), []int{1, 2, 3, 4}))

assert.True(t, v.Validate())
dump.Println(v.Errors)

// dump.Println(validate.Enum(s1, []int{1, 2, 3, 4}), validate.Enum(int32(s1), []int{1, 2, 3, 4}))
// dump.Println(v.Errors)

type someMode string
var m1 someMode = "abc"
Expand All @@ -223,7 +221,6 @@ func TestIssues34(t *testing.T) {
"mode": "required|in:abc,def",
})
assert.True(t, v.Validate())

}

type issues36Form struct {
Expand Down Expand Up @@ -264,7 +261,7 @@ func TestIssues36(t *testing.T) {
}

// https://github.com/gookit/validate/issues/60
func TestIssues60(t *testing.T) {
func TestIssues_60(t *testing.T) {
is := assert.New(t)
m := map[string]interface{}{
"title": "1",
Expand Down Expand Up @@ -420,7 +417,7 @@ func TestStructNested_gt2level(t *testing.T) {
}

// https://github.com/gookit/validate/issues/76
func TestIssue_76(t *testing.T) {
func TestIssues_76(t *testing.T) {
type CategoryReq struct {
Name string
}
Expand Down Expand Up @@ -519,7 +516,7 @@ func TestIssues_I3B3AV(t *testing.T) {
}

// https://github.com/gookit/validate/issues/92
func TestIssue_92(t *testing.T) {
func TestIssues_92(t *testing.T) {
m := map[string]interface{}{
"t": 1.1,
}
Expand All @@ -532,7 +529,7 @@ func TestIssue_92(t *testing.T) {
}

// https://github.com/gookit/validate/issues/98
func TestIssue_98(t *testing.T) {
func TestIssues_98(t *testing.T) {
// MenuActionResource 菜单动作关联资源对象
type MenuActionResource struct {
ID uint64 `json:"id"` // 唯一标识
Expand Down Expand Up @@ -589,7 +586,7 @@ func TestIssue_98(t *testing.T) {
}

// https://github.com/gookit/validate/issues/103
func TestIssue_103(t *testing.T) {
func TestIssues_103(t *testing.T) {
type Example struct {
SomeID string `validate:"required"`
}
Expand Down Expand Up @@ -643,7 +640,7 @@ func (d Issue104Demo) ConfigValidation(v *validate.Validation) {
}

// https://github.com/gookit/validate/issues/104
func TestIssue_104(t *testing.T) {
func TestIssues_104(t *testing.T) {
d := &Issue104Demo{
Issue104A: Issue104A{
ID: 0,
Expand Down Expand Up @@ -672,7 +669,7 @@ func TestIssue_104(t *testing.T) {
}

// https://github.com/gookit/validate/issues/107
func TestIssue_107(t *testing.T) {
func TestIssues_107(t *testing.T) {
taFilter := func(val interface{}) int64 {
if val != nil {
// log.WithFields(log.Fields{"value": val}).Info("value should be other than nil")
Expand Down Expand Up @@ -704,7 +701,7 @@ func TestIssue_107(t *testing.T) {
}

// https://github.com/gookit/validate/issues/111
func TestIssue_111(t *testing.T) {
func TestIssues_111(t *testing.T) {
v := validate.New(map[string]interface{}{
"username": "inhere",
"password": "h9i8tssx9153",
Expand All @@ -725,7 +722,7 @@ func TestIssue_111(t *testing.T) {
}

// https://github.com/gookit/validate/issues/120
func TestIssue_120(t *testing.T) {
func TestIssues_120(t *testing.T) {
type ThirdStruct struct {
Val string `json:"val" validate:"required"`
}
Expand All @@ -749,8 +746,29 @@ func TestIssue_120(t *testing.T) {
})

ok := v.Validate()
dump.Println(v.Errors)
assert.True(t, ok)
// dump.Println(v.Errors)
}

// https://github.com/gookit/validate/issues/124
func TestIssue_124(t *testing.T) {
m := map[string]interface{}{
"names": []string{"John", "Jane", "abc"},
}

v := validate.Map(m)
v.StringRule("names", "required|array|minLen:1")
v.StringRule("names.*", "required|string|min_len:4")

assert.False(t, v.Validate())
assert.Error(t, v.Errors.ErrOrNil())
assert.Equal(t, "names.* min length is 4", v.Errors.One())
// dump.Println(v.Errors)

// TODO how to use on struct.
// type user struct {
// Tags []string `json:"tags" validate:"required|slice"`
// }
}

// https://github.com/gookit/validate/issues/125
Expand Down
74 changes: 46 additions & 28 deletions validating.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,33 +224,12 @@ func (r *Rule) valueValidate(field, name string, val interface{}, v *Validation)
}
}

rftVal := reflect.ValueOf(val)
valKind := rftVal.Kind()

// want check sub element in a slice list. eg: field=names.*
if valKind == reflect.Slice && strings.HasSuffix(field, ".*") {

}

// some prepare and check.
argNum := len(r.arguments) + 1 // "+1" is the "val" position
// check arg num is match, need exclude "requiredXXX"
if r.nameNotRequired {
//noinspection GoNilness
fm.checkArgNum(argNum, r.validator)

//noinspection GoNilness
ft := fm.fv.Type()
// convert field value type, is first argument.
arg0Kind := ft.In(0).Kind()
if arg0Kind != valKind && arg0Kind != reflect.Interface {
val, ok = convValAsFuncArg0Type(arg0Kind, valKind, val)
if !ok {
//noinspection GoNilness
v.convArgTypeError(field, fm.name, valKind, arg0Kind, 0)
return false
}
}
}

// 1. args data type convert
Expand All @@ -259,7 +238,47 @@ func (r *Rule) valueValidate(field, name string, val interface{}, v *Validation)
return false
}

// 2. call built in validators
ft := fm.fv.Type()
arg0Kind := ft.In(0).Kind()

rftVal := reflect.ValueOf(val)
valKind := rftVal.Kind()

// feat: support check sub element in a slice list. eg: field=names.*
if valKind == reflect.Slice && strings.HasSuffix(field, ".*") {
var subVal interface{}
for i := 0; i < rftVal.Len(); i++ {
subRv := rftVal.Index(i)
subKind := subRv.Kind()
// 1.1 convert field value type, is func first argument.
if r.nameNotRequired && arg0Kind != reflect.Interface && arg0Kind != subKind {
subVal, ok = convValAsFuncArg0Type(arg0Kind, subKind, subRv.Interface())
if !ok {
v.convArgTypeError(field, fm.name, subKind, arg0Kind, 0)
return false
}
} else {
subVal = subRv.Interface()
}

// 2. call built in validator
if !callValidator(v, fm, field, subVal, r.arguments) {
return false
}
}
return true
}

// 1.1 convert field value type, is func first argument.
if r.nameNotRequired && arg0Kind != reflect.Interface && arg0Kind != valKind {
val, ok = convValAsFuncArg0Type(arg0Kind, valKind, val)
if !ok {
v.convArgTypeError(field, fm.name, valKind, arg0Kind, 0)
return false
}
}

// 2. call built in validator
return callValidator(v, fm, field, val, r.arguments)
}

Expand All @@ -268,13 +287,11 @@ func convValAsFuncArg0Type(arg0Kind, valKind reflect.Kind, val interface{}) (int
// ak, err := basicKind(rftVal)
bk, err := basicKindV2(valKind)
if err != nil {
//noinspection GoNilness
// v.convArgTypeError(field, fm.name, valKind, arg0Kind, 0)
return nil, false
}

// manual converted
if nVal, _ := convertType(val, bk, arg0Kind); nVal != nil {
if nVal, _ := convTypeByBaseKind(val, bk, arg0Kind); nVal != nil {
return nVal, true
}

Expand Down Expand Up @@ -403,7 +420,7 @@ func convertArgsType(v *Validation, fm *funcMeta, field string, args []interface
}

// manual converted
if nVal, _ := convertType(args[i], ak, lastTyp); nVal != nil {
if nVal, _ := convTypeByBaseKind(args[i], ak, lastTyp); nVal != nil {
args[i] = nVal
continue
}
Expand All @@ -428,9 +445,10 @@ func convertArgsType(v *Validation, fm *funcMeta, field string, args []interface
return
}

if av.Type().ConvertibleTo(argITyp) { // can auto convert type.
// can auto convert type.
if av.Type().ConvertibleTo(argITyp) {
args[i] = av.Convert(argITyp).Interface()
} else if nVal, _ := convertType(args[i], ak, wantTyp); nVal != nil { // manual converted
} else if nVal, _ := convTypeByBaseKind(args[i], ak, wantTyp); nVal != nil { // manual converted
args[i] = nVal
} else { // unable to convert
v.convArgTypeError(field, fm.name, av.Kind(), wantTyp, fcArgIndex)
Expand Down

0 comments on commit f48988b

Please sign in to comment.