Skip to content

Commit 13130d2

Browse files
authored
Fixed missing keys from returned errors in map validation (#1284)
## Fixes Or Enhances ### Bug Description: The `ValidateMapCtx` function calls the `VarCtx(ctx context.Context, field interface{}, tag string) (err error)` method, but the returned errors do not contain keys. ```go package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() data := map[string]interface{}{"email": "emailaddress"} rules := map[string]interface{}{"email": "required,email"} errs := validate.ValidateMap(data, rules) // when VarCtx is called fmt.Println(errs) //output: map[email:Key: '' Error:Field validation for '' failed on the 'email' tag] } ``` ### Fix: Added a new method `VarWithKeyCtx(ctx context.Context, key string, field interface{}, tag string) (err error)` to support validating a single variable, ensuring that the returned error contains the key of the field. This retains compatibility with the current `VarCtx(...)` method. Now, `ValidateMapCtx` will call the new `VarWithKeyCtx(...)` method. ```go package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() data := map[string]interface{}{"email": "emailaddress"} rules := map[string]interface{}{"email": "required,email"} errs := validate.ValidateMap(data, rules) // when the new VarWithKeyCtx is called fmt.Println(errs) //output: map[email:Key: 'email' Error:Field validation for 'email' failed on the 'email' tag] } ``` **Make sure that you've checked the boxes below before you submit PR:** - [x] Tests exist or have been written that cover this particular change. @go-playground/validator-maintainers
1 parent 94e89f0 commit 13130d2

File tree

2 files changed

+153
-1
lines changed

2 files changed

+153
-1
lines changed

validator_instance.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{
181181
errs[field] = errors.New("The field: '" + field + "' is not a map to dive")
182182
}
183183
} else if ruleStr, ok := rule.(string); ok {
184-
err := v.VarCtx(ctx, data[field], ruleStr)
184+
err := v.VarWithKeyCtx(ctx, field, data[field], ruleStr)
185185
if err != nil {
186186
errs[field] = err
187187
}
@@ -681,6 +681,64 @@ func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other
681681
return
682682
}
683683

684+
// VarWithKey validates a single variable with a key to be included in the returned error using tag style validation
685+
// eg.
686+
// var s string
687+
// validate.VarWithKey("email_address", s, "required,email")
688+
//
689+
// WARNING: a struct can be passed for validation eg. time.Time is a struct or
690+
// if you have a custom type and have registered a custom type handler, so must
691+
// allow it; however unforeseen validations will occur if trying to validate a
692+
// struct that is meant to be passed to 'validate.Struct'
693+
//
694+
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
695+
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
696+
// validate Array, Slice and maps fields which may contain more than one error
697+
func (v *Validate) VarWithKey(key string, field interface{}, tag string) error {
698+
return v.VarWithKeyCtx(context.Background(), key, field, tag)
699+
}
700+
701+
// VarWithKeyCtx validates a single variable with a key to be included in the returned error using tag style validation
702+
// and allows passing of contextual validation information via context.Context.
703+
// eg.
704+
// var s string
705+
// validate.VarWithKeyCtx("email_address", s, "required,email")
706+
//
707+
// WARNING: a struct can be passed for validation eg. time.Time is a struct or
708+
// if you have a custom type and have registered a custom type handler, so must
709+
// allow it; however unforeseen validations will occur if trying to validate a
710+
// struct that is meant to be passed to 'validate.Struct'
711+
//
712+
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
713+
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
714+
// validate Array, Slice and maps fields which may contain more than one error
715+
func (v *Validate) VarWithKeyCtx(ctx context.Context, key string, field interface{}, tag string) (err error) {
716+
if len(tag) == 0 || tag == skipValidationTag {
717+
return nil
718+
}
719+
720+
ctag := v.fetchCacheTag(tag)
721+
722+
cField := &cField{
723+
name: key,
724+
altName: key,
725+
namesEqual: true,
726+
}
727+
728+
val := reflect.ValueOf(field)
729+
vd := v.pool.Get().(*validate)
730+
vd.top = val
731+
vd.isPartial = false
732+
vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], cField, ctag)
733+
734+
if len(vd.errs) > 0 {
735+
err = vd.errs
736+
vd.errs = nil
737+
}
738+
v.pool.Put(vd)
739+
return
740+
}
741+
684742
func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilCheckable bool) error {
685743
if len(tag) == 0 {
686744
return errors.New("function Key cannot be empty")

validator_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13508,6 +13508,100 @@ func TestValidate_ValidateMapCtx(t *testing.T) {
1350813508
}
1350913509
}
1351013510

13511+
func TestValidate_ValidateMapCtxWithKeys(t *testing.T) {
13512+
type args struct {
13513+
data map[string]interface{}
13514+
rules map[string]interface{}
13515+
errors map[string]interface{}
13516+
}
13517+
tests := []struct {
13518+
name string
13519+
args args
13520+
want int
13521+
}{
13522+
{
13523+
name: "test invalid email",
13524+
args: args{
13525+
data: map[string]interface{}{
13526+
"email": "emailaddress",
13527+
},
13528+
rules: map[string]interface{}{
13529+
"email": "required,email",
13530+
},
13531+
errors: map[string]interface{}{
13532+
"email": "Key: 'email' Error:Field validation for 'email' failed on the 'email' tag",
13533+
},
13534+
},
13535+
want: 1,
13536+
},
13537+
{
13538+
name: "test multiple errors with capitalized keys",
13539+
args: args{
13540+
data: map[string]interface{}{
13541+
"Email": "emailaddress",
13542+
"Age": 15,
13543+
},
13544+
rules: map[string]interface{}{
13545+
"Email": "required,email",
13546+
"Age": "number,gt=16",
13547+
},
13548+
errors: map[string]interface{}{
13549+
"Email": "Key: 'Email' Error:Field validation for 'Email' failed on the 'email' tag",
13550+
"Age": "Key: 'Age' Error:Field validation for 'Age' failed on the 'gt' tag",
13551+
},
13552+
},
13553+
want: 2,
13554+
},
13555+
{
13556+
name: "test valid map data",
13557+
args: args{
13558+
data: map[string]interface{}{
13559+
"email": "[email protected]",
13560+
"age": 17,
13561+
},
13562+
rules: map[string]interface{}{
13563+
"email": "required,email",
13564+
"age": "number,gt=16",
13565+
},
13566+
errors: map[string]interface{}{},
13567+
},
13568+
want: 0,
13569+
},
13570+
}
13571+
13572+
for _, tt := range tests {
13573+
t.Run(tt.name, func(t *testing.T) {
13574+
validate := New()
13575+
errs := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules)
13576+
NotEqual(t, errs, nil)
13577+
Equal(t, len(errs), tt.want)
13578+
for key, err := range errs {
13579+
Equal(t, err.(ValidationErrors)[0].Error(), tt.args.errors[key])
13580+
}
13581+
})
13582+
}
13583+
}
13584+
13585+
func TestValidate_VarWithKey(t *testing.T) {
13586+
validate := New()
13587+
errs := validate.VarWithKey("email", "invalidemail", "required,email")
13588+
NotEqual(t, errs, nil)
13589+
AssertError(t, errs, "email", "email", "email", "email", "email")
13590+
13591+
errs = validate.VarWithKey("email", "[email protected]", "required,email")
13592+
Equal(t, errs, nil)
13593+
}
13594+
13595+
func TestValidate_VarWithKeyCtx(t *testing.T) {
13596+
validate := New()
13597+
errs := validate.VarWithKeyCtx(context.Background(), "age", 15, "required,gt=16")
13598+
NotEqual(t, errs, nil)
13599+
AssertError(t, errs, "age", "age", "age", "age", "gt")
13600+
13601+
errs = validate.VarWithKey("age", 17, "required,gt=16")
13602+
Equal(t, errs, nil)
13603+
}
13604+
1351113605
func TestMongoDBObjectIDFormatValidation(t *testing.T) {
1351213606
tests := []struct {
1351313607
value string `validate:"mongodb"`

0 commit comments

Comments
 (0)