Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed missing keys from returned errors in map validation #1284

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion validator_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{
errs[field] = errors.New("The field: '" + field + "' is not a map to dive")
}
} else if ruleStr, ok := rule.(string); ok {
err := v.VarCtx(ctx, data[field], ruleStr)
err := v.VarWithKeyCtx(ctx, field, data[field], ruleStr)
if err != nil {
errs[field] = err
}
Expand Down Expand Up @@ -708,3 +708,61 @@ func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other
v.pool.Put(vd)
return
}

// VarWithKey validates a single variable with a key to be included in the returned error using tag style validation
// eg.
// var s string
// validate.VarWithKey("email_address", s, "required,email")
//
// WARNING: a struct can be passed for validation eg. time.Time is a struct or
// if you have a custom type and have registered a custom type handler, so must
// allow it; however unforeseen validations will occur if trying to validate a
// struct that is meant to be passed to 'validate.Struct'
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) VarWithKey(key string, field interface{}, tag string) error {
return v.VarWithKeyCtx(context.Background(), key, field, tag)
}

// VarWithKeyCtx validates a single variable with a key to be included in the returned error using tag style validation
// and allows passing of contextual validation information via context.Context.
// eg.
// var s string
// validate.VarWithKeyCtx("email_address", s, "required,email")
//
// WARNING: a struct can be passed for validation eg. time.Time is a struct or
// if you have a custom type and have registered a custom type handler, so must
// allow it; however unforeseen validations will occur if trying to validate a
// struct that is meant to be passed to 'validate.Struct'
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) VarWithKeyCtx(ctx context.Context, key string, field interface{}, tag string) (err error) {
if len(tag) == 0 || tag == skipValidationTag {
return nil
}

ctag := v.fetchCacheTag(tag)

cField := &cField{
name: key,
altName: key,
namesEqual: true,
}

val := reflect.ValueOf(field)
vd := v.pool.Get().(*validate)
vd.top = val
vd.isPartial = false
vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], cField, ctag)

if len(vd.errs) > 0 {
err = vd.errs
vd.errs = nil
}
v.pool.Put(vd)
return
}
94 changes: 94 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13351,6 +13351,100 @@ func TestValidate_ValidateMapCtx(t *testing.T) {
}
}

func TestValidate_ValidateMapCtxWithKeys(t *testing.T) {
type args struct {
data map[string]interface{}
rules map[string]interface{}
errors map[string]interface{}
}
tests := []struct {
name string
args args
want int
}{
{
name: "test invalid email",
args: args{
data: map[string]interface{}{
"email": "emailaddress",
},
rules: map[string]interface{}{
"email": "required,email",
},
errors: map[string]interface{}{
"email": "Key: 'email' Error:Field validation for 'email' failed on the 'email' tag",
},
},
want: 1,
},
{
name: "test multiple errors with capitalized keys",
args: args{
data: map[string]interface{}{
"Email": "emailaddress",
"Age": 15,
},
rules: map[string]interface{}{
"Email": "required,email",
"Age": "number,gt=16",
},
errors: map[string]interface{}{
"Email": "Key: 'Email' Error:Field validation for 'Email' failed on the 'email' tag",
"Age": "Key: 'Age' Error:Field validation for 'Age' failed on the 'gt' tag",
},
},
want: 2,
},
{
name: "test valid map data",
args: args{
data: map[string]interface{}{
"email": "[email protected]",
"age": 17,
},
rules: map[string]interface{}{
"email": "required,email",
"age": "number,gt=16",
},
errors: map[string]interface{}{},
},
want: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
validate := New()
errs := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules)
NotEqual(t, errs, nil)
Equal(t, len(errs), tt.want)
for key, err := range errs {
Equal(t, err.(ValidationErrors)[0].Error(), tt.args.errors[key])
}
})
}
}

func TestValidate_VarWithKey(t *testing.T) {
validate := New()
errs := validate.VarWithKey("email", "invalidemail", "required,email")
NotEqual(t, errs, nil)
AssertError(t, errs, "email", "email", "email", "email", "email")

errs = validate.VarWithKey("email", "[email protected]", "required,email")
Equal(t, errs, nil)
}

func TestValidate_VarWithKeyCtx(t *testing.T) {
validate := New()
errs := validate.VarWithKeyCtx(context.Background(), "age", 15, "required,gt=16")
NotEqual(t, errs, nil)
AssertError(t, errs, "age", "age", "age", "age", "gt")

errs = validate.VarWithKey("age", 17, "required,gt=16")
Equal(t, errs, nil)
}

func TestMongoDBObjectIDFormatValidation(t *testing.T) {
tests := []struct {
value string `validate:"mongodb"`
Expand Down