Skip to content

Commit

Permalink
✅ up: update requiredX validate logic, add more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Jan 9, 2024
1 parent b24adf8 commit 050296c
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 73 deletions.
6 changes: 3 additions & 3 deletions _examples/httpdemo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type UserForm struct {
Code string
}

func main() {
func main() {
mux := http.NewServeMux()

handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -36,7 +36,7 @@ func main() {
v.AddRule("name", "required")
v.AddRule("name", "minLen", 7)
v.AddRule("age", "max", 99)
v.AddRule("code", `required|regex:\d{4,6}`)
v.AddRule("code", "regex", `\d{4,6}`)

if v.Validate() { // validate ok
// safeData := v.SafeData()
Expand All @@ -46,7 +46,7 @@ func main() {
// do something ...
fmt.Println(userForm.Name)
} else {
fmt.Println(v.Errors) // all error messages
fmt.Println(v.Errors) // all error messages
fmt.Println(v.Errors.One()) // returns a random error message text
}
})
Expand Down
25 changes: 11 additions & 14 deletions data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ type StructData struct {
src any
// max depth for parse sub-struct. TODO WIP ...
// depth int
// from reflect source Struct

// reflect.Value of the source struct
value reflect.Value
// source struct reflect.Type
valueTpy reflect.Type
// reflect.Type of the source struct
valueTyp reflect.Type
// field names in the src struct
// 0:common field 1:anonymous field 2:nonAnonymous field
fieldNames map[string]int8
Expand Down Expand Up @@ -254,21 +255,21 @@ func (d *StructData) Create(err ...error) *Validation {
d.parseRulesFromTag(v)

// has custom config func
if d.valueTpy.Implements(cvFaceType) {
if d.valueTyp.Implements(cvFaceType) {
fv := d.value.MethodByName("ConfigValidation")
fv.Call([]reflect.Value{reflect.ValueOf(v)})
}

// collect custom field translates config
if d.valueTpy.Implements(ftFaceType) {
if d.valueTyp.Implements(ftFaceType) {
fv := d.value.MethodByName("Translates")
vs := fv.Call(nil)
v.WithTranslates(vs[0].Interface().(map[string]string))
}

// collect custom error messages config
// if reflect.PtrTo(d.valueTpy).Implements(cmFaceType) {
if d.valueTpy.Implements(cmFaceType) {
// if reflect.PtrTo(d.valueTyp).Implements(cmFaceType) {
if d.valueTyp.Implements(cmFaceType) {
fv := d.value.MethodByName("Messages")
vs := fv.Call(nil)
v.WithMessages(vs[0].Interface().(map[string]string))
Expand All @@ -293,7 +294,7 @@ func (d *StructData) parseRulesFromTag(v *Validation) {
var recursiveFunc func(vv reflect.Value, vt reflect.Type, preStrName string, parentIsAnonymous bool)

vv := d.value
vt := d.valueTpy
vt := d.valueTyp
// preStrName - the parent field name.
recursiveFunc = func(vv reflect.Value, vt reflect.Type, parentFName string, parentIsAnonymous bool) {
for i := 0; i < vt.NumField(); i++ {
Expand Down Expand Up @@ -520,7 +521,7 @@ func (d *StructData) TryGet(field string) (val any, exist, zero bool) {
// want to get sub struct field.
if strings.IndexByte(field, '.') > 0 {
fieldNodes := strings.Split(field, ".")
topLevelField, ok := d.valueTpy.FieldByName(fieldNodes[0])
topLevelField, ok := d.valueTyp.FieldByName(fieldNodes[0])
if !ok {
return
}
Expand Down Expand Up @@ -569,10 +570,6 @@ func (d *StructData) TryGet(field string) (val any, exist, zero bool) {
if fv.Kind() == reflect.Ptr && fv.IsNil() {
return
}

// if i < lastIndex && fv.Type().Kind() != reflect.Struct {
// return nil, false
// }
}

d.fieldNames[field] = fieldAtSubStruct
Expand Down Expand Up @@ -705,7 +702,7 @@ func (d *StructData) HasField(field string) bool {
}

// has field, cache it
if _, ok := d.valueTpy.FieldByName(field); ok {
if _, ok := d.valueTyp.FieldByName(field); ok {
d.fieldNames[field] = fieldAtTopStruct
return true
}
Expand Down
30 changes: 23 additions & 7 deletions issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ func TestIssues_76(t *testing.T) {
ok := v.Validate()
assert.False(t, ok)
dump.Println(v.Errors)
assert.Equal(t, "categories is required when is_private is [false]", v.Errors.One())
assert.Equal(t, "categories is required when is_private is in [false]", v.Errors.One())

v = validate.Struct(&Issue76{Categories: []*CategoryReq{
{Name: "book"},
Expand Down Expand Up @@ -970,7 +970,7 @@ func TestIssue_140(t *testing.T) {
err := v.ValidateE()
dump.Println(err)
assert.Error(t, err)
assert.Equal(t, "Field2 is required when Field1 is [value]", err.One())
assert.Equal(t, "Field2 is required when Field1 is in [value]", err.One())

test.Field2 = "hi"
v = validate.Struct(test)
Expand Down Expand Up @@ -1120,8 +1120,6 @@ func TestIssue_152(t *testing.T) {
validate.SetBuiltinMessages(old)
}()

zhcn.RegisterGlobal()

// test required if
type requiredIf struct {
Type int64 `validate:"required" label:"类型"`
Expand All @@ -1132,9 +1130,9 @@ func TestIssue_152(t *testing.T) {
Type: 1,
Data: "",
})
zhcn.Register(v)

v.Validate()

assert.False(t, v.Validate())
assert.Equal(t, `当 类型 为 [1] 时 数据 不能为空。`, v.Errors.One())

// test required unless
Expand All @@ -1147,8 +1145,9 @@ func TestIssue_152(t *testing.T) {
Type: 0,
Data: "",
})
zhcn.Register(v)

v.Validate()
assert.False(t, v.Validate())
assert.Equal(t, `当 类型 不为 [1] 时 数据 不能为空。`, v.Errors.One())
}

Expand Down Expand Up @@ -1480,6 +1479,23 @@ func TestIssues_223(t *testing.T) {
assert.StrContains(t, s, "clinics.*.doctors.*.duration value must be an integer")
}

// https://github.com/gookit/validate/issues/242
// requiredWithoutAll tag is not work with struct validation
func TestIssues_242(t *testing.T) {
type RequiredWithoutAll struct {
ID string `validate:"requiredWithoutAll:NewID|uuid4"`
NewID string `validate:"requiredWithoutAll:OldID|uuid4"`
OldID string `validate:"requiredWithoutAll:NewID|string"`
}

h := RequiredWithoutAll{}
v := validate.Struct(h)
assert.False(t, v.Validate())
s := v.Errors.String()
fmt.Println(v.Errors)
assert.StrContains(t, s, "requiredWithoutAll: ID field is required when none of [NewID] are present")
}

// https://github.com/gookit/validate/issues/245
// required_if 中,当指定的 anotherField 为 uint8 类型时,校验失效
func TestIssues_245(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ var builtinMessages = map[string]string{
"gt": "{field} value should be greater than %v",
// required
"required": "{field} is required to not be empty",
"requiredIf": "{field} is required when {args0} is {args1end}",
"requiredIf": "{field} is required when {args0} is in {args1end}",
"requiredUnless": "{field} field is required unless {args0} is in {args1end}",
"requiredWith": "{field} field is required when {values} is present",
"requiredWithAll": "{field} field is required when {values} is present",
Expand Down
6 changes: 6 additions & 0 deletions rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func (r *Rule) errorMessage(field, validator string, v *Validation) (msg string)
// v.StringRule("name", "required|string|minLen:6")
// // will try convert to int before applying validation.
// v.StringRule("age", "required|int|min:12", "toInt")
// v.StringRule("email", "required|min_len:6", "trim|email|lower")
func (v *Validation) StringRule(field, rule string, filterRule ...string) *Validation {
rule = strings.TrimSpace(rule)
if rule == "" {
Expand Down Expand Up @@ -243,6 +244,11 @@ func (v *Validation) ConfigRules(mp MS) *Validation {
}

// AddRule for current validation
//
// Usage:
//
// v.AddRule("name", "required")
// v.AddRule("name", "min_len", "2")
func (v *Validation) AddRule(fields, validator string, args ...any) *Rule {
return v.addOneRule(fields, validator, ValidatorName(validator), args)
}
Expand Down
2 changes: 1 addition & 1 deletion validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ func FromStruct(s any) (*StructData, error) {

data.src = s
data.value = val
data.valueTpy = typ
data.valueTyp = typ

return data, nil
}
Expand Down
105 changes: 97 additions & 8 deletions validating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func TestValidation_CheckDefault(t *testing.T) {
}

func TestValidation_RequiredIf(t *testing.T) {
// test map data
v := New(M{
"name": "lee",
"age": "12",
Expand All @@ -85,10 +86,24 @@ func TestValidation_RequiredIf(t *testing.T) {
"nothing": "required_if:age,12,13,14",
})

v.Validate()
assert.Equal(t, "nothing is required when age is [12,13,14]", v.Errors.One())
assert.False(t, v.RequiredIf("age", "12"))
assert.False(t, v.Validate())
assert.Equal(t, "nothing is required when age is in [12,13,14]", v.Errors.One())

// test struct data
type user struct {
Name string `validate:"required"`
Age int `validate:"required_if:Name,lee"`
}

u := &user{Name: "lee"}
v = New(u)
e := v.ValidateErr()
assert.Err(t, e)
assert.StrContains(t, e.Error(), "Age is required when Name is in [lee]")
}

// 验证的字段必须存在且不为空,除非目标字段等于给定的任何值。
func TestValidation_RequiredUnless(t *testing.T) {
v := New(M{
"age": "18",
Expand All @@ -100,11 +115,25 @@ func TestValidation_RequiredUnless(t *testing.T) {
"nothing": "required_unless:age,12,13,14",
})

v.Validate()
assert.False(t, v.RequiredUnless("age", "12"))
assert.False(t, v.Validate())
assert.Equal(t, "nothing field is required unless age is in [12,13,14]", v.Errors.One())

// test struct data
type user struct {
Name string `validate:"required"`
Age int `validate:"required_unless:Name,tom"`
}

u := &user{Name: "lee"}
v = New(u)
assert.False(t, v.Validate())
assert.Equal(t, "Age field is required unless Name is in [tom]", v.Errors.One())
}

// 仅当任何其他指定字段存在时,验证下的字段才必须存在且不为空。
func TestValidation_RequiredWith(t *testing.T) {
// test map data
v := New(M{
"age": "18",
"name": "test",
Expand All @@ -115,10 +144,24 @@ func TestValidation_RequiredWith(t *testing.T) {
"nothing": "required_with:age,name",
})

v.Validate()
assert.False(t, v.RequiredWith("age", "12"))
assert.False(t, v.Validate())
assert.Equal(t, "nothing field is required when [age,name] is present", v.Errors.One())

// test struct data
type user struct {
Name string `validate:"requiredWith:Age,City"`
Age int `validate:"required_with:Name,City"`
City string `validate:"required_with:Name,Age"`
}

u := &user{Name: "lee"}
v = New(u)
assert.False(t, v.Validate())
assert.Equal(t, "Age field is required when [Name,City] is present", v.Errors.One())
}

// 仅当所有其他指定字段都存在时,验证下的字段才必须存在且不为空。
func TestValidation_RequiredWithAll(t *testing.T) {
v := New(M{
"age": "18",
Expand All @@ -132,11 +175,27 @@ func TestValidation_RequiredWithAll(t *testing.T) {
"nothing": "required_with_all:age,name,sex",
})

v.Validate()
// fmt.Println(v.Errors)
assert.False(t, v.RequiredWithAll("age", "12"))
assert.False(t, v.Validate())
assert.Equal(t, "nothing field is required when [age,name,sex] is present", v.Errors.One())

// test struct data
type user struct {
Age int
Sex string
Name string `validate:"requiredWithAll:age,sex"`
}

u := &user{Age: 23}
v = New(u)
assert.True(t, v.Validate())
u.Sex = "man"
v = New(u)
assert.False(t, v.Validate())
assert.Equal(t, "Name field is required when [age,sex] is present", v.Errors.One())
}

// 仅当任何一个其他指定字段不存在时,验证中的字段才必须存在且不为空。
func TestValidation_RequiredWithout(t *testing.T) {
v := New(M{
"age": "18",
Expand All @@ -148,10 +207,24 @@ func TestValidation_RequiredWithout(t *testing.T) {
"nothing": "required_without:sex,name",
})

v.Validate()
assert.False(t, v.RequiredWithout("age", "12"))
assert.False(t, v.Validate())
assert.Equal(t, "nothing field is required when [sex,name] is not present", v.Errors.One())

// test struct data
type user struct {
Name string `validate:"requiredWithout:Age,City"`
Age int `validate:"required_without:Name,City"`
City string
}

u := &user{Name: "lee"}
v = New(u)
assert.False(t, v.Validate())
assert.Equal(t, "Age field is required when [Name,City] is not present", v.Errors.One())
}

// 仅当任何其他指定字段都不存在时,验证中的字段才必须存在且不为空。
func TestValidation_RequiredWithoutAll(t *testing.T) {
v := New(M{
"age": "18",
Expand All @@ -163,8 +236,24 @@ func TestValidation_RequiredWithoutAll(t *testing.T) {
"nothing": "required_without_all:sex,city",
})

v.Validate()
assert.False(t, v.RequiredWithoutAll("age", "12"))
assert.False(t, v.Validate())
assert.Equal(t, "nothing field is required when none of [sex,city] are present", v.Errors.One())

// test struct data
type user struct {
Name string `validate:"requiredWithoutAll:Age,City"`
Age int
City string
}

u := &user{Name: "lee"}
v = New(u)
assert.True(t, v.Validate())
u.Name = ""
v = New(u)
assert.False(t, v.Validate())
assert.Equal(t, "Name field is required when none of [Age,City] are present", v.Errors.One())
}

func TestVariadicArgs(t *testing.T) {
Expand Down
4 changes: 3 additions & 1 deletion validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ func (v *Validation) RawVal(key string) any {

// try to get value by key.
//
// If v.data is StructData, will return zero check
// **NOTE:**
//
// If v.data is StructData, will return zero value check. Other dataSource will always return `zero=False`.
func (v *Validation) tryGet(key string) (val any, exist, zero bool) {
if v.data == nil {
return
Expand Down
Loading

0 comments on commit 050296c

Please sign in to comment.