diff --git a/internal/structs/structs_field.go b/internal/structs/structs_field.go index dda5a996e..81ae1d960 100644 --- a/internal/structs/structs_field.go +++ b/internal/structs/structs_field.go @@ -14,6 +14,16 @@ func (f *Field) Tag(key string) string { return f.Field.Tag.Get(key) } +// TagLookup returns the value associated with key in the tag string. +// If the key is present in the tag the value (which may be empty) +// is returned. Otherwise the returned value will be the empty string. +// The ok return value reports whether the value was explicitly set in +// the tag string. If the tag does not have the conventional format, +// the value returned by Lookup is unspecified. +func (f *Field) TagLookup(key string) (value string, ok bool) { + return f.Field.Tag.Lookup(key) +} + // IsEmbedded returns true if the given field is an anonymous field (embedded) func (f *Field) IsEmbedded() bool { return f.Field.Anonymous diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 871013ed4..d3a02594a 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -464,3 +464,8 @@ func (t *Time) UnmarshalText(data []byte) error { } return gerror.Newf(`invalid time value: %s`, data) } + +// NoValidation marks this struct object will not be validated by package gvalid. +func (t *Time) NoValidation() { + +} diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 4a660a954..2a2e1316a 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -64,17 +64,35 @@ import ( // like: map[field] => string|map[rule]string type CustomMsg = map[string]interface{} +// doCheckStructWithParamMapInput is used for struct validation for internal function. +type doCheckStructWithParamMapInput struct { + Object interface{} // Can be type of struct/*struct. + ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `UseParamMapInsteadOfObjectValue`. + UseParamMapInsteadOfObjectValue bool // Using `ParamMap` as its validation source instead of values from `Object`. + CustomRules interface{} // Custom validation rules. + CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. +} + +// apiNoValidation is an interface that marks current struct not validated by package `gvalid`. +type apiNoValidation interface { + NoValidation() +} + const ( // regular expression pattern for single validation rule. singleRulePattern = `^([\w-]+):{0,1}(.*)` invalidRulesErrKey = "invalid_rules" invalidParamsErrKey = "invalid_params" invalidObjectErrKey = "invalid_object" + + // no validation tag name for struct attribute. + noValidationTagName = "nv" ) var ( - // defaultValidator is the default validator for package functions. - defaultValidator = New() + defaultValidator = New() // defaultValidator is the default validator for package functions. + structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. + aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. // all internal error keys. internalErrKeyMap = map[string]string{ diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 6aab61a49..5ed23c45e 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -10,24 +10,9 @@ import ( "github.com/gogf/gf/internal/structs" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "reflect" "strings" ) -// doCheckStructWithParamMapInput is used for struct validation for internal function. -type doCheckStructWithParamMapInput struct { - Object interface{} // Can be type of struct/*struct. - ParamMap interface{} // Validation parameter map. Note that it acts different according attribute `UseParamMapInsteadOfObjectValue`. - UseParamMapInsteadOfObjectValue bool // Using `ParamMap` as its validation source instead of values from `Object`. - CustomRules interface{} // Custom validation rules. - CustomErrorMessageMap CustomMsg // Custom error message map for validation rules. -} - -var ( - structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. - aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. -) - // CheckStruct validates struct and returns the error result. // // The parameter `object` should be type of struct/*struct. @@ -70,16 +55,27 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) *Error { var ( - errorMaps = make(ErrorMap) // Returning error. + // Returning error. + errorMaps = make(ErrorMap) ) fieldMap, err := structs.FieldMap(input.Object, aliasNameTagPriority, true) if err != nil { return newErrorStr("invalid_object", err.Error()) } - // It checks the struct recursively the its attribute is also a struct. + // It checks the struct recursively the its attribute is an embedded struct. for _, field := range fieldMap { - if field.OriginalKind() == reflect.Struct { - if err := v.CheckStruct(field.Value, input.CustomRules, input.CustomErrorMessageMap); err != nil { + if field.IsEmbedded() { + // No validation interface implements check. + if _, ok := field.Value.Interface().(apiNoValidation); ok { + continue + } + if _, ok := field.TagLookup(noValidationTagName); ok { + continue + } + recursiveInput := doCheckStructWithParamMapInput{} + recursiveInput = *input + recursiveInput.Object = field.Value + if err := v.doCheckStructWithParamMap(&recursiveInput); err != nil { // It merges the errors into single error map. for k, m := range err.errors { errorMaps[k] = m diff --git a/util/gvalid/gvalid_z_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go index da76790fb..ac65bd8b4 100755 --- a/util/gvalid/gvalid_z_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -228,6 +228,57 @@ func Test_CheckStruct(t *testing.T) { }) } +func Test_CheckStruct_EmbeddedObject_Attribute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Base struct { + Time *gtime.Time + } + type Object struct { + Base + Name string + Type int + } + rules := map[string]string{ + "Name": "required", + "Type": "required", + } + ruleMsg := map[string]interface{}{ + "Name": "名称必填", + "Type": "类型必填", + } + obj := &Object{} + obj.Type = 1 + obj.Name = "john" + obj.Time = gtime.Now() + err := gvalid.CheckStruct(context.TODO(), obj, rules, ruleMsg) + t.Assert(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + type Base struct { + Name string + Type int + } + type Object struct { + Base Base + Name string + Type int + } + rules := map[string]string{ + "Name": "required", + "Type": "required", + } + ruleMsg := map[string]interface{}{ + "Name": "名称必填", + "Type": "类型必填", + } + obj := &Object{} + obj.Type = 1 + obj.Name = "john" + err := gvalid.CheckStruct(context.TODO(), obj, rules, ruleMsg) + t.Assert(err, nil) + }) +} + func Test_CheckStruct_With_EmbeddedObject(t *testing.T) { gtest.C(t, func(t *gtest.T) { type Pass struct { @@ -261,13 +312,13 @@ func Test_CheckStruct_With_StructAttribute(t *testing.T) { Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"` } type User struct { - Id int - Name string `valid:"name@required#请输入您的姓名"` - Passwords Pass + Pass + Id int + Name string `valid:"name@required#请输入您的姓名"` } user := &User{ Name: "", - Passwords: Pass{ + Pass: Pass{ Pass1: "1", Pass2: "2", },