From b95d43b789d7f058ee3a17f3f1a267379ea024d1 Mon Sep 17 00:00:00 2001 From: zytell3301 <68239081+zytell3301@users.noreply.github.com> Date: Thu, 6 May 2021 13:36:41 +0000 Subject: [PATCH] Add ability to validate map data (#752) * Implement ValidateMapCtx func for validating map data * Add ValidateMap func for validating map data with an empty context * Implement a basic example of ValidateMap method * Update ValidateMapCtx method, so it can validate nested maps * Due to changes in ValidateMapCtx, make some updates on ValidateMap method * Update tests due to changes in ValidateMap method * Create a basic test for validating nested maps * Handle error when invalid data is supplied for diving --- _examples/map-validation/main.go | 73 ++++++++++++++++++++++++++++++++ validator_instance.go | 27 ++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 _examples/map-validation/main.go diff --git a/_examples/map-validation/main.go b/_examples/map-validation/main.go new file mode 100644 index 0000000..92e282f --- /dev/null +++ b/_examples/map-validation/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "github.com/go-playground/validator/v10" +) + +var validate *validator.Validate + +func main() { + validate = validator.New() + + validateMap() + validateNestedMap() +} + +func validateMap() { + user := map[string]interface{}{"name": "Arshiya Kiani", "email": "zytel3301@gmail.com"} + + // Every rule will be applied to the item of the data that the offset of rule is pointing to. + // So if you have a field "email": "omitempty,required,email", the validator will apply these + // rules to offset of email in user data + rules := map[string]interface{}{"name": "required,min=8,max=32", "email": "omitempty,required,email"} + + // ValidateMap will return map[string]error. + // The offset of every item in errs is the name of invalid field and the value + // is the message of error. If there was no error, ValidateMap method will + // return an EMPTY map of errors, not nil. If you want to check that + // if there was an error or not, you must check the length of the return value + errs := validate.ValidateMap(user, rules) + + if len(errs) > 0 { + fmt.Println(errs) + // The user is invalid + } + + // The user is valid +} + +func validateNestedMap() { + + data := map[string]interface{}{ + "name": "Arshiya Kiani", + "email": "zytel3301@gmail.com", + "details": map[string]interface{}{ + "family_members": map[string]interface{}{ + "father_name": "Micheal", + "mother_name": "Hannah", + }, + "salary": "1000", + }, + } + + // Rules must be set as the structure as the data itself. If you want to dive into the + // map, just declare its rules as a map + rules := map[string]interface{}{ + "name": "min=4,max=32", + "email": "required,email", + "details": map[string]interface{}{ + "family_members": map[string]interface{}{ + "father_name": "required,min=4,max=32", + "mother_name": "required,min=4,max=32", + }, + "salary": "number", + }, + } + + if len(validate.ValidateMap(data, rules)) == 0 { + // Data is valid + } + + // Data is invalid +} diff --git a/validator_instance.go b/validator_instance.go index d230a32..8e27707 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -143,6 +143,33 @@ func (v *Validate) SetTagName(name string) { v.tagName = name } +// ValidateMapCtx validates a map using a map of validation rules and allows passing of contextual +// validation validation information via context.Context. +func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { + errs := make(map[string]interface{}) + for field, rule := range rules { + if reflect.ValueOf(rule).Kind() == reflect.Map && reflect.ValueOf(data[field]).Kind() == reflect.Map { + err := v.ValidateMapCtx(ctx, data[field].(map[string]interface{}), rule.(map[string]interface{})) + if len(err) > 0 { + errs[field] = err + } + } else if reflect.ValueOf(rule).Kind() == reflect.Map { + errs[field] = errors.New("The field: '" + field + "' is not a map to dive") + } else { + err := v.VarCtx(ctx, data[field], rule.(string)) + if err != nil { + errs[field] = err + } + } + } + return errs +} + +// ValidateMap validates map data form a map of tags +func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { + return v.ValidateMapCtx(context.Background(), data, rules) +} + // RegisterTagNameFunc registers a function to get alternate names for StructFields. // // eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names: