This commit is contained in:
John Guo 2021-05-17 19:59:34 +08:00
parent 302e234bfe
commit 2274a10cfd
5 changed files with 105 additions and 25 deletions

View File

@ -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

View File

@ -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() {
}

View File

@ -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{

View File

@ -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

View File

@ -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",
},