From 0413a0eb80cac8ab2d666639130658ce49a0c967 Mon Sep 17 00:00:00 2001 From: Saddam H Date: Thu, 7 Feb 2019 12:58:25 +0600 Subject: [PATCH 1/5] Added `ValidateStruct` to validate a struct without a request (#57) (#58) Fixes #12 --- README.md | 4 ++ doc/STRUCT_VALIDATION.md | 61 ++++++++++++++++++++ errors.go | 3 + validator.go | 35 +++++++++-- validator_test.go | 122 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 211 insertions(+), 14 deletions(-) create mode 100644 doc/STRUCT_VALIDATION.md diff --git a/README.md b/README.md index 367781e..347e7b4 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,10 @@ Send request to the server using curl or postman: `curl GET "http://localhost:90 * [Validate JSON to nested struct](doc/NESTED_STRUCT.md) * [Validate using custom rule](doc/CUSTOM_RULE.md) +***Validate struct directly*** + +* [Validate Struct](doc/STRUCT_VALIDATION.md) + ### Validation Rules * `alpha` The field under validation must be entirely alphabetic characters. * `alpha_dash` The field under validation may have alpha-numeric characters, as well as dashes and underscores. diff --git a/doc/STRUCT_VALIDATION.md b/doc/STRUCT_VALIDATION.md new file mode 100644 index 0000000..84d7cfd --- /dev/null +++ b/doc/STRUCT_VALIDATION.md @@ -0,0 +1,61 @@ + + +### Validate Struct + +When using ValidateStruct you must provide data struct and rules. You can also pass message rules if you need custom message or localization. + +```go +package main + +import ( + "encoding/json" + "fmt" + + "github.com/thedevsaddam/govalidator" +) + +type user struct { + Username string `json:"username"` + Email string `json:"email"` + Web string `json:"web"` +} + +func validate(user *user) { + rules := govalidator.MapData{ + "username": []string{"required", "between:3,5"}, + "email": []string{"required", "min:4", "max:20", "email"}, + "web": []string{"url"}, + } + + opts := govalidator.Options{ + Data: &user, + Rules: rules, + } + + v := govalidator.New(opts) + e := v.ValidateStruct() + if len(e) > 0 { + data, _ := json.MarshalIndent(e, "", " ") + fmt.Println(string(data)) + } +} + +func main() { + validate(&user{ + Username: "john", + Email: "invalid", + }) +} +``` +***Prints*** +```json +{ + "email": [ + "The email field is required", + "The email field must be a valid email address" + ], + "username": [ + "The username field is required" + ] +} +``` diff --git a/errors.go b/errors.go index dd96b3a..727506c 100644 --- a/errors.go +++ b/errors.go @@ -5,7 +5,10 @@ import "errors" var ( errStringToInt = errors.New("govalidator: unable to parse string to integer") errStringToFloat = errors.New("govalidator: unable to parse string to float") + errRequireRules = errors.New("govalidator: provide at least rules for Validate* method") errValidateArgsMismatch = errors.New("govalidator: provide at least *http.Request and rules for Validate method") errInvalidArgument = errors.New("govalidator: invalid number of argument") errRequirePtr = errors.New("govalidator: provide pointer to the data structure") + errRequireData = errors.New("govalidator: provide non-nil data structure for ValidateStruct method") + errRequestNotAccepted = errors.New("govalidator: cannot provide an *http.Request for ValidateStruct method") ) diff --git a/validator.go b/validator.go index 55b8f76..0f034c5 100644 --- a/validator.go +++ b/validator.go @@ -146,14 +146,39 @@ func (v *Validator) ValidateJSON() url.Values { if reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr { panic(errRequirePtr) } + + return v.internalValidateStruct() +} + +func (v *Validator) ValidateStruct() url.Values { + if len(v.Opts.Rules) == 0 { + panic(errRequireRules) + } + if v.Opts.Request != nil { + panic(errRequestNotAccepted) + } + if v.Opts.Data != nil && reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr { + panic(errRequirePtr) + } + if v.Opts.Data == nil { + panic(errRequireData) + } + + return v.internalValidateStruct() +} + +func (v *Validator) internalValidateStruct() url.Values { errsBag := url.Values{} - defer v.Opts.Request.Body.Close() - err := json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data) - if err != nil { - errsBag.Add("_error", err.Error()) - return errsBag + if v.Opts.Request != nil { + defer v.Opts.Request.Body.Close() + err := json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data) + if err != nil { + errsBag.Add("_error", err.Error()) + return errsBag + } } + r := roller{} r.setTagIdentifier(tagIdentifier) if v.Opts.TagIdentifier != "" { diff --git a/validator_test.go b/validator_test.go index 5b6014e..ab9896e 100644 --- a/validator_test.go +++ b/validator_test.go @@ -170,18 +170,122 @@ func TestValidator_ValidateJSON_NULLValue(t *testing.T) { } } -func TestValidator_ValidateJSON_panic(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Errorf("ValidateJSON did not panic") - } - }() +func TestValidator_ValidateStruct(t *testing.T) { + type User struct { + Name string `json:"name"` + Email string `json:"email"` + Address string `json:"address"` + Age int `json:"age"` + Zip string `json:"zip"` + Color int `json:"color"` + } - opts := Options{} + postUser := User{ + Name: "", + Email: "inalid email", + Address: "", + Age: 1, + Zip: "122", + Color: 5, + } + + rules := MapData{ + "name": []string{"required"}, + "email": []string{"email"}, + "address": []string{"required", "between:3,5"}, + "age": []string{"bool"}, + "zip": []string{"len:4"}, + "color": []string{"min:10"}, + } + + opts := Options{ + Data: &postUser, + Rules: rules, + } vd := New(opts) - validationErr := vd.ValidateJSON() + vd.SetTagIdentifier("json") + validationErr := vd.ValidateStruct() if len(validationErr) != 5 { - t.Error("ValidateJSON failed") + t.Error("ValidateStruct failed") } } + +func TestValidator_ValidateJSON_NoRules_panic(t *testing.T) { + opts := Options{} + + assertPanicWith(t, errValidateArgsMismatch, func() { + New(opts).ValidateJSON() + }) +} + +func TestValidator_ValidateJSON_NonPointer_panic(t *testing.T) { + req, _ := http.NewRequest("POST", "/", nil) + + type User struct { + } + + var user User + opts := Options{ + Request: req, + Data: user, + Rules: MapData{ + "name": []string{"required"}, + }, + } + + assertPanicWith(t, errRequirePtr, func() { + New(opts).ValidateJSON() + }) +} + +func TestValidator_ValidateStruct_NoRules_panic(t *testing.T) { + opts := Options{} + + assertPanicWith(t, errRequireRules, func() { + New(opts).ValidateStruct() + }) +} + +func TestValidator_ValidateStruct_RequestProvided_panic(t *testing.T) { + req, _ := http.NewRequest("POST", "/", nil) + opts := Options{ + Request: req, + Rules: MapData{ + "name": []string{"required"}, + }, + } + + assertPanicWith(t, errRequestNotAccepted, func() { + New(opts).ValidateStruct() + }) +} + +func TestValidator_ValidateStruct_NonPointer_panic(t *testing.T) { + type User struct { + } + + var user User + opts := Options{ + Data: user, + Rules: MapData{ + "name": []string{"required"}, + }, + } + + assertPanicWith(t, errRequirePtr, func() { + New(opts).ValidateStruct() + }) +} + +func TestValidator_ValidateStruct_DataNil_panic(t *testing.T) { + opts := Options{ + Rules: MapData{ + "name": []string{"required"}, + }, + } + + assertPanicWith(t, errRequireData, func() { + New(opts).ValidateStruct() + }) +} From 12641b3bbc5bea2d55cbf4c8bf28ecbd7f990957 Mon Sep 17 00:00:00 2001 From: Tiago Cardoso <34026324+tiagoacardoso@users.noreply.github.com> Date: Sat, 9 Mar 2019 16:55:10 +0000 Subject: [PATCH 2/5] Update MAP_VALIDATION.md (#61) * Added `ValidateStruct` to validate a struct without a request (#57) (#58) Fixes #12 * Update MAP_VALIDATION.md Fixed typo --- doc/MAP_VALIDATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/MAP_VALIDATION.md b/doc/MAP_VALIDATION.md index e3e0c50..1a0347c 100644 --- a/doc/MAP_VALIDATION.md +++ b/doc/MAP_VALIDATION.md @@ -33,7 +33,7 @@ func handler(w http.ResponseWriter, r *http.Request) { e := vd.ValidateJSON() fmt.Println(data) err := map[string]interface{}{"validation error": e} - w.Header().Set("Content-type", "applciation/json") + w.Header().Set("Content-type", "application/json") json.NewEncoder(w).Encode(err) } From 4432d4fe42ba8b06aa764b637a26c57f047963dd Mon Sep 17 00:00:00 2001 From: amjustdoit Date: Wed, 27 Mar 2019 21:00:35 +0400 Subject: [PATCH 3/5] fix validate uint type of struct (#65) change format for printf, fix validate uint field type of struct --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index f8a2715..a9565c2 100644 --- a/utils.go +++ b/utils.go @@ -37,7 +37,7 @@ func isRuleExist(rule string) bool { func toString(v interface{}) string { str, ok := v.(string) if !ok { - str = fmt.Sprintf("%#v", v) + str = fmt.Sprintf("%v", v) } return str } From dde5f75b06f40e24ac16a916b93c922fcd03ced1 Mon Sep 17 00:00:00 2001 From: Bakhtiyor Ruziev <32102033+bruziev@users.noreply.github.com> Date: Sat, 13 Apr 2019 09:06:25 -0400 Subject: [PATCH 4/5] Make lint and fix linter issues (#67) --- roller.go | 9 +-------- roller_test.go | 5 +++-- type.go | 10 +++++----- utils_test.go | 10 ---------- validate_file_test.go | 4 ++-- validator.go | 4 ++-- 6 files changed, 13 insertions(+), 29 deletions(-) diff --git a/roller.go b/roller.go index 45431e2..af319a5 100644 --- a/roller.go +++ b/roller.go @@ -67,7 +67,7 @@ func (r *roller) getFlatMap() map[string]interface{} { return r.root } -// getFlatVal return interfac{} value if exist +// getFlatVal return interface{} value if exist func (r *roller) getFlatVal(key string) (interface{}, bool) { var val interface{} var ok bool @@ -173,13 +173,6 @@ func (r *roller) traverseStruct(iface interface{}) { // traverseMap through all the map and add it to root func (r *roller) traverseMap(iface interface{}) { - ifv := reflect.ValueOf(iface) - ift := reflect.TypeOf(iface) - if ift.Kind() == reflect.Ptr { - ifv = ifv.Elem() - ift = ift.Elem() - } - switch t := iface.(type) { case map[string]interface{}: for k, v := range t { diff --git a/roller_test.go b/roller_test.go index e6a46b7..2a7f807 100644 --- a/roller_test.go +++ b/roller_test.go @@ -1,6 +1,7 @@ package govalidator import ( + "reflect" "testing" ) @@ -117,7 +118,7 @@ func BenchmarkRoller_Start(b *testing.B) { func Test_Roller_Start_empty_map(t *testing.T) { r := roller{} - emap := make(map[string]interface{}, 0) + emap := make(map[string]interface{}) r.setTagIdentifier("validate") r.setTagSeparator("|") r.start(emap) @@ -242,7 +243,7 @@ func TestRoller_GetFlatVal(t *testing.T) { //check struct field with array intArrOf5, _ := r.getFlatVal("array") - if len(intArrOf5.([5]int)) != 5 { + if reflect.ValueOf(intArrOf5).Len() != 5 && reflect.TypeOf(intArrOf5).Kind() == reflect.Array { t.Error("GetFlatVal failed for struct array of [5]int field!") } diff --git a/type.go b/type.go index 3f5109b..19354e5 100644 --- a/type.go +++ b/type.go @@ -15,7 +15,7 @@ var null = []byte("null") // UnmarshalJSON ... func (i *Int) UnmarshalJSON(data []byte) error { - if bytes.Compare(data, null) == 0 { + if bytes.Equal(data, null) { return nil } i.IsSet = true @@ -40,7 +40,7 @@ type Int64 struct { // UnmarshalJSON ... func (i *Int64) UnmarshalJSON(data []byte) error { - if bytes.Compare(data, null) == 0 { + if bytes.Equal(data, null) { return nil } i.IsSet = true @@ -65,7 +65,7 @@ type Float32 struct { // UnmarshalJSON ... func (i *Float32) UnmarshalJSON(data []byte) error { - if bytes.Compare(data, null) == 0 { + if bytes.Equal(data, null) { return nil } i.IsSet = true @@ -90,7 +90,7 @@ type Float64 struct { // UnmarshalJSON ... func (i *Float64) UnmarshalJSON(data []byte) error { - if bytes.Compare(data, null) == 0 { + if bytes.Equal(data, null) { return nil } i.IsSet = true @@ -115,7 +115,7 @@ type Bool struct { // UnmarshalJSON ... func (i *Bool) UnmarshalJSON(data []byte) error { - if bytes.Compare(data, null) == 0 { + if bytes.Equal(data, null) { return nil } i.IsSet = true diff --git a/utils_test.go b/utils_test.go index 4ef77a1..d06e3e6 100644 --- a/utils_test.go +++ b/utils_test.go @@ -22,16 +22,6 @@ func Benchmark_isContainRequiredField(b *testing.B) { } } -type person struct{} - -func (person) Details() string { - return "John Doe" -} - -func (person) Age(age string) string { - return "Age: " + age -} - func Test_isRuleExist(t *testing.T) { if !isRuleExist("required") { t.Error("isRuleExist failed for valid rule") diff --git a/validate_file_test.go b/validate_file_test.go index 1f12287..8a6330e 100644 --- a/validate_file_test.go +++ b/validate_file_test.go @@ -23,8 +23,8 @@ func buildMocFormReq() (*http.Request, error) { if err != nil { return nil, err } - io.Copy(part, file) - file.Close() + _, _ = io.Copy(part, file) + _ = file.Close() err = writer.Close() if err != nil { return nil, err diff --git a/validator.go b/validator.go index 0f034c5..c88e74c 100644 --- a/validator.go +++ b/validator.go @@ -117,9 +117,9 @@ func (v *Validator) Validate() url.Values { // and if the input data is empty for this field func (v *Validator) getNonRequiredFields() map[string]struct{} { if v.Opts.FormSize > 0 { - v.Opts.Request.ParseMultipartForm(v.Opts.FormSize) + _ = v.Opts.Request.ParseMultipartForm(v.Opts.FormSize) } else { - v.Opts.Request.ParseMultipartForm(defaultFormSize) + _ = v.Opts.Request.ParseMultipartForm(defaultFormSize) } inputs := v.Opts.Request.Form From 3ec9f4b2b1273b1b2fe2edd5afdbc2a819b3e511 Mon Sep 17 00:00:00 2001 From: Tiago Cardoso <34026324+tiagoacardoso@users.noreply.github.com> Date: Fri, 14 Jun 2019 07:55:41 +0100 Subject: [PATCH 5/5] Added Mac Address validator (#69) * Added Mac Address validator * Fixed typo adress instead of address * Fixed tests for mac address message error --- README.md | 1 + doc/BENCHMARK.md | 65 +++++++++++++++++++++++---------------------- helper.go | 5 ++++ helper_test.go | 21 +++++++++++++++ regex_patterns.go | 3 +++ rules.go | 13 +++++++++ rules_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 143 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 347e7b4..3527a48 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,7 @@ Send request to the server using curl or postman: `curl GET "http://localhost:90 * `not_in:foo,bar` The field under validation must have one value except foo,bar. e.g: `not_in:admin,manager,user` must not contain the values (admin or manager or user) * `email` The field under validation must have a valid email. * `float` The field under validation must have a valid float number. +* `mac_address` The field under validation must have be a valid Mac Address. * `min:numeric` The field under validation must have a min length of characters for string, items length for slice/map, value for integer or float. e.g: `min:3` may contains characters minimum length of 3 like `"john", "jane", "jane321"` but not `"mr", "xy"` * `max:numeric` The field under validation must have a max length of characters for string, items length for slice/map, value for integer or float. diff --git a/doc/BENCHMARK.md b/doc/BENCHMARK.md index ab378b4..144991f 100644 --- a/doc/BENCHMARK.md +++ b/doc/BENCHMARK.md @@ -1,36 +1,37 @@ Benchmarks =================== -Machine: Mac Book Pro-2015 2.7GHz 8GB -Go version: go1.8.1 darwin/amd64 +Machine: XPS 13 9370 (07E6) +Go version: go version go1.12.6 linux/amd64 -| ➜ go test -run=XXX -bench=. -benchmem=true | | | | | -|--------------------------------------------|-----------|------------|-----------|--------------| -| Benchmark_IsAlpha-4 | 5000000 | 323 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsAlphaDash-4 | 3000000 | 415 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsAlphaNumeric-4 | 5000000 | 338 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsBoolean-4 | 100000000 | 10.6 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsCreditCard-4 | 3000000 | 543 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsCoordinate-4 | 2000000 | 950 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsCSSColor-4 | 5000000 | 300 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsDate-4 | 2000000 | 719 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsDateDDMMYY-4 | 3000000 | 481 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsEmail-4 | 1000000 | 1172 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsFloat-4 | 3000000 | 432 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsIn-4 | 200000000 | 7.34 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsJSON-4 | 1000000 | 1595 ns/op | 768 B/op | 12 allocs/op | -| Benchmark_IsNumeric-4 | 10000000 | 195 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsLatitude-4 | 3000000 | 523 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsLongitude-4 | 3000000 | 516 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsIP-4 | 1000000 | 1073 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsIPV4-4 | 3000000 | 580 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsIPV6-4 | 1000000 | 1288 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsMatchedRegex-4 | 200000 | 7133 ns/op | 5400 B/op | 66 allocs/op | -| Benchmark_IsURL-4 | 1000000 | 1159 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsUUID-4 | 2000000 | 832 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsUUID3-4 | 2000000 | 783 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsUUID4-4 | 2000000 | 899 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_IsUUID5-4 | 2000000 | 828 ns/op | 0 B/op | 0 allocs/op | -| BenchmarkRoller_Start-4 | 200000 | 6869 ns/op | 2467 B/op | 28 allocs/op | -| Benchmark_isContainRequiredField-4 | 300000000 | 4.23 ns/op | 0 B/op | 0 allocs/op | -| Benchmark_Validate-4 | 200000 | 9347 ns/op | 664 B/op | 28 allocs/op | +| ➜ go test -run=XXX -bench=. -benchmem=true | | | | | +|--------------------------------------------|------------|--------------|--------------|--------------| +|Benchmark_IsAlpha-8 | 10000000 | 205 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsAlphaDash-8 | 5000000 | 268 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsAlphaNumeric-8 | 10000000 | 182 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsBoolean-8 | 200000000 | 6.84 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsCreditCard-8 | 10000000 | 243 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsCoordinate-8 | 3000000 | 482 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsCSSColor-8 | 10000000 | 160 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsDate-8 | 3000000 | 531 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsDateDDMMYY-8 | 5000000 | 246 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsEmail-8 | 3000000 | 549 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsFloat-8 | 10000000 | 199 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsIn-8 | 5000000 | 3.77 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsJSON-8 | 2000000 | 956 ns/op | 640 B/op | 12 allocs/op | +|Benchmark_IsMacAddress-8 | 5000000 | 277 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsNumeric-8 | 20000000 | 110 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsLatitude-8 | 5000000 | 249 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsLongitude-8 | 5000000 | 250 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsIP-8 | 3000000 | 578 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsIPV4-8 | 5000000 | 286 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsIPV6-8 | 2000000 | 931 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsMatchedRegex-8 | 200000 | 5786 ns/op | 4465 B/op | 57 allocs/op | +|Benchmark_IsURL-8 | 2000000 | 866 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsUUID-8 | 3000000 | 455 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsUUID3-8 | 3000000 | 536 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsUUID4-8 | 3000000 | 411 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_IsUUID5-8 | 3000000 | 443 ns/op | 0 B/op | 0 allocs/op | +|BenchmarkRoller_Start-8 | 300000 | 4659 ns/op | 2468 B/op | 28 allocs/op | +|Benchmark_isContainRequiredField-8 | 1000000000 | 2.69 ns/op | 0 B/op | 0 allocs/op | +|Benchmark_Validate-8 | 200000 | 6742 ns/op | 727 B/op | 29 allocs/op | diff --git a/helper.go b/helper.go index 9cb02b8..2780458 100644 --- a/helper.go +++ b/helper.go @@ -97,6 +97,11 @@ func isNumeric(str string) bool { return regexNumeric.MatchString(str) } +// isMacAddres check the provided string is valid Mac Address or not +func isMacAddress(str string) bool { + return regexMacAddress.MatchString(str) +} + // isLatitude check the provided input string is a valid latitude or not func isLatitude(str string) bool { return regexLatitude.MatchString(str) diff --git a/helper_test.go b/helper_test.go index 474194c..87b7137 100644 --- a/helper_test.go +++ b/helper_test.go @@ -81,6 +81,13 @@ var ( _roleList = []string{"admin", "manager", "supervisor"} _validJSONString = `{"FirstName": "Bob", "LastName": "Smith"}` _invalidJSONString = `{"invalid json"}` + _macaddressList = inputs{ + "fc:40:2e:f1:d3:6f": true, + "87:7a:45:f6:8b:ed": true, + "a5:91:91:80:d2:fd": true, + "1f:ce:44:46:24:b4": true, + "00:02:x2:34:72:a5": false, + } _numericStringList = inputs{"12": true, "09": true, "878": true, "100": true, "a": false, "xyz": false, "1000000000000": true} _latList = inputs{"30.297018": true, "40.044438": true, "a": false, "xyz": false} _lonList = inputs{"-78.486328": true, "-104.0625": true, "a": false, "xyz": false} @@ -301,6 +308,20 @@ func Benchmark_IsJSON(b *testing.B) { } } +func Test_IsMacAddress(t *testing.T) { + for n, s := range _macaddressList { + if isMacAddress(n) != s { + t.Error("IsMacAddress failed!") + } + } +} + +func Benchmark_IsMacAddress(b *testing.B) { + for n := 0; n < b.N; n++ { + isMacAddress("00:02:02:34:72:a5") + } +} + func Test_IsNumeric(t *testing.T) { for n, s := range _numericStringList { if isNumeric(n) != s { diff --git a/regex_patterns.go b/regex_patterns.go index cffd92d..7db879f 100644 --- a/regex_patterns.go +++ b/regex_patterns.go @@ -39,6 +39,8 @@ const ( Latitude string = "^(\\+|-)?(?:90(?:(?:\\.0{1,6})?)|(?:[0-9]|[1-8][0-9])(?:(?:\\.[0-9]{1,6})?))$" // Longitude represents longitude regular expression Longitude string = "^(\\+|-)?(?:180(?:(?:\\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\\.[0-9]{1,6})?))$" + // MacAddress represents regular expression for mac address + MacAddress string = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" // Numeric represents regular expression for numeric Numeric string = "^-?[0-9]+$" // URL represents regular expression for url @@ -66,6 +68,7 @@ var ( regexDigits = regexp.MustCompile(Digits) regexEmail = regexp.MustCompile(Email) regexFloat = regexp.MustCompile(Float) + regexMacAddress = regexp.MustCompile(MacAddress) regexNumeric = regexp.MustCompile(Numeric) regexLatitude = regexp.MustCompile(Latitude) regexLongitude = regexp.MustCompile(Longitude) diff --git a/rules.go b/rules.go index 3b94488..9d1dfca 100644 --- a/rules.go +++ b/rules.go @@ -852,6 +852,19 @@ func init() { return nil }) + // Numeric check if the value of the field is Numeric + AddCustomRule("mac_address", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid Mac Address", field) + if message != "" { + err = errors.New(message) + } + if !isMacAddress(str) { + return err + } + return nil + }) + // Numeric check if the value of the field is Numeric AddCustomRule("numeric", func(field string, rule string, message string, value interface{}) error { str := toString(value) diff --git a/rules_test.go b/rules_test.go index cd7593e..f350c3e 100644 --- a/rules_test.go +++ b/rules_test.go @@ -1332,6 +1332,73 @@ func Test_Len_message(t *testing.T) { } } +func Test_MacAddress(t *testing.T) { + type user struct { + MacAddress string `json:"mac_address"` + } + + postUser := user{MacAddress: "e4:2b:e8:d3:41:0f"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "mac_address": []string{"mac_address"}, + } + + messages := MapData{ + "mac_address": []string{"mac_address:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Error("Valid Mac Address validation failed!") + } +} + +func Test_MacAddress_message(t *testing.T) { + type user struct { + MacAddress string `json:"mac_address"` + } + + postUser := user{MacAddress: "invalid_mac_address"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "mac_address": []string{"mac_address"}, + } + + messages := MapData{ + "mac_address": []string{"mac_address:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("mac_address") != "custom_message" { + t.Error("Mac Address custom message failed!") + } +} + + func Test_Numeric(t *testing.T) { type user struct { NID string `json:"nid"`