mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 03:07:45 +08:00
improve struct validation for package gvalid
This commit is contained in:
parent
034a3f1808
commit
a4240bdfb7
@ -9,11 +9,19 @@ package gvalid
|
||||
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 `ParamMapStrict`.
|
||||
ParamMapStrict bool // Strictly using `ParamMap` as its validation source. If false, it ignores `ParamMap` and retrieves 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.
|
||||
@ -25,8 +33,18 @@ var (
|
||||
// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result
|
||||
// if `rules` is type of []string.
|
||||
// The optional parameter `messages` specifies the custom error messages for specified keys and rules.
|
||||
func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error {
|
||||
return v.CheckStructWithParamMap(object, nil, rules, messages...)
|
||||
func (v *Validator) CheckStruct(object interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) *Error {
|
||||
var message CustomMsg
|
||||
if len(customErrorMessageMap) > 0 {
|
||||
message = customErrorMessageMap[0]
|
||||
}
|
||||
return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{
|
||||
Object: object,
|
||||
ParamMap: nil,
|
||||
ParamMapStrict: false,
|
||||
CustomRules: customRules,
|
||||
CustomErrorMessageMap: message,
|
||||
})
|
||||
}
|
||||
|
||||
// CheckStructWithParamMap validates struct with given parameter map and returns the error result.
|
||||
@ -35,18 +53,32 @@ func (v *Validator) CheckStruct(object interface{}, rules interface{}, messages
|
||||
// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result
|
||||
// if `rules` is type of []string.
|
||||
// The optional parameter `messages` specifies the custom error messages for specified keys and rules.
|
||||
func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, rules interface{}, messages ...CustomMsg) *Error {
|
||||
func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interface{}, customRules interface{}, customErrorMessageMap ...CustomMsg) *Error {
|
||||
var message CustomMsg
|
||||
if len(customErrorMessageMap) > 0 {
|
||||
message = customErrorMessageMap[0]
|
||||
}
|
||||
return v.doCheckStructWithParamMap(&doCheckStructWithParamMapInput{
|
||||
Object: object,
|
||||
ParamMap: paramMap,
|
||||
ParamMapStrict: true,
|
||||
CustomRules: customRules,
|
||||
CustomErrorMessageMap: message,
|
||||
})
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckStructWithParamMap(input *doCheckStructWithParamMapInput) *Error {
|
||||
var (
|
||||
errorMaps = make(ErrorMap) // Returning error.
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(object, aliasNameTagPriority)
|
||||
fieldMap, err := structs.FieldMap(input.Object, aliasNameTagPriority)
|
||||
if err != nil {
|
||||
return newErrorStr("invalid_object", err.Error())
|
||||
}
|
||||
// It checks the struct recursively the its attribute is also a struct.
|
||||
for _, field := range fieldMap {
|
||||
if field.OriginalKind() == reflect.Struct {
|
||||
if err := v.CheckStruct(field.Value, rules, messages...); err != nil {
|
||||
if err := v.CheckStruct(field.Value, input.CustomRules, input.CustomErrorMessageMap); err != nil {
|
||||
// It merges the errors into single error map.
|
||||
for k, m := range err.errors {
|
||||
errorMaps[k] = m
|
||||
@ -55,12 +87,12 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa
|
||||
}
|
||||
}
|
||||
// It here must use structs.TagFields not structs.FieldMap to ensure error sequence.
|
||||
tagField, err := structs.TagFields(object, structTagPriority)
|
||||
tagField, err := structs.TagFields(input.Object, structTagPriority)
|
||||
if err != nil {
|
||||
return newErrorStr("invalid_object", err.Error())
|
||||
}
|
||||
// If there's no struct tag and validation rules, it does nothing and returns quickly.
|
||||
if len(tagField) == 0 && rules == nil {
|
||||
if len(tagField) == 0 && input.CustomRules == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -71,12 +103,7 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa
|
||||
fieldAliases = make(map[string]string) // Alias names for `messages` overwriting struct tag names.
|
||||
errorRules = make([]string, 0) // Sequence rules.
|
||||
)
|
||||
if paramMap == nil {
|
||||
inputParamMap = make(map[string]interface{})
|
||||
} else {
|
||||
inputParamMap = gconv.Map(paramMap)
|
||||
}
|
||||
switch v := rules.(type) {
|
||||
switch v := input.CustomRules.(type) {
|
||||
// Sequence tag: []sequence tag
|
||||
// Sequence has order for error results.
|
||||
case []string:
|
||||
@ -119,21 +146,22 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa
|
||||
if len(tagField) == 0 && len(checkRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Input parameter map handling.
|
||||
if input.ParamMap == nil || !input.ParamMapStrict {
|
||||
inputParamMap = make(map[string]interface{})
|
||||
} else {
|
||||
inputParamMap = gconv.Map(input.ParamMap)
|
||||
}
|
||||
// Checks and extends the parameters map with struct alias tag.
|
||||
for nameOrTag, field := range fieldMap {
|
||||
if _, ok := inputParamMap[nameOrTag]; !ok {
|
||||
if foundKey, foundValue := gutil.MapPossibleItemByKey(inputParamMap, nameOrTag); foundKey != "" {
|
||||
inputParamMap[nameOrTag] = foundValue
|
||||
inputParamMap[field.Name()] = foundValue
|
||||
} else {
|
||||
// If the custom tag alias name is quite different from the attribute name,
|
||||
// it just uses the value of the attribute.
|
||||
// Note that the attribute may have default value according to its type.
|
||||
inputParamMap[nameOrTag] = field.Value.Interface()
|
||||
if !input.ParamMapStrict {
|
||||
for nameOrTag, field := range fieldMap {
|
||||
inputParamMap[nameOrTag] = field.Value.Interface()
|
||||
if nameOrTag != field.Name() {
|
||||
inputParamMap[field.Name()] = field.Value.Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range tagField {
|
||||
fieldName := field.Name()
|
||||
// sequence tag == struct tag
|
||||
@ -187,8 +215,8 @@ func (v *Validator) CheckStructWithParamMap(object interface{}, paramMap interfa
|
||||
|
||||
// Custom error messages,
|
||||
// which have the most priority than `rules` and struct tag.
|
||||
if len(messages) > 0 && len(messages[0]) > 0 {
|
||||
for k, v := range messages[0] {
|
||||
if len(input.CustomErrorMessageMap) > 0 {
|
||||
for k, v := range input.CustomErrorMessageMap {
|
||||
if a, ok := fieldAliases[k]; ok {
|
||||
// Overwrite the key of field name.
|
||||
customMessage[a] = v
|
||||
|
@ -8,6 +8,7 @@ package gvalid_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
@ -352,3 +353,51 @@ func Test_CheckStruct_InvalidRule(t *testing.T) {
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidator_CheckStructWithParamMap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
Uid int64 `json:"uid" v:"required"`
|
||||
Nickname string `json:"nickname" v:"required-with:Uid"`
|
||||
}
|
||||
data := UserApiSearch{}
|
||||
t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
Uid int64 `json:"uid" v:"required"`
|
||||
Nickname string `json:"nickname" v:"required-with:Uid"`
|
||||
}
|
||||
data := UserApiSearch{
|
||||
Uid: 1,
|
||||
}
|
||||
t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
Uid int64 `json:"uid"`
|
||||
Nickname string `json:"nickname" v:"required-with:Uid"`
|
||||
StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
|
||||
EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"`
|
||||
}
|
||||
data := UserApiSearch{
|
||||
StartTime: nil,
|
||||
EndTime: nil,
|
||||
}
|
||||
t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
Uid int64 `json:"uid"`
|
||||
Nickname string `json:"nickname" v:"required-with:Uid"`
|
||||
StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
|
||||
EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"`
|
||||
}
|
||||
data := UserApiSearch{
|
||||
StartTime: gtime.Now(),
|
||||
EndTime: nil,
|
||||
}
|
||||
t.AssertNE(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil)
|
||||
})
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gvalid_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidator_CheckStructWithParamMap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
Uid int64 `json:"uid" v:"required"`
|
||||
Nickname string `json:"nickname" v:"required-with:Uid"`
|
||||
}
|
||||
data := UserApiSearch{}
|
||||
t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type UserApiSearch struct {
|
||||
Uid int64 `json:"uid"`
|
||||
Nickname string `json:"nickname" v:"required-with:Uid"`
|
||||
StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
|
||||
EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"`
|
||||
}
|
||||
data := UserApiSearch{
|
||||
StartTime: nil,
|
||||
EndTime: nil,
|
||||
}
|
||||
t.Assert(gvalid.CheckStructWithParamMap(data, g.Map{}, nil), nil)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user