diff --git a/validationmdl/validationcore/CONTRIBUTING.md b/validationmdl/validationcore/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..dd4b0ed5f4fb40350bf63a0f88291b921c547cfd --- /dev/null +++ b/validationmdl/validationcore/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing + +## Must follow the guide for issues + - Use the search tool before opening a new issue. + - Please provide source code and stack trace if you found a bug. + - Please review the existing issues, [project cards](https://github.com/thedevsaddam/govalidator/projects/1) and provide feedback to them + +## Pull Request Process + - Open your pull request against `dev` branch + - It should pass all tests in the available continuous integrations systems such as TravisCI. + - You should add/modify tests to cover your proposed code changes. + - If your pull request contains a new feature, please document it on the README. diff --git a/validationmdl/validationcore/LICENSE.md b/validationmdl/validationcore/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..5786a9421b13c54aadb26eec80bda6628c144bb4 --- /dev/null +++ b/validationmdl/validationcore/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2017 Saddam H <thedevsaddam@gmail.com> + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/validationmdl/validationcore/README.md b/validationmdl/validationcore/README.md new file mode 100644 index 0000000000000000000000000000000000000000..11eff74fee33bf7f84c061748ff85214bee5d77a --- /dev/null +++ b/validationmdl/validationcore/README.md @@ -0,0 +1,232 @@ +Package govalidator +========================= +[](https://travis-ci.org/thedevsaddam/govalidator) +[](https://github.com/thedevsaddam/govalidator/releases) +[](https://goreportcard.com/report/github.com/thedevsaddam/govalidator) +[](https://coveralls.io/github/thedevsaddam/govalidator?branch=master) +[](https://godoc.org/github.com/thedevsaddam/govalidator) +[](https://github.com/thedevsaddam/govalidator/blob/dev/LICENSE.md) + +Validate golang request data with simple rules. Highly inspired by Laravel's request validation. + + +### Installation + +Install the package using +```go +$ go get github.com/thedevsaddam/govalidator +// or +$ go get gopkg.in/thedevsaddam/govalidator.v1 +``` + +### Usage + +To use the package import it in your `*.go` code +```go +import "github.com/thedevsaddam/govalidator" +// or +import "gopkg.in/thedevsaddam/govalidator.v1" +``` + +### Example + +***Validate `form-data`, `x-www-form-urlencoded` and `query params`*** + +```go + +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/thedevsaddam/govalidator" +) + +func handler(w http.ResponseWriter, r *http.Request) { + rules := govalidator.MapData{ + "username": []string{"required", "between:3,8"}, + "email": []string{"required", "min:4", "max:20", "email"}, + "web": []string{"url"}, + "phone": []string{"digits:11"}, + "agree": []string{"bool"}, + "dob": []string{"date"}, + } + + messages := govalidator.MapData{ + "username": []string{"required:আপনাকে অবশà§à¦¯à¦‡ ইউজারনেম দিতে হবে", "between:ইউজারনেম অবশà§à¦¯à¦‡ ৩-৮ অকà§à¦·à¦° হতে হবে"}, + "phone": []string{"digits:ফোন নামà§à¦¬à¦¾à¦° অবশà§à¦¯à¦‡ ১১ নমà§à¦¬à¦¾à¦°à§‡à¦° হতে হবে"}, + } + + opts := govalidator.Options{ + Request: r, // request object + Rules: rules, // rules map + Messages: messages, // custom message map (Optional) + RequiredDefault: true, // all the field to be pass the rules + } + v := govalidator.New(opts) + e := v.Validate() + err := map[string]interface{}{"validationError": e} + w.Header().Set("Content-type", "application/json") + json.NewEncoder(w).Encode(err) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Listening on port: 9000") + http.ListenAndServe(":9000", nil) +} + +``` + +Send request to the server using curl or postman: `curl GET "http://localhost:9000?web=&phone=&zip=&dob=&agree="` + + +***Response*** +```json +{ + "validationError": { + "agree": [ + "The agree may only contain boolean value, string or int 0, 1" + ], + "dob": [ + "The dob field must be a valid date format. e.g: yyyy-mm-dd, yyyy/mm/dd etc" + ], + "email": [ + "The email field is required", + "The email field must be a valid email address" + ], + "phone": [ + "ফোন নামà§à¦¬à¦¾à¦° অবশà§à¦¯à¦‡ ১১ নমà§à¦¬à¦¾à¦°à§‡à¦° হতে হবে" + ], + "username": [ + "আপনাকে অবশà§à¦¯à¦‡ ইউজারনেম দিতে হবে", + "ইউজারনেম অবশà§à¦¯à¦‡ ৩-৮ অকà§à¦·à¦° হতে হবে" + ], + "web": [ + "The web field format is invalid" + ] + } +} +``` + +### More examples + +***Validate file*** + +* [Validate file](doc/FILE_VALIDATION.md) + +***Validate `application/json` or `text/plain` as raw body*** + +* [Validate JSON to simple struct](doc/SIMPLE_STRUCT_VALIDATION.md) +* [Validate JSON to map](doc/MAP_VALIDATION.md) +* [Validate JSON to nested struct](doc/NESTED_STRUCT.md) +* [Validate using custom rule](doc/CUSTOM_RULE.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. +* `alpha_num` The field under validation must be entirely alpha-numeric characters. +* `between:numeric,numeric` The field under validation check the length of characters/ length of array, slice, map/ range between two integer or float number etc. +* `numeric` The field under validation must be entirely numeric characters. +* `numeric_between:numeric,numeric` The field under validation must be a numeric value between the range. + e.g: `numeric_between:18,65` may contains numeric value like `35`, `55` . You can also pass float value to check +* `bool` The field under validation must be able to be cast as a boolean. Accepted input are `true, false, 1, 0, "1" and "0"`. +* `credit_card` The field under validation must have a valid credit card number. Accepted cards are `Visa, MasterCard, American Express, Diners Club, Discover and JCB card` +* `coordinate` The field under validation must have a value of valid coordinate. +* `css_color` The field under validation must have a value of valid CSS color. Accepted colors are `hex, rgb, rgba, hsl, hsla` like `#909, #00aaff, rgb(255,122,122)` +* `date` The field under validation must have a valid date of format yyyy-mm-dd or yyyy/mm/dd. +* `date:dd-mm-yyyy` The field under validation must have a valid date of format dd-mm-yyyy. +* `digits:int` The field under validation must be numeric and must have an exact length of value. +* `digits_between:int,int` The field under validation must be numeric and must have length between the range. + e.g: `digits_between:3,5` may contains digits like `2323`, `12435` +* `in:foo,bar` The field under validation must have one of the values. e.g: `in:admin,manager,user` must contain the values (admin or manager or user) +* `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. +* `max: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. + e.g: `max:6` may contains characters maximum length of 6 like `"john doe", "jane doe"` but not `"john", "jane"` +* `len:numeric` The field under validation must have an exact length of characters, exact integer or float value, exact size of map/slice. + e.g: `len:4` may contains characters exact length of 4 like `Food, Mood, Good` +* `ip` The field under validation must be a valid IP address. +* `ip_v4` The field under validation must be a valid IP V4 address. +* `ip_v6` The field under validation must be a valid IP V6 address. +* `json` The field under validation must be a valid JSON string. +* `lat` The field under validation must be a valid latitude. +* `lon` The field under validation must be a valid longitude. +* `regex:regular expression` The field under validation validate against the regex. e.g: `regex:^[a-zA-Z]+$` validate the letters. +* `required` The field under validation must be present in the input data and not empty. A field is considered "empty" if one of the following conditions are true: 1) The value is null. 2)The value is an empty string. 3) Zero length of map, slice. 4) Zero value for integer or float +* `size:integer` The field under validation validate a file size only in form-data ([see example](doc/FILE_VALIDATION.md)) +* `ext:jpg,png` The field under validation validate a file extension ([see example](doc/FILE_VALIDATION.md)) +* `mime:image/jpg,image/png` The field under validation validate a file mime type ([see example](doc/FILE_VALIDATION.md)) +* `url` The field under validation must be a valid URL. +* `uuid` The field under validation must be a valid UUID. +* `uuid_v3` The field under validation must be a valid UUID V3. +* `uuid_v4` The field under validation must be a valid UUID V4. +* `uuid_v5` The field under validation must be a valid UUID V5. + +### Add Custom Rules + +```go +func init() { + // simple example + govalidator.AddCustomRule("must_john", func(field string, rule string, message string, value interface{}) error { + val := value.(string) + if val != "john" || val != "John" { + return fmt.Errorf("The %s field must be John or john", field) + } + return nil + }) + + // custom rules to take fixed length word. + // e.g: word:5 will throw error if the field does not contain exact 5 word + govalidator.AddCustomRule("word", func(field string, rule string, message string, value interface{}) error { + valSlice := strings.Fields(value.(string)) + l, _ := strconv.Atoi(strings.TrimPrefix(rule, "word:")) //handle other error + if len(valSlice) != l { + return fmt.Errorf("The %s field must be %d word", field, l) + } + return nil + }) + +} +``` +Note: Array, map, slice can be validated by adding custom rules. + +### Custom Message/ Localization +If you need to translate validation message you can pass messages as options. + +```go +messages := govalidator.MapData{ + "username": []string{"required:You must provide username", "between:The username field must be between 3 to 8 chars"}, + "zip": []string{"numeric:Please provide zip field as numeric"}, +} + +opts := govalidator.Options{ + Messages: messages, +} +``` + +### Contribution +If you are interested to make the package better please send pull requests or create an issue so that others can fix. +[Read the contribution guide here](CONTRIBUTING.md) + +### Contributors + +- [Jun Kimura](https://github.com/bluele) +- [Steve HIll](https://github.com/stevehill1981) +- [ErickSkrauch](https://github.com/erickskrauch) +- [Sakib Sami](https://github.com/s4kibs4mi) +- [Rip](https://github.com/ripbandit) +- [Jose Nazario](https://github.com/paralax) + +### See all [contributors](https://github.com/thedevsaddam/govalidator/graphs/contributors) + +### See [benchmarks](doc/BENCHMARK.md) +### Read [API documentation](https://godoc.org/github.com/thedevsaddam/govalidator) + +### **License** +The **govalidator** is an open-source software licensed under the [MIT License](LICENSE.md). diff --git a/validationmdl/validationcore/doc/BENCHMARK.md b/validationmdl/validationcore/doc/BENCHMARK.md new file mode 100644 index 0000000000000000000000000000000000000000..ab378b425323c1c7c40d8a5882652ed5e0d0d18e --- /dev/null +++ b/validationmdl/validationcore/doc/BENCHMARK.md @@ -0,0 +1,36 @@ +Benchmarks +=================== + +Machine: Mac Book Pro-2015 2.7GHz 8GB +Go version: go1.8.1 darwin/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 | diff --git a/validationmdl/validationcore/doc/CUSTOM_RULE.md b/validationmdl/validationcore/doc/CUSTOM_RULE.md new file mode 100644 index 0000000000000000000000000000000000000000..f8c4ab266c01111373375f65e0ca6fba1ebf659c --- /dev/null +++ b/validationmdl/validationcore/doc/CUSTOM_RULE.md @@ -0,0 +1,86 @@ + +### Validate with custom rule + +You can register custom validation rules. This rule will work for both `Validate` and `ValidateJSON` method. You will get all the information you need to validate an input. + +```go +package main + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/thedevsaddam/govalidator" +) + +func init() { + // custom rules to take fixed length word. + // e.g: max_word:5 will throw error if the field contains more than 5 words + govalidator.AddCustomRule("max_word", func(field string, rule string, message string, value interface{}) error { + valSlice := strings.Fields(value.(string)) + l, _ := strconv.Atoi(strings.TrimPrefix(rule, "max_word:")) //handle other error + if len(valSlice) > l { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must not be greater than %d words", field, l) + } + return nil + }) +} + +type article struct { + Title string `json:"title"` + Body string `json:"body"` + Tags []string `json:"tags"` +} + +func handler(w http.ResponseWriter, r *http.Request) { + var article article + rules := govalidator.MapData{ + "title": []string{"between:10,120"}, + "body": []string{"max_word:150"}, // using custom rule max_word + "tags": []string{"between:3,5"}, + } + + opts := govalidator.Options{ + Request: r, + Data: &article, + Rules: rules, + RequiredDefault: true, //force user to fill all the inputs + } + + v := govalidator.New(opts) + e := v.ValidateJSON() + err := map[string]interface{}{"validationError": e} + w.Header().Set("Content-type", "applciation/json") + json.NewEncoder(w).Encode(err) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Listening on port: 9000") + http.ListenAndServe(":9000", nil) +} + +``` +***Resposne*** +```json +{ + "validationError": { + "body": [ + "The body field must not be greater than 150 words" + ], + "tags": [ + "The tags field must be between 3 and 5" + ], + "title": [ + "The title field must be between 10 and 120" + ] + } +} +``` diff --git a/validationmdl/validationcore/doc/FILE_VALIDATION.md b/validationmdl/validationcore/doc/FILE_VALIDATION.md new file mode 100644 index 0000000000000000000000000000000000000000..55e68d128db3e88da4b9874a9fb3e80aa26a3408 --- /dev/null +++ b/validationmdl/validationcore/doc/FILE_VALIDATION.md @@ -0,0 +1,67 @@ + +### Validate File + +For `multipart/form-data` validation, use `file:` prefix to _field_ name which contains the file. If use custom message then also use the `file:` prefix to Messages MapData key. + +```go +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/thedevsaddam/govalidator" +) + +func handler(w http.ResponseWriter, r *http.Request) { + rules := govalidator.MapData{ + "file:photo": []string{"ext:jpg,png", "size:10000", "mime:jpg,png", "required"}, + } + + messages := govalidator.MapData{ + "file:photo": []string{"ext:Only jpg/png is allowed", "required:Photo is required"}, + } + + opts := govalidator.Options{ + Request: r, // request object + Rules: rules, // rules map, + Messages: messages, + } + v := govalidator.New(opts) + e := v.Validate() + err := map[string]interface{}{"validationError": e} + w.Header().Set("Content-type", "applciation/json") + json.NewEncoder(w).Encode(err) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Listening on port: 9000") + http.ListenAndServe(":9000", nil) +} + +``` +***Resposne*** +```json +{ + "validationError": { + "photo": [ + "Photo is required" + ] + } +} + +or + +{ + "validationError": { + "photo": [ + "Only jpg/png is allowed", + "The photo field size is can not be greater than 10000 bytes", + "The photo field file mime text/plain is invalid" + ] + } +} +``` +Note: At this time it can validate only single file. diff --git a/validationmdl/validationcore/doc/MAP_VALIDATION.md b/validationmdl/validationcore/doc/MAP_VALIDATION.md new file mode 100644 index 0000000000000000000000000000000000000000..e3e0c505a83e041845af6b63819d3d2e2625d78d --- /dev/null +++ b/validationmdl/validationcore/doc/MAP_VALIDATION.md @@ -0,0 +1,85 @@ +### Validate JSON body into Map + +When using ValidateJSON you must provide data struct or map, rules and request. You can also pass message rules if you need custom message or localization. + +```go +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/thedevsaddam/govalidator" +) + +func handler(w http.ResponseWriter, r *http.Request) { + rules := govalidator.MapData{ + "username": []string{"required", "between:3,5"}, + "email": []string{"required", "min:4", "max:20", "email"}, + "web": []string{"url"}, + "age": []string{"numeric_between:18,56"}, + } + + data := make(map[string]interface{}, 0) + + opts := govalidator.Options{ + Request: r, + Rules: rules, + Data: &data, + } + + vd := govalidator.New(opts) + e := vd.ValidateJSON() + fmt.Println(data) + err := map[string]interface{}{"validation error": e} + w.Header().Set("Content-type", "applciation/json") + json.NewEncoder(w).Encode(err) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Listening on port: 9000") + http.ListenAndServe(":9000", nil) +} + +``` + +***Resposne*** +```json +{ + "validationError": { + "age": [ + "The age field must be between 18 and 56" + ], + "dob": [ + "The dob field must be a valid date format. e.g: yyyy-mm-dd, yyyy/mm/dd etc" + ], + "email": [ + "The email field is required", + "The email field must be a valid email address" + ], + "phone": [ + "The phone field must be 11 digits" + ], + "postalCode": [ + "The postalCode field must be 4 digits" + ], + "roles": [ + "The roles field must be length of 4" + ], + "username": [ + "The username field is required", + "The username field must be between 3 and 8" + ], + "village": [ + "The village field must be between 3 and 10" + ], + "web": [ + "The web field format is invalid" + ] + } +} +``` + +Note: You can pass custom message diff --git a/validationmdl/validationcore/doc/NESTED_STRUCT.md b/validationmdl/validationcore/doc/NESTED_STRUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..3425b83a5f4224d6b19fdf4ae6dd27a5f355bb90 --- /dev/null +++ b/validationmdl/validationcore/doc/NESTED_STRUCT.md @@ -0,0 +1,95 @@ + +### Validate JSON body with nested struct and slice + + +```go +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/thedevsaddam/govalidator" +) + +type ( + user struct { + Username string `json:"username"` + Email string `json:"email"` + Web string `json:"web"` + Age int `json:"age"` + Phone string `json:"phone"` + Agree bool `json:"agree"` + DOB string `json:"dob"` + Address address + Roles []string `json:"roles"` + } + + address struct { + Village string `json:"village"` + PostalCode string `json:"postalCode"` + } +) + +func handler(w http.ResponseWriter, r *http.Request) { + var usr user + rules := govalidator.MapData{ + "username": []string{"required", "between:3,8"}, + "email": []string{"required", "min:4", "max:20", "email"}, + "web": []string{"url"}, + "age": []string{"between:18,56"}, + "phone": []string{"digits:11"}, + "agree": []string{"bool"}, + "dob": []string{"date"}, + "village": []string{"between:3,10"}, + "postalCode": []string{"digits:4"}, + "roles": []string{"len:4"}, + } + opts := govalidator.Options{ + Request: r, // request object + Rules: rules, // rules map + Data: &usr, + RequiredDefault: true, // all the field to be required + } + v := govalidator.New(opts) + e := v.ValidateJSON() + fmt.Println(usr) + err := map[string]interface{}{"validationError": e} + w.Header().Set("Content-type", "applciation/json") + json.NewEncoder(w).Encode(err) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Listening on port: 9000") + http.ListenAndServe(":9000", nil) +} + +``` +***Resposne*** +```json +{ + "validationError": { + "email": [ + "The email field must be minimum 4 char", + "The email field must be a valid email address" + ], + "phone": [ + "The phone field must be 11 digits" + ], + "postalCode": [ + "The postalCode field must be 4 digits" + ], + "roles": [ + "The roles field must be length of 4" + ], + "village": [ + "The village field must be between 3 and 10" + ], + "web": [ + "The web field format is invalid" + ] + } +} +``` diff --git a/validationmdl/validationcore/doc/SIMPLE_STRUCT_VALIDATION.md b/validationmdl/validationcore/doc/SIMPLE_STRUCT_VALIDATION.md new file mode 100644 index 0000000000000000000000000000000000000000..be9e8f685ef99dc823c24919060041e532d9a9c2 --- /dev/null +++ b/validationmdl/validationcore/doc/SIMPLE_STRUCT_VALIDATION.md @@ -0,0 +1,79 @@ + +### Validate JSON body into a simple Struct + +When using ValidateJSON you must provide data struct or map, rules and request. You can also pass message rules if you need custom message or localization. + +```go +package main + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/thedevsaddam/govalidator" +) + +type user struct { + Username string `json:"username"` + Email string `json:"email"` + Web string `json:"web"` + Age govalidator.Int `json:"age"` + Agree govalidator.Bool `json:"agree"` +} + +func handler(w http.ResponseWriter, r *http.Request) { + var user user + rules := govalidator.MapData{ + "username": []string{"required", "between:3,5"}, + "email": []string{"required", "min:4", "max:20", "email"}, + "web": []string{"url"}, + "age": []string{"required"}, + "agree": []string{"required"}, + } + + opts := govalidator.Options{ + Request: r, + Data: &user, + Rules: rules, + } + + v := govalidator.New(opts) + e := v.ValidateJSON() + fmt.Println(user) // your incoming JSON data in Go data struct + err := map[string]interface{}{"validationError": e} + w.Header().Set("Content-type", "applciation/json") + json.NewEncoder(w).Encode(err) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Listening on port: 9000") + http.ListenAndServe(":9000", nil) +} + +``` +***Resposne*** +```json +{ + "validationError": { + "age": [ + "The age field is required" + ], + "agree": [ + "The agree field is required" + ], + "email": [ + "The email field is required", + "The email field must be minimum 4 char", + "The email field must be a valid email address" + ], + "username": [ + "The username field is required", + "The username field must be between 3 and 5" + ] + } +} +``` + +#### Note: When using `required` rule with number or boolean data, use provided custom type like: Int, Int64, Float32, Float64 or Bool diff --git a/validationmdl/validationcore/errors.go b/validationmdl/validationcore/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..dd96b3a01ee1e23ab95287cf8ac09d456a602ca0 --- /dev/null +++ b/validationmdl/validationcore/errors.go @@ -0,0 +1,11 @@ +package govalidator + +import "errors" + +var ( + errStringToInt = errors.New("govalidator: unable to parse string to integer") + errStringToFloat = errors.New("govalidator: unable to parse string to float") + 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") +) diff --git a/validationmdl/validationcore/helper.go b/validationmdl/validationcore/helper.go new file mode 100644 index 0000000000000000000000000000000000000000..e4b3f1f99a4067add71269ae1a1c0a937a14f8bf --- /dev/null +++ b/validationmdl/validationcore/helper.go @@ -0,0 +1,152 @@ +package govalidator + +import ( + "encoding/json" + "regexp" +) + +// isAlpha check the input is letters (a-z,A-Z) or not +func isAlpha(str string) bool { + return regexAlpha.MatchString(str) +} + +// isAlphaDash check the input is letters, number with dash and underscore +func isAlphaDash(str string) bool { + return regexAlphaDash.MatchString(str) +} + +// isAlphaNumeric check the input is alpha numeric or not +func isAlphaNumeric(str string) bool { + return regexAlphaNumeric.MatchString(str) +} + +// isBoolean check the input contains boolean type values +// in this case: "0", "1", "true", "false", "True", "False" +func isBoolean(str string) bool { + bools := []string{"0", "1", "true", "false", "True", "False"} + for _, b := range bools { + if b == str { + return true + } + } + return false +} + +//isCreditCard check the provided card number is a valid +// Visa, MasterCard, American Express, Diners Club, Discover or JCB card +func isCreditCard(card string) bool { + return regexCreditCard.MatchString(card) +} + +// isCoordinate is a valid Coordinate or not +func isCoordinate(str string) bool { + return regexCoordinate.MatchString(str) +} + +// isCSSColor is a valid CSS color value (hex, rgb, rgba, hsl, hsla) etc like #909, #00aaff, rgb(255,122,122) +func isCSSColor(str string) bool { + return regexCSSColor.MatchString(str) +} + +// isDate check the date string is valid or not +func isDate(date string) bool { + return regexDate.MatchString(date) +} + +// isDateDDMMYY check the date string is valid or not +func isDateDDMMYY(date string) bool { + return regexDateDDMMYY.MatchString(date) +} + +// isEmail check a email is valid or not +func isEmail(email string) bool { + return regexEmail.MatchString(email) +} + +// isFloat check the input string is a float or not +func isFloat(str string) bool { + return regexFloat.MatchString(str) +} + +// isIn check if the niddle exist in the haystack +func isIn(haystack []string, niddle string) bool { + for _, h := range haystack { + if h == niddle { + return true + } + } + return false +} + +// isJSON check wheather the input string is a valid json or not +func isJSON(str string) bool { + var data interface{} + if err := json.Unmarshal([]byte(str), &data); err != nil { + return false + } + return true +} + +// isNumeric check the provided input string is numeric or not +func isNumeric(str string) bool { + return regexNumeric.MatchString(str) +} + +// isLatitude check the provided input string is a valid latitude or not +func isLatitude(str string) bool { + return regexLatitude.MatchString(str) +} + +// isLongitude check the provided input string is a valid longitude or not +func isLongitude(str string) bool { + return regexLongitude.MatchString(str) +} + +// isIP check the provided input string is a valid IP address or not +func isIP(str string) bool { + return regexIP.MatchString(str) +} + +// isIPV4 check the provided input string is a valid IP address version 4 or not +// Ref: https://en.wikipedia.org/wiki/IPv4 +func isIPV4(str string) bool { + return regexIPV4.MatchString(str) +} + +// isIPV6 check the provided input string is a valid IP address version 6 or not +// Ref: https://en.wikipedia.org/wiki/IPv6 +func isIPV6(str string) bool { + return regexIPV6.MatchString(str) +} + +// isMatchedRegex match the regular expression string provided in first argument +// with second argument which is also a string +func isMatchedRegex(rxStr, str string) bool { + rx := regexp.MustCompile(rxStr) + return rx.MatchString(str) +} + +// isURL check a URL is valid or not +func isURL(url string) bool { + return regexURL.MatchString(url) +} + +// isUUID check the provided string is valid UUID or not +func isUUID(str string) bool { + return regexUUID.MatchString(str) +} + +// isUUID3 check the provided string is valid UUID version 3 or not +func isUUID3(str string) bool { + return regexUUID3.MatchString(str) +} + +// isUUID4 check the provided string is valid UUID version 4 or not +func isUUID4(str string) bool { + return regexUUID4.MatchString(str) +} + +// isUUID5 check the provided string is valid UUID version 5 or not +func isUUID5(str string) bool { + return regexUUID5.MatchString(str) +} diff --git a/validationmdl/validationcore/helper_test.go b/validationmdl/validationcore/helper_test.go new file mode 100644 index 0000000000000000000000000000000000000000..abe98ecf6bcf225d8bb845c227972f85c014ec64 --- /dev/null +++ b/validationmdl/validationcore/helper_test.go @@ -0,0 +1,466 @@ +package govalidator + +import "testing" + +type inputs map[string]bool + +var ( + _alpha = inputs{ + "abcdefghijgklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": true, + "7877**": false, + "abc": true, + ")(^%&)": false, + } + _alphaDash = inputs{ + "abcdefghijgklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-": true, + "John_Do-E": true, + "+=a(0)": false, + } + _alphaNumeric = inputs{ + "abcdefghijgklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890": true, + "090a": true, + "*&*)": false, + } + _boolStringsList = inputs{ + "0": true, + "1": true, + "true": true, + "false": true, + "o": false, + "a": false, + } + // Ref: https://www.freeformatter.com/credit-card-number-generator-validator.html + _creditCardList = inputs{ + "4896644531043572": true, + "2221005631780408": true, + "349902515380498": true, + "6011843157272458": true, + "3543358904915048": true, + "5404269782892303": true, + "4508168417293390": true, + "0604595245598387": false, + "6388244169973297": false, + } + _coordinateList = inputs{ + "30.297018,-78.486328": true, + "40.044438,-104.0625": true, + "58.068581,-99.580078": true, + "abc, xyz": false, + "0, 887": false, + } + _cssColorList = inputs{ + "#000": true, + "#00aaff": true, + "rgb(123,32,12)": true, + "#0": false, + "#av": false, + } + _dateList = inputs{ + "2016-10-14": true, + "2013/02/18": true, + "2020/12/30": true, + "0001/14/30": false, + } + _dateDDMMYYList = inputs{ + "01-01-2000": true, + "28/02/2001": true, + "01/12/2000": true, + "2012/11/30": false, + "201/11/30": false, + } + _emailList = inputs{ + "john@example.com": true, + "thedevsaddam@gmail.com": true, + "jane@yahoo.com": true, + "janeahoo.com": false, + "janea@.com": false, + } + _floatList = inputs{"123": true, "12.50": true, "33.07": true, "abc": false, "o0.45": false} + _roleList = []string{"admin", "manager", "supervisor"} + _validJSONString = `{"FirstName": "Bob", "LastName": "Smith"}` + _invalidJSONString = `{"invalid json"}` + _numericStringList = inputs{"12": true, "09": true, "878": true, "100": true, "a": false, "xyz": false} + _latList = inputs{"30.297018": true, "40.044438": true, "a": false, "xyz": false} + _lonList = inputs{"-78.486328": true, "-104.0625": true, "a": false, "xyz": false} + _ipList = inputs{"10.255.255.255": true, "172.31.255.255": true, "192.168.255.255": true, "a92.168.255.255": false, "172.31.255.25b": false} + _ipV6List = inputs{ + "1200:0000:AB00:1234:0000:2552:7777:1313": true, + "21DA:D3:0:2F3B:2AA:FF:FE28:9C5A": true, + "10.255.255.255": false, + } + _urlList = inputs{ + "http://www.google.com": true, + "https://www.google.com": true, + "https://facebook.com": true, + "yahoo.com": true, + "adca": false, + } + _uuidList = inputs{ + "ee7cf0a0-1922-401b-a1ae-6ec9261484c0": true, + "ee7cf0a0-1922-401b-a1ae-6ec9261484c1": true, + "ee7cf0a0-1922-401b-a1ae-6ec9261484a0": true, + "39888f87-fb62-5988-a425-b2ea63f5b81e": false, + } + _uuidV3List = inputs{ + "a987fbc9-4bed-3078-cf07-9141ba07c9f3": true, + "b987fbc9-4bed-3078-cf07-9141ba07c9f3": true, + "ee7cf0a0-1922-401b-a1ae-6ec9261484c0": false, + } + _uuidV4List = inputs{ + "df7cca36-3d7a-40f4-8f06-ae03cc22f045": true, + "ef7cca36-3d7a-40f4-8f06-ae03cc22f048": true, + "b987fbc9-4bed-3078-cf07-9141ba07c9f3": false, + } + _uuidV5List = inputs{ + "39888f87-fb62-5988-a425-b2ea63f5b81e": true, + "33388f87-fb62-5988-a425-b2ea63f5b81f": true, + "b987fbc9-4bed-3078-cf07-9141ba07c9f3": false, + } +) + +func Test_IsAlpha(t *testing.T) { + for a, s := range _alpha { + if isAlpha(a) != s { + t.Error("IsAlpha failed to determine alpha!") + } + } +} + +func Benchmark_IsAlpha(b *testing.B) { + for n := 0; n < b.N; n++ { + isAlpha("abcdAXZY") + } +} + +func Test_IsAlphaDash(t *testing.T) { + for a, s := range _alphaDash { + if isAlphaDash(a) != s { + t.Error("IsAlphaDash failed to determine alpha dash!") + } + } +} + +func Benchmark_IsAlphaDash(b *testing.B) { + for n := 0; n < b.N; n++ { + isAlphaDash("John_Do-E") + } +} + +func Test_IsAlphaNumeric(t *testing.T) { + for a, s := range _alphaNumeric { + if isAlphaNumeric(a) != s { + t.Error("IsAlphaNumeric failed to determine alpha numeric!") + } + } +} + +func Benchmark_IsAlphaNumeric(b *testing.B) { + for n := 0; n < b.N; n++ { + isAlphaNumeric("abc12AZ") + } +} + +func Test_IsBoolean(t *testing.T) { + for b, s := range _boolStringsList { + if isBoolean(b) != s { + t.Error("IsBoolean failed to determine boolean!") + } + } +} + +func Benchmark_IsBoolean(b *testing.B) { + for n := 0; n < b.N; n++ { + isBoolean("true") + } +} + +func Test_IsCreditCard(t *testing.T) { + for card, state := range _creditCardList { + if isCreditCard(card) != state { + t.Error("IsCreditCard failed to determine credit card!") + } + } +} + +func Benchmark_IsCreditCard(b *testing.B) { + for n := 0; n < b.N; n++ { + isCreditCard("2221005631780408") + } +} + +func Test_IsCoordinate(t *testing.T) { + for c, s := range _coordinateList { + if isCoordinate(c) != s { + t.Error("IsCoordinate failed to determine coordinate!") + } + } +} + +func Benchmark_IsCoordinate(b *testing.B) { + for n := 0; n < b.N; n++ { + isCoordinate("30.297018,-78.486328") + } +} + +func Test_IsCSSColor(t *testing.T) { + for c, s := range _cssColorList { + if isCSSColor(c) != s { + t.Error("IsCSSColor failed to determine css color code!") + } + } +} + +func Benchmark_IsCSSColor(b *testing.B) { + for n := 0; n < b.N; n++ { + isCSSColor("#00aaff") + } +} + +func Test_IsDate(t *testing.T) { + for d, s := range _dateList { + if isDate(d) != s { + t.Error("IsDate failed to determine date!") + } + } +} + +func Benchmark_IsDate(b *testing.B) { + for n := 0; n < b.N; n++ { + isDate("2016-10-14") + } +} + +func Test_IsDateDDMMYY(t *testing.T) { + for d, s := range _dateDDMMYYList { + if isDateDDMMYY(d) != s { + t.Error("IsDateDDMMYY failed to determine date!") + } + } +} + +func Benchmark_IsDateDDMMYY(b *testing.B) { + for n := 0; n < b.N; n++ { + isDateDDMMYY("23-10-2014") + } +} + +func Test_IsEmail(t *testing.T) { + for e, s := range _emailList { + if isEmail(e) != s { + t.Error("IsEmail failed to determine email!") + } + } +} + +func Benchmark_IsEmail(b *testing.B) { + for n := 0; n < b.N; n++ { + isEmail("thedevsaddam@gmail.com") + } +} + +func Test_IsFloat(t *testing.T) { + for f, s := range _floatList { + if isFloat(f) != s { + t.Error("IsFloat failed to determine float value!") + } + } +} + +func Benchmark_IsFloat(b *testing.B) { + for n := 0; n < b.N; n++ { + isFloat("123.001") + } +} + +func Test_IsIn(t *testing.T) { + if !isIn(_roleList, "admin") { + t.Error("IsIn failed!") + } +} + +func Benchmark_IsIn(b *testing.B) { + for n := 0; n < b.N; n++ { + isIn(_roleList, "maager") + } +} + +func Test_IsJSON(t *testing.T) { + if !isJSON(_validJSONString) { + t.Error("IsJSON failed!") + } + if isJSON(_invalidJSONString) { + t.Error("IsJSON unable to detect invalid json!") + } +} + +func Benchmark_IsJSON(b *testing.B) { + for n := 0; n < b.N; n++ { + isJSON(_validJSONString) + } +} + +func Test_IsNumeric(t *testing.T) { + for n, s := range _numericStringList { + if isNumeric(n) != s { + t.Error("IsNumeric failed!") + } + } +} + +func Benchmark_IsNumeric(b *testing.B) { + for n := 0; n < b.N; n++ { + isNumeric("123") + } +} + +func Test_IsLatitude(t *testing.T) { + for n, s := range _latList { + if isLatitude(n) != s { + t.Error("IsLatitude failed!") + } + } +} + +func Benchmark_IsLatitude(b *testing.B) { + for n := 0; n < b.N; n++ { + isLatitude("30.297018") + } +} + +func Test_IsLongitude(t *testing.T) { + for n, s := range _lonList { + if isLongitude(n) != s { + t.Error("IsLongitude failed!") + } + } +} + +func Benchmark_IsLongitude(b *testing.B) { + for n := 0; n < b.N; n++ { + isLongitude("-78.486328") + } +} + +func Test_IsIP(t *testing.T) { + for i, s := range _ipList { + if isIP(i) != s { + t.Error("IsIP failed!") + } + } +} + +func Benchmark_IsIP(b *testing.B) { + for n := 0; n < b.N; n++ { + isIP("10.255.255.255") + } +} + +func Test_IsIPV4(t *testing.T) { + for i, s := range _ipList { + if isIPV4(i) != s { + t.Error("IsIPV4 failed!") + } + } +} + +func Benchmark_IsIPV4(b *testing.B) { + for n := 0; n < b.N; n++ { + isIPV4("10.255.255.255") + } +} + +func Test_IsIPV6(t *testing.T) { + for i, s := range _ipV6List { + if isIPV6(i) != s { + t.Error("IsIPV4 failed!") + } + } +} + +func Benchmark_IsIPV6(b *testing.B) { + for n := 0; n < b.N; n++ { + isIPV6("10.255.255.255") + } +} + +func Test_IsMatchedRegex(t *testing.T) { + if !isMatchedRegex("^(name|age)$", "name") { + t.Error("IsMatchedRegex failed!") + } +} + +func Benchmark_IsMatchedRegex(b *testing.B) { + for n := 0; n < b.N; n++ { + isMatchedRegex("^(name|age)$", "name") + } +} + +func Test_IsURL(t *testing.T) { + for u, s := range _urlList { + if isURL(u) != s { + t.Error("IsURL failed!") + } + } +} + +func Benchmark_IsURL(b *testing.B) { + for n := 0; n < b.N; n++ { + isURL("https://www.facebook.com") + } +} + +func Test_IsUUID(t *testing.T) { + for u, s := range _uuidList { + if isUUID(u) != s { + t.Error("IsUUID failed!") + } + } +} + +func Benchmark_IsUUID(b *testing.B) { + for n := 0; n < b.N; n++ { + isUUID("ee7cf0a0-1922-401b-a1ae-6ec9261484c0") + } +} + +func Test_IsUUID3(t *testing.T) { + for u, s := range _uuidV3List { + if isUUID3(u) != s { + t.Error("IsUUID3 failed!") + } + } +} + +func Benchmark_IsUUID3(b *testing.B) { + for n := 0; n < b.N; n++ { + isUUID3("a987fbc9-4bed-3078-cf07-9141ba07c9f3") + } +} + +func Test_IsUUID4(t *testing.T) { + for u, s := range _uuidV4List { + if isUUID4(u) != s { + t.Error("IsUUID4 failed!") + } + } +} + +func Benchmark_IsUUID4(b *testing.B) { + for n := 0; n < b.N; n++ { + isUUID4("57b73598-8764-4ad0-a76a-679bb6640eb1") + } +} + +func Test_IsUUID5(t *testing.T) { + for u, s := range _uuidV5List { + if isUUID5(u) != s { + t.Error("IsUUID5 failed!") + } + } +} + +func Benchmark_IsUUID5(b *testing.B) { + for n := 0; n < b.N; n++ { + isUUID5("987fbc97-4bed-5078-9f07-9141ba07c9f3") + } +} diff --git a/validationmdl/validationcore/regex_patterns.go b/validationmdl/validationcore/regex_patterns.go new file mode 100644 index 0000000000000000000000000000000000000000..bdb5f2b4f0e937daed3b2c583b98d6cb9a5124e5 --- /dev/null +++ b/validationmdl/validationcore/regex_patterns.go @@ -0,0 +1,74 @@ +package govalidator + +import ( + "regexp" +) + +const ( + // Alpha represents regular expression for alpha chartacters + Alpha string = "^[a-zA-Z]+$" + // AlphaDash represents regular expression for alpha chartacters with underscore and ash + AlphaDash string = "^[a-zA-Z0-9_-]+$" + // AlphaNumeric represents regular expression for alpha numeric chartacters + AlphaNumeric string = "^[a-zA-Z0-9]+$" + // CreditCard represents regular expression for credit cards like (Visa, MasterCard, American Express, Diners Club, Discover, and JCB cards). Ref: https://stackoverflow.com/questions/9315647/regex-credit-card-number-tests + CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|[25][1-7][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$" + // Coordinate represents latitude and longitude regular expression + Coordinate string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?),\\s*[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" // Ref: https://stackoverflow.com/questions/3518504/regular-expression-for-matching-latitude-longitude-coordinates + // CSSColor represents css valid color code with hex, rgb, rgba, hsl, hsla etc. Ref: http://www.regexpal.com/97509 + CSSColor string = "^(#([\\da-f]{3}){1,2}|(rgb|hsl)a\\((\\d{1,3}%?,\\s?){3}(1|0?\\.\\d+)\\)|(rgb|hsl)\\(\\d{1,3}%?(,\\s?\\d{1,3}%?){2}\\))$" + // Date represents regular expression for valid date like: yyyy-mm-dd + Date string = "^(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[469]|11)[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[13578]|1[02])[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))$" + // DateDDMMYY represents regular expression for valid date of format dd/mm/yyyy , dd-mm-yyyy etc.Ref: http://regexr.com/346hf + DateDDMMYY string = "^(0?[1-9]|[12][0-9]|3[01])[\\/\\-](0?[1-9]|1[012])[\\/\\-]\\d{4}$" + // Email represents regular expression for email + Email string = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$" + // Float represents regular expression for finding fload number + Float string = "^[+-]?([0-9]*[.])?[0-9]+$" + // IP represents regular expression for ip address + IP string = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + // IPV4 represents regular expression for ip address version 4 + IPV4 string = "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$" + // IPV6 represents regular expression for ip address version 6 + IPV6 string = `^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$` + // Latitude represents latitude regular expression + 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})?))$" + // Numeric represents regular expression for numeric + Numeric string = "^[0-9]+$" + // URL represents regular expression for url + URL string = "^(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$" // Ref: https://stackoverflow.com/questions/136505/searching-for-uuids-in-text-with-regex + // UUID represents regular expression for UUID + UUID string = "^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$" + // UUID3 represents regular expression for UUID version 3 + UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" + // UUID4 represents regular expression for UUID version 4 + UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + // UUID5 represents regular expression for UUID version 5 + UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" +) + +var ( + regexAlpha = regexp.MustCompile(Alpha) + regexAlphaDash = regexp.MustCompile(AlphaDash) + regexAlphaNumeric = regexp.MustCompile(AlphaNumeric) + regexCreditCard = regexp.MustCompile(CreditCard) + regexCoordinate = regexp.MustCompile(Coordinate) + regexCSSColor = regexp.MustCompile(CSSColor) + regexDate = regexp.MustCompile(Date) + regexDateDDMMYY = regexp.MustCompile(DateDDMMYY) + regexEmail = regexp.MustCompile(Email) + regexFloat = regexp.MustCompile(Float) + regexNumeric = regexp.MustCompile(Numeric) + regexLatitude = regexp.MustCompile(Latitude) + regexLongitude = regexp.MustCompile(Longitude) + regexIP = regexp.MustCompile(IP) + regexIPV4 = regexp.MustCompile(IPV4) + regexIPV6 = regexp.MustCompile(IPV6) + regexURL = regexp.MustCompile(URL) + regexUUID = regexp.MustCompile(UUID) + regexUUID3 = regexp.MustCompile(UUID3) + regexUUID4 = regexp.MustCompile(UUID4) + regexUUID5 = regexp.MustCompile(UUID5) +) diff --git a/validationmdl/validationcore/roller.go b/validationmdl/validationcore/roller.go new file mode 100644 index 0000000000000000000000000000000000000000..a200f0451585124422da3dcddee2998fbb3acb04 --- /dev/null +++ b/validationmdl/validationcore/roller.go @@ -0,0 +1,300 @@ +package govalidator + +import ( + "reflect" + "strings" +) + +// ROADMAP +// traverse map or struct +// detect each type +// if type is struct or map then traverse it +// if type is not struct or map then just push them in parent map's key as key and value of it +// make flatten all the type in map[string]interface{} +// in this case mapWalker will do the task + +// roller represents a roller type that will be used to flatten our data in a map[string]interface{} +type roller struct { + root map[string]interface{} + typeName string + tagIdentifier string + tagSeparator string +} + +// start start traversing through the tree +func (r *roller) start(iface interface{}) { + //initialize the Tree + r.root = make(map[string]interface{}) + r.typeName = "" + ifv := reflect.ValueOf(iface) + ift := reflect.TypeOf(iface) + if ift.Kind() == reflect.Ptr { + ifv = ifv.Elem() + ift = ift.Elem() + } + canInterface := ifv.CanInterface() + //check the provided root elment + switch ift.Kind() { + case reflect.Struct: + if canInterface { + r.traverseStruct(ifv.Interface()) + } + case reflect.Map: + if ifv.Len() > 0 { + if canInterface { + r.traverseMap(ifv.Interface()) + } + } + case reflect.Slice: + if canInterface { + r.push("slice", ifv.Interface()) + } + } +} + +// setTagIdentifier set the struct tag identifier. e.g: json, validate etc +func (r *roller) setTagIdentifier(i string) { + r.tagIdentifier = i +} + +// setTagSeparator set the struct tag separator. e.g: pipe (|) or comma (,) +func (r *roller) setTagSeparator(s string) { + r.tagSeparator = s +} + +// getFlatMap get the all flatten values +func (r *roller) getFlatMap() map[string]interface{} { + return r.root +} + +// getFlatVal return interfac{} value if exist +func (r *roller) getFlatVal(key string) (interface{}, bool) { + var val interface{} + var ok bool + if val, ok = r.root[key]; ok { + return val, ok + } + return val, ok +} + +// push add value to map if key does not exist +func (r *roller) push(key string, val interface{}) bool { + if _, ok := r.root[key]; ok { + return false + } + r.root[key] = val + return true +} + +// traverseStruct through all structs and add it to root +func (r *roller) traverseStruct(iface interface{}) { + ifv := reflect.ValueOf(iface) + ift := reflect.TypeOf(iface) + + if ift.Kind() == reflect.Ptr { + ifv = ifv.Elem() + ift = ift.Elem() + } + + for i := 0; i < ift.NumField(); i++ { + v := ifv.Field(i) + rfv := ift.Field(i) + + switch v.Kind() { + case reflect.Struct: + var typeName string + if len(rfv.Tag.Get(r.tagIdentifier)) > 0 { + tags := strings.Split(rfv.Tag.Get(r.tagIdentifier), r.tagSeparator) + if tags[0] != "-" { + typeName = tags[0] + } + } else { + typeName = rfv.Name + } + if v.CanInterface() { + switch v.Type().String() { + case "govalidator.Int": + r.push(typeName, v.Interface()) + case "govalidator.Int64": + r.push(typeName, v.Interface()) + case "govalidator.Float32": + r.push(typeName, v.Interface()) + case "govalidator.Float64": + r.push(typeName, v.Interface()) + case "govalidator.Bool": + r.push(typeName, v.Interface()) + default: + r.typeName = ift.Name() + r.traverseStruct(v.Interface()) + } + } + case reflect.Map: + if v.CanInterface() { + r.traverseMap(v.Interface()) + } + case reflect.Ptr: // if the field inside struct is Ptr then get the type and underlying values as interface{} + ptrReflectionVal := reflect.Indirect(v) + if !isEmpty(ptrReflectionVal) { + ptrField := ptrReflectionVal.Type() + switch ptrField.Kind() { + case reflect.Struct: + if v.CanInterface() { + r.traverseStruct(v.Interface()) + } + case reflect.Map: + if v.CanInterface() { + r.traverseMap(v.Interface()) + } + } + } + default: + if len(rfv.Tag.Get(r.tagIdentifier)) > 0 { + tags := strings.Split(rfv.Tag.Get(r.tagIdentifier), r.tagSeparator) + // add if first tag is not hyphen + if tags[0] != "-" { + if v.CanInterface() { + r.push(tags[0], v.Interface()) + } + } + } else { + if v.Kind() == reflect.Ptr { + if ifv.CanInterface() { + r.push(ift.Name()+"."+rfv.Name, ifv.Interface()) + } + } else { + if v.CanInterface() { + r.push(ift.Name()+"."+rfv.Name, v.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 iface.(type) { + case map[string]interface{}: + for k, v := range iface.(map[string]interface{}) { + switch reflect.TypeOf(v).Kind() { + case reflect.Struct: + r.typeName = k // set the map key as name + r.traverseStruct(v) + case reflect.Map: + r.typeName = k // set the map key as name + r.traverseMap(v) + case reflect.Ptr: // if the field inside map is Ptr then get the type and underlying values as interface{} + switch reflect.TypeOf(v).Elem().Kind() { + case reflect.Struct: + r.traverseStruct(v) + case reflect.Map: + switch v.(type) { + case *map[string]interface{}: + r.traverseMap(*v.(*map[string]interface{})) + case *map[string]string: + r.traverseMap(*v.(*map[string]string)) + case *map[string]bool: + r.traverseMap(*v.(*map[string]bool)) + case *map[string]int: + r.traverseMap(*v.(*map[string]int)) + case *map[string]int8: + r.traverseMap(*v.(*map[string]int8)) + case *map[string]int16: + r.traverseMap(*v.(*map[string]int16)) + case *map[string]int32: + r.traverseMap(*v.(*map[string]int32)) + case *map[string]int64: + r.traverseMap(*v.(*map[string]int64)) + case *map[string]float32: + r.traverseMap(*v.(*map[string]float32)) + case *map[string]float64: + r.traverseMap(*v.(*map[string]float64)) + case *map[string]uint: + r.traverseMap(*v.(*map[string]uint)) + case *map[string]uint8: + r.traverseMap(*v.(*map[string]uint8)) + case *map[string]uint16: + r.traverseMap(*v.(*map[string]uint16)) + case *map[string]uint32: + r.traverseMap(*v.(*map[string]uint32)) + case *map[string]uint64: + r.traverseMap(*v.(*map[string]uint64)) + case *map[string]uintptr: + r.traverseMap(*v.(*map[string]uintptr)) + } + default: + r.push(k, v.(interface{})) + } + default: + r.push(k, v) + } + } + case map[string]string: + for k, v := range iface.(map[string]string) { + r.push(k, v) + } + case map[string]bool: + for k, v := range iface.(map[string]bool) { + r.push(k, v) + } + case map[string]int: + for k, v := range iface.(map[string]int) { + r.push(k, v) + } + case map[string]int8: + for k, v := range iface.(map[string]int8) { + r.push(k, v) + } + case map[string]int16: + for k, v := range iface.(map[string]int16) { + r.push(k, v) + } + case map[string]int32: + for k, v := range iface.(map[string]int32) { + r.push(k, v) + } + case map[string]int64: + for k, v := range iface.(map[string]int64) { + r.push(k, v) + } + case map[string]float32: + for k, v := range iface.(map[string]float32) { + r.push(k, v) + } + case map[string]float64: + for k, v := range iface.(map[string]float64) { + r.push(k, v) + } + case map[string]uint: + for k, v := range iface.(map[string]uint) { + r.push(k, v) + } + case map[string]uint8: + for k, v := range iface.(map[string]uint8) { + r.push(k, v) + } + case map[string]uint16: + for k, v := range iface.(map[string]uint16) { + r.push(k, v) + } + case map[string]uint32: + for k, v := range iface.(map[string]uint32) { + r.push(k, v) + } + case map[string]uint64: + for k, v := range iface.(map[string]uint64) { + r.push(k, v) + } + case map[string]uintptr: + for k, v := range iface.(map[string]uintptr) { + r.push(k, v) + } + } +} diff --git a/validationmdl/validationcore/roller_test.go b/validationmdl/validationcore/roller_test.go new file mode 100644 index 0000000000000000000000000000000000000000..30e2032bc74c67e6ef0e5e51385383ea2d8e3015 --- /dev/null +++ b/validationmdl/validationcore/roller_test.go @@ -0,0 +1,411 @@ +package govalidator + +import ( + "testing" +) + +type Earth struct { + Human + Name string + Liveable bool + Planet map[string]interface{} +} + +type Human struct { + Male + Female +} + +type Male struct { + Name string + Age int +} + +type Female struct { + Name string + Age int +} + +type deepLevel struct { + Deep string + Levels map[string]string +} + +type structWithTag struct { + Name string `validate:"name"` + Age int `validate:"age"` +} + +var p = map[string]interface{}{ + "naam": "Jane", + "bois": 29, + "white": true, +} +var dl = deepLevel{ + Deep: "So much deep", + Levels: map[string]string{ + "level 1": "20 m", + "level 2": "30 m", + "level 3": "80 m", + "level 4": "103 m", + }, +} +var planet = map[string]interface{}{ + "name": "mars", + "age": 1000, + "red": true, + "deepLevel": dl, + "p": p, +} +var male = Male{"John", 33} +var female = Female{"Jane", 30} +var h = Human{ + male, + female, +} +var e = Earth{ + h, + "green earth", + true, + planet, +} + +var m = make(map[string]interface{}) + +type structWithPointerToEmbeddedStruct struct { + Male *Male + Female *Female + Planet *map[string]interface{} +} + +func init() { + m["earth"] = e + m["person"] = "John Doe" + m["iface"] = map[string]string{"another_person": "does it change root!"} + m["array"] = [5]int{1, 4, 5, 6, 7} +} + +func TestRoller_push(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(male) + if r.push("Male.Name", "set new name") != false { + t.Error("push failed!") + } +} + +func TestRoller_Start(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(m) + if len(r.getFlatMap()) != 20 { + t.Error("Start failed!") + } +} + +func BenchmarkRoller_Start(b *testing.B) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + for n := 0; n < b.N; n++ { + r.start(m) + } +} + +func Test_Roller_Start_empty_map(t *testing.T) { + r := roller{} + emap := make(map[string]interface{}, 0) + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(emap) + if len(r.getFlatMap()) > 0 { + t.Error("Failed to validate empty map") + } +} + +func TestRoller_traverseStructWithEmbeddedPointerStructAndMap(t *testing.T) { + r := roller{} + s := structWithPointerToEmbeddedStruct{ + &male, + &female, + &p, + } + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(s) + if len(r.getFlatMap()) != 4 { + t.Error("traverseStructWithEmbeddedPointerStructAndMap failed!") + } +} + +func TestRoller_traverseMapWithPointerStructAndMap(t *testing.T) { + r := roller{} + mapOfPointerVals := map[string]interface{}{ + "structField": male, + "structPointerField": &female, + "mapPointerField": &p, + } + + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(mapOfPointerVals) + if len(r.getFlatMap()) != 7 { + t.Error("traverseMapWithPointerStructAndMap failed!") + } +} + +func TestRoller_StartPointerToStruct(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(&male) + if len(r.getFlatMap()) != 2 { + t.Error("StartPointerToStruct failed!") + } +} + +func TestRoller_StartMap(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(m) + if len(r.getFlatMap()) != 20 { + t.Error("StartMap failed!") + } +} + +func TestRoller_StartPointerToMap(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(&p) + if len(r.getFlatMap()) != 3 { + t.Error("StartPointerToMap failed!") + } +} + +func TestRoller_StartStruct(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(h) + + if len(r.getFlatMap()) != 4 { + t.Error("StartStruct failed!") + } +} + +func TestRoller_StartStructWithTag(t *testing.T) { + r := roller{} + swTag := structWithTag{"John", 44} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(swTag) + + if len(r.getFlatMap()) != 2 { + t.Error("StartStructWithTag failed!") + } +} + +func TestRoller_StartStructPointerWithTag(t *testing.T) { + r := roller{} + swTag := structWithTag{"John", 44} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(&swTag) + + if len(r.getFlatMap()) != 2 { + t.Error("StartStructPointerWithTag failed!") + } +} + +func TestRoller_GetFlatVal(t *testing.T) { + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(m) + + //check struct field with string + name, _ := r.getFlatVal("Male.Name") + if name != "John" { + t.Error("GetFlatVal failed for struct string field!") + } + + //check struct field with int + age, _ := r.getFlatVal("Male.Age") + if age != 33 { + t.Error("GetFlatVal failed for struct int field!") + } + + //check struct field with array + intArrOf5, _ := r.getFlatVal("array") + if len(intArrOf5.([5]int)) != 5 { + t.Error("GetFlatVal failed for struct array of [5]int field!") + } + + //check map key of string + person, _ := r.getFlatVal("person") + if person != "John Doe" { + t.Error("GetFlatVal failed for map[string]string!") + } + + //check not existed key + _, ok := r.getFlatVal("not_existed_key") + if ok { + t.Error("GetFlatVal failed for not available key!") + } +} + +func TestRoller_PremitiveDataType(t *testing.T) { + mStr := map[string]string{"oneStr": "hello", "twoStr": "Jane", "threeStr": "Doe"} + mBool := map[string]bool{"oneBool": true, "twoBool": false, "threeBool": true} + mInt := map[string]int{"oneInt": 1, "twoInt": 2, "threeInt": 3} + mInt8 := map[string]int8{"oneInt8": 1, "twoInt8": 2, "threeInt8": 3} + mInt16 := map[string]int16{"oneInt16": 1, "twoInt16": 2, "threeInt16": 3} + mInt32 := map[string]int32{"oneInt32": 1, "twoInt32": 2, "threeInt32": 3} + mInt64 := map[string]int64{"oneInt64": 1, "twoInt64": 2, "threeInt64": 3} + mFloat32 := map[string]float32{"onefloat32": 1.09, "twofloat32": 20.87, "threefloat32": 11.3} + mFloat64 := map[string]float64{"onefloat64": 10.88, "twofloat64": 92.09, "threefloat64": 3.90} + mUintptr := map[string]uintptr{"oneUintptr": 1, "twoUintptr": 2, "threeUintptr": 3} + mUint := map[string]uint{"oneUint": 1, "twoUint": 2, "threeUint": 3} + mUint8 := map[string]uint8{"oneUint8": 1, "twoUint8": 2, "threeUint8": 3} + mUint16 := map[string]uint16{"oneUint16": 1, "twoUint16": 2, "threeUint16": 3} + mUint32 := map[string]uint32{"oneUint32": 1, "twoUint32": 2, "threeUint32": 3} + mUint64 := map[string]uint64{"oneUint64": 1, "twoUint64": 2, "threeUint64": 3} + mComplex := map[string]interface{}{ + "ptrToMapString": &mStr, + "ptrToMapBool": &mBool, + "ptrToMapInt": &mInt, + "ptrToMapInt8": &mInt8, + "ptrToMapInt16": &mInt16, + "ptrToMapInt32": &mInt32, + "ptrToMapInt64": &mInt64, + "ptrToMapfloat32": &mFloat32, + "ptrToMapfloat64": &mFloat64, + "ptrToMapUintptr": &mUintptr, + "ptrToMapUint": &mUint, + "ptrToMapUint8": &mUint8, + "ptrToMapUint16": &mUint16, + "ptrToMapUint32": &mUint32, + "ptrToMapUint64": &mUint64, + } + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(mComplex) + itemsLen := len(mComplex) * 3 + if len(r.getFlatMap()) != itemsLen { + t.Error("PremitiveDataType failed!") + } +} + +func TestRoller_sliceOfType(t *testing.T) { + males := []Male{ + {Name: "John", Age: 29}, + {Name: "Jane", Age: 23}, + {Name: "Tom", Age: 10}, + } + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(males) + i, _ := r.getFlatVal("slice") + if len(i.([]Male)) != len(males) { + t.Error("slice failed!") + } +} + +func TestRoller_ptrSliceOfType(t *testing.T) { + males := []Male{ + {Name: "John", Age: 29}, + {Name: "Jane", Age: 23}, + {Name: "Tom", Age: 10}, + } + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(&males) + i, _ := r.getFlatVal("slice") + if len(i.([]Male)) != len(males) { + t.Error("slice failed!") + } +} + +func TestRoller_MapWithPointerPremitives(t *testing.T) { + type customType string + var str string + var varInt int + var varInt8 int8 + var varInt16 int16 + var varInt32 int32 + var varInt64 int64 + var varFloat32 float32 + var varFloat64 float64 + var varUint uint + var varUint8 uint8 + var varUint16 uint16 + var varUint32 uint32 + var varUint64 uint64 + var varUintptr uintptr + var x customType = "custom" + y := []string{"y", "z"} + + males := map[string]interface{}{ + "string": &str, + "int": &varInt, + "int8": &varInt8, + "int16": &varInt16, + "int32": &varInt32, + "int64": &varInt64, + "float32": &varFloat32, + "float64": &varFloat64, + "uint": &varUint, + "uint8": &varUint8, + "uint16": &varUint16, + "uint32": &varUint32, + "uint64": &varUint64, + "uintPtr": &varUintptr, + "customType": &x, + "y": &y, + } + r := roller{} + r.setTagIdentifier("validate") + r.setTagSeparator("|") + r.start(males) + + val, _ := r.getFlatVal("customType") + if *val.(*customType) != "custom" { + t.Error("fetching custom type value failed!") + } + + valY, _ := r.getFlatVal("y") + if len(*valY.(*[]string)) != len(y) { + t.Error("fetching pointer to struct value failed!") + } + + if len(r.getFlatMap()) != len(males) { + t.Error("MapWithPointerPremitives failed!") + } +} + +type structWithCustomType struct { + Name string `json:"name"` + Integer Int `json:"integer"` + Integer64 Int64 `json:"integer64"` + Fpoint32 Float32 `json:"float32"` + Fpoint64 Float64 `json:"float64"` + Boolean Bool `json:"bool"` +} + +func TestRoller_StartCustomType(t *testing.T) { + r := roller{} + swTag := structWithCustomType{Name: "John Doe", Integer: Int{Value: 44}} + r.setTagIdentifier("json") + r.setTagSeparator("|") + r.start(&swTag) + if len(r.getFlatMap()) != 6 { + t.Error("failed to push custom type") + } +} diff --git a/validationmdl/validationcore/rules.go b/validationmdl/validationcore/rules.go new file mode 100644 index 0000000000000000000000000000000000000000..3a384257701debad0bc53d8d9170527e0ba8b8ae --- /dev/null +++ b/validationmdl/validationcore/rules.go @@ -0,0 +1,1020 @@ +package govalidator + +import ( + "errors" + "fmt" + "mime/multipart" + "net/url" + "reflect" + "strconv" + "strings" +) + +// var mutex = &sync.Mutex{} +// var Cnt = 0 + +var rulesFuncMap = make(map[string]func(string, string, string, interface{}) error) + +// AddCustomRule help to add custom rules for validator +// First argument it takes the rule name and second arg a func +// Second arg must have this signature below +// fn func(name string, fn func(field string, rule string, message string, value interface{}) error +// see example in readme: https://github.com/thedevsaddam/govalidator#add-custom-rules +func AddCustomRule(name string, fn func(field string, rule string, message string, value interface{}) error) { + if isRuleExist(name) { + panic(fmt.Errorf("govalidator: %s is already defined in rules", name)) + } + rulesFuncMap[name] = fn +} + +// validateCustomRules validate custom rules +func validateCustomRules(field string, rule string, message string, value interface{}, errsBag url.Values) { + + // loggermdl.LogDebug(field, " - ", rule) + // mutex.Lock() + // Cnt++ + // mutex.Unlock() + + for k, v := range rulesFuncMap { + if k == rule || strings.HasPrefix(rule, k+":") { + err := v(field, rule, message, value) + if err != nil { + errsBag.Add(field, err.Error()) + } + break + } + } +} + +func init() { + + // Required check the Required fields + AddCustomRule("required", func(field, rule, message string, value interface{}) error { + err := fmt.Errorf("The %s field is required", field) + if message != "" { + err = errors.New(message) + } + if value == nil { + return err + } + if _, ok := value.(multipart.File); ok { + return nil + } + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.String, reflect.Array, reflect.Slice, reflect.Map: + if rv.Len() == 0 { + return err + } + case reflect.Int: + if isEmpty(value.(int)) { + return err + } + case reflect.Int8: + if isEmpty(value.(int8)) { + return err + } + case reflect.Int16: + if isEmpty(value.(int16)) { + return err + } + case reflect.Int32: + if isEmpty(value.(int32)) { + return err + } + case reflect.Int64: + if isEmpty(value.(int64)) { + return err + } + case reflect.Float32: + if isEmpty(value.(float32)) { + return err + } + case reflect.Float64: + if isEmpty(value.(float64)) { + return err + } + case reflect.Uint: + if isEmpty(value.(uint)) { + return err + } + case reflect.Uint8: + if isEmpty(value.(uint8)) { + return err + } + case reflect.Uint16: + if isEmpty(value.(uint16)) { + return err + } + case reflect.Uint32: + if isEmpty(value.(uint32)) { + return err + } + case reflect.Uint64: + if isEmpty(value.(uint64)) { + return err + } + case reflect.Uintptr: + if isEmpty(value.(uintptr)) { + return err + } + case reflect.Struct: + switch rv.Type().String() { + case "govalidator.Int": + if v, ok := value.(Int); ok { + if !v.IsSet { + return err + } + } + case "govalidator.Int64": + if v, ok := value.(Int64); ok { + if !v.IsSet { + return err + } + } + case "govalidator.Float32": + if v, ok := value.(Float32); ok { + if !v.IsSet { + return err + } + } + case "govalidator.Float64": + if v, ok := value.(Float64); ok { + if !v.IsSet { + return err + } + } + case "govalidator.Bool": + if v, ok := value.(Bool); ok { + if !v.IsSet { + return err + } + } + default: + panic("govalidator: invalid custom type for required rule") + + } + + default: + panic("govalidator: invalid type for required rule") + + } + return nil + }) + + // Regex check the custom Regex rules + // Regex:^[a-zA-Z]+$ means this field can only contain alphabet (a-z and A-Z) + AddCustomRule("regex", func(field, rule, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field format is invalid", field) + if message != "" { + err = errors.New(message) + } + rxStr := strings.TrimPrefix(rule, "regex:") + if !isMatchedRegex(rxStr, str) { + return err + } + return nil + }) + + // Alpha check if provided field contains valid letters + AddCustomRule("alpha", func(field string, vlaue string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s may only contain letters", field) + if message != "" { + err = errors.New(message) + } + if !isAlpha(str) { + return err + } + return nil + }) + + // AlphaDash check if provided field contains valid letters, numbers, underscore and dash + AddCustomRule("alpha_dash", func(field string, vlaue string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s may only contain letters, numbers, and dashes", field) + if message != "" { + err = errors.New(message) + } + if !isAlphaDash(str) { + return err + } + return nil + }) + + // AlphaNumeric check if provided field contains valid letters and numbers + AddCustomRule("alpha_num", func(field string, vlaue string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s may only contain letters and numbers", field) + if message != "" { + err = errors.New(message) + } + if !isAlphaNumeric(str) { + return err + } + return nil + }) + + // Boolean check if provided field contains Boolean + // in this case: "0", "1", 0, 1, "true", "false", true, false etc + AddCustomRule("bool", func(field string, vlaue string, message string, value interface{}) error { + err := fmt.Errorf("The %s may only contain boolean value, string or int 0, 1", field) + if message != "" { + err = errors.New(message) + } + switch value.(type) { + case bool: + //if value is boolean then pass + case string: + if !isBoolean(value.(string)) { + return err + } + case int: + v := value.(int) + if v != 0 && v != 1 { + return err + } + case int8: + v := value.(int8) + if v != 0 && v != 1 { + return err + } + case int16: + v := value.(int16) + if v != 0 && v != 1 { + return err + } + case int32: + v := value.(int32) + if v != 0 && v != 1 { + return err + } + case int64: + v := value.(int64) + if v != 0 && v != 1 { + return err + } + case uint: + v := value.(uint) + if v != 0 && v != 1 { + return err + } + case uint8: + v := value.(uint8) + if v != 0 && v != 1 { + return err + } + case uint16: + v := value.(uint16) + if v != 0 && v != 1 { + return err + } + case uint32: + v := value.(uint32) + if v != 0 && v != 1 { + return err + } + case uint64: + v := value.(uint64) + if v != 0 && v != 1 { + return err + } + case uintptr: + v := value.(uintptr) + if v != 0 && v != 1 { + return err + } + } + return nil + }) + + // Between check the fields character length range + // if the field is array, map, slice then the valdiation rule will be the length of the data + // if the value is int or float then the valdiation rule will be the value comparison + AddCustomRule("between", func(field string, rule string, message string, value interface{}) error { + rng := strings.Split(strings.TrimPrefix(rule, "between:"), ",") + if len(rng) != 2 { + panic(errInvalidArgument) + } + minFloat, err := strconv.ParseFloat(rng[0], 64) + if err != nil { + panic(errStringToInt) + } + maxFloat, err := strconv.ParseFloat(rng[1], 64) + if err != nil { + panic(errStringToInt) + } + min := int(minFloat) + + max := int(maxFloat) + + err = fmt.Errorf("The %s field must be between %d and %d", field, min, max) + if message != "" { + err = errors.New(message) + } + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.String, reflect.Array, reflect.Map, reflect.Slice: + inLen := rv.Len() + if !(inLen >= min && inLen <= max) { + return err + } + case reflect.Int: + in := value.(int) + if !(in >= min && in <= max) { + return err + } + case reflect.Int8: + in := int(value.(int8)) + if !(in >= min && in <= max) { + return err + } + case reflect.Int16: + in := int(value.(int16)) + if !(in >= min && in <= max) { + return err + } + case reflect.Int32: + in := int(value.(int32)) + if !(in >= min && in <= max) { + return err + } + case reflect.Int64: + in := int(value.(int64)) + if !(in >= min && in <= max) { + return err + } + case reflect.Uint: + in := int(value.(uint)) + if !(in >= min && in <= max) { + return err + } + case reflect.Uint8: + in := int(value.(uint8)) + if !(in >= min && in <= max) { + return err + } + case reflect.Uint16: + in := int(value.(uint16)) + if !(in >= min && in <= max) { + return err + } + case reflect.Uint32: + in := int(value.(uint32)) + if !(in >= min && in <= max) { + return err + } + case reflect.Uint64: + in := int(value.(uint64)) + if !(in >= min && in <= max) { + return err + } + case reflect.Uintptr: + in := int(value.(uintptr)) + if !(in >= min && in <= max) { + return err + } + case reflect.Float32: + in := float64(value.(float32)) + if !(in >= minFloat && in <= maxFloat) { + return fmt.Errorf("The %s field must be between %f and %f", field, minFloat, maxFloat) + } + case reflect.Float64: + in := value.(float64) + if !(in >= minFloat && in <= maxFloat) { + return fmt.Errorf("The %s field must be between %f and %f", field, minFloat, maxFloat) + } + + } + + return nil + }) + + // CreditCard check if provided field contains valid credit card number + // Accepted cards are Visa, MasterCard, American Express, Diners Club, Discover and JCB card + AddCustomRule("credit_card", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid credit card number", field) + if message != "" { + err = errors.New(message) + } + if !isCreditCard(str) { + return err + } + return nil + }) + + // Coordinate check if provided field contains valid Coordinate + AddCustomRule("coordinate", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid coordinate", field) + if message != "" { + err = errors.New(message) + } + if !isCoordinate(str) { + return err + } + return nil + }) + + // ValidateCSSColor check if provided field contains a valid CSS color code + AddCustomRule("css_color", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid CSS color code", field) + if message != "" { + err = errors.New(message) + } + if !isCSSColor(str) { + return err + } + return nil + }) + + // Digits check the exact matching length of digit (0,9) + // Digits:5 means the field must have 5 digit of length. + // e.g: 12345 or 98997 etc + AddCustomRule("digits", func(field string, rule string, message string, value interface{}) error { + l, err := strconv.Atoi(strings.TrimPrefix(rule, "digits:")) + if err != nil { + panic(errStringToInt) + } + err = fmt.Errorf("The %s field must be %d digits", field, l) + if l == 1 { + err = fmt.Errorf("The %s field must be 1 digit", field) + } + if message != "" { + err = errors.New(message) + } + str := toString(value) + if len(str) != l || !isNumeric(str) { + return err + } + + return nil + }) + + // DigitsBetween check if the field contains only digit and length between provided range + // e.g: digits_between:4,5 means the field can have value like: 8887 or 12345 etc + AddCustomRule("digits_between", func(field string, rule string, message string, value interface{}) error { + rng := strings.Split(strings.TrimPrefix(rule, "digits_between:"), ",") + if len(rng) != 2 { + panic(errInvalidArgument) + } + min, err := strconv.Atoi(rng[0]) + if err != nil { + panic(errStringToInt) + } + max, err := strconv.Atoi(rng[1]) + if err != nil { + panic(errStringToInt) + } + err = fmt.Errorf("The %s field must be digits between %d and %d", field, min, max) + if message != "" { + err = errors.New(message) + } + str := toString(value) + if !isNumeric(str) || !(len(str) >= min && len(str) <= max) { + return err + } + + return nil + }) + + // Date check the provided field is valid Date + AddCustomRule("date", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + if rule == "date:dd-mm-yyyy" { + if !isDateDDMMYY(str) { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must be a valid date format. e.g: dd-mm-yyyy, dd/mm/yyyy etc", field) + } + } + if !isDate(str) { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must be a valid date format. e.g: yyyy-mm-dd, yyyy/mm/dd etc", field) + } + return nil + }) + + // Email check the provided field is valid Email + AddCustomRule("email", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid email address", field) + if message != "" { + err = errors.New(message) + } + if !isEmail(str) { + return err + } + return nil + }) + + // validFloat check the provided field is valid float number + AddCustomRule("float", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a float number", field) + if message != "" { + err = errors.New(message) + } + if !isFloat(str) { + return err + } + return nil + }) + + // IP check if provided field is valid IP address + AddCustomRule("ip", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid IP address", field) + if message != "" { + err = errors.New(message) + } + if !isIP(str) { + return err + } + return nil + }) + + // IP check if provided field is valid IP v4 address + AddCustomRule("ip_v4", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid IPv4 address", field) + if message != "" { + err = errors.New(message) + } + if !isIPV4(str) { + return err + } + return nil + }) + + // IP check if provided field is valid IP v6 address + AddCustomRule("ip_v6", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must be a valid IPv6 address", field) + if message != "" { + err = errors.New(message) + } + if !isIPV6(str) { + return err + } + return nil + }) + + // ValidateJSON check if provided field contains valid json string + AddCustomRule("json", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid JSON string", field) + if message != "" { + err = errors.New(message) + } + if !isJSON(str) { + return err + } + return nil + }) + + /// Latitude check if provided field contains valid Latitude + AddCustomRule("lat", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid latitude", field) + if message != "" { + err = errors.New(message) + } + if !isLatitude(str) { + return err + } + return nil + }) + + // Longitude check if provided field contains valid Longitude + AddCustomRule("lon", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid longitude", field) + if message != "" { + err = errors.New(message) + } + if !isLongitude(str) { + return err + } + return nil + }) + + // Length check the field's character Length + AddCustomRule("len", func(field string, rule string, message string, value interface{}) error { + l, err := strconv.Atoi(strings.TrimPrefix(rule, "len:")) + if err != nil { + panic(errStringToInt) + } + err = fmt.Errorf("The %s field must be length of %d", field, l) + if message != "" { + err = errors.New(message) + } + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.String, reflect.Array, reflect.Map, reflect.Slice: + vLen := rv.Len() + if vLen != l { + return err + } + default: + str := toString(value) //force the value to be string + if len(str) != l { + return err + } + } + + return nil + }) + + // Min check the field's minimum character length for string, value for int, float and size for array, map, slice + AddCustomRule("min", func(field string, rule string, message string, value interface{}) error { + mustLen := strings.TrimPrefix(rule, "min:") + lenInt, err := strconv.Atoi(mustLen) + if err != nil { + panic(errStringToInt) + } + lenFloat, err := strconv.ParseFloat(mustLen, 64) + if err != nil { + panic(errStringToFloat) + } + errMsg := fmt.Errorf("The %s field value can not be less than %d", field, lenInt) + if message != "" { + errMsg = errors.New(message) + } + errMsgFloat := fmt.Errorf("The %s field value can not be less than %f", field, lenFloat) + if message != "" { + errMsgFloat = errors.New(message) + } + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.String: + inLen := rv.Len() + if inLen < lenInt { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must be minimum %d char", field, lenInt) + } + case reflect.Array, reflect.Map, reflect.Slice: + inLen := rv.Len() + if inLen < lenInt { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must be minimum %d in size", field, lenInt) + } + case reflect.Int: + in := value.(int) + if in < lenInt { + return errMsg + } + case reflect.Int8: + in := int(value.(int8)) + if in < lenInt { + return errMsg + } + case reflect.Int16: + in := int(value.(int16)) + if in < lenInt { + return errMsg + } + case reflect.Int32: + in := int(value.(int32)) + if in < lenInt { + return errMsg + } + case reflect.Int64: + in := int(value.(int64)) + if in < lenInt { + return errMsg + } + case reflect.Uint: + in := int(value.(uint)) + if in < lenInt { + return errMsg + } + case reflect.Uint8: + in := int(value.(uint8)) + if in < lenInt { + return errMsg + } + case reflect.Uint16: + in := int(value.(uint16)) + if in < lenInt { + return errMsg + } + case reflect.Uint32: + in := int(value.(uint32)) + if in < lenInt { + return errMsg + } + case reflect.Uint64: + in := int(value.(uint64)) + if in < lenInt { + return errMsg + } + case reflect.Uintptr: + in := int(value.(uintptr)) + if in < lenInt { + return errMsg + } + case reflect.Float32: + in := value.(float32) + if in < float32(lenFloat) { + return errMsgFloat + } + case reflect.Float64: + in := value.(float64) + if in < lenFloat { + return errMsgFloat + } + + } + + return nil + }) + + // Max check the field's maximum character length for string, value for int, float and size for array, map, slice + AddCustomRule("max", func(field string, rule string, message string, value interface{}) error { + mustLen := strings.TrimPrefix(rule, "max:") + lenInt, err := strconv.Atoi(mustLen) + if err != nil { + panic(errStringToInt) + } + lenFloat, err := strconv.ParseFloat(mustLen, 64) + if err != nil { + panic(errStringToFloat) + } + errMsg := fmt.Errorf("The %s field value can not be greater than %d", field, lenInt) + if message != "" { + errMsg = errors.New(message) + } + errMsgFloat := fmt.Errorf("The %s field value can not be greater than %f", field, lenFloat) + if message != "" { + errMsgFloat = errors.New(message) + } + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.String: + inLen := rv.Len() + if inLen > lenInt { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must be maximum %d char", field, lenInt) + } + case reflect.Array, reflect.Map, reflect.Slice: + inLen := rv.Len() + if inLen > lenInt { + if message != "" { + return errors.New(message) + } + return fmt.Errorf("The %s field must be minimum %d in size", field, lenInt) + } + case reflect.Int: + in := value.(int) + if in > lenInt { + return errMsg + } + case reflect.Int8: + in := int(value.(int8)) + if in > lenInt { + return errMsg + } + case reflect.Int16: + in := int(value.(int16)) + if in > lenInt { + return errMsg + } + case reflect.Int32: + in := int(value.(int32)) + if in > lenInt { + return errMsg + } + case reflect.Int64: + in := int(value.(int64)) + if in > lenInt { + return errMsg + } + case reflect.Uint: + in := int(value.(uint)) + if in > lenInt { + return errMsg + } + case reflect.Uint8: + in := int(value.(uint8)) + if in > lenInt { + return errMsg + } + case reflect.Uint16: + in := int(value.(uint16)) + if in > lenInt { + return errMsg + } + case reflect.Uint32: + in := int(value.(uint32)) + if in > lenInt { + return errMsg + } + case reflect.Uint64: + in := int(value.(uint64)) + if in > lenInt { + return errMsg + } + case reflect.Uintptr: + in := int(value.(uintptr)) + if in > lenInt { + return errMsg + } + case reflect.Float32: + in := value.(float32) + if in > float32(lenFloat) { + return errMsgFloat + } + case reflect.Float64: + in := value.(float64) + if in > lenFloat { + return errMsgFloat + } + + } + + 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) + err := fmt.Errorf("The %s field must be numeric", field) + if message != "" { + err = errors.New(message) + } + if !isNumeric(str) { + return err + } + return nil + }) + + // NumericBetween check if the value field numeric value range + // e.g: numeric_between:18, 65 means number value must be in between a numeric value 18 & 65 + AddCustomRule("numeric_between", func(field string, rule string, message string, value interface{}) error { + rng := strings.Split(strings.TrimPrefix(rule, "numeric_between:"), ",") + if len(rng) != 2 { + panic(errInvalidArgument) + } + // check for integer value + _min, err := strconv.ParseFloat(rng[0], 64) + if err != nil { + panic(errStringToInt) + } + min := int(_min) + _max, err := strconv.ParseFloat(rng[1], 64) + if err != nil { + panic(errStringToInt) + } + max := int(_max) + errMsg := fmt.Errorf("The %s field must be numeric value between %d and %d", field, min, max) + if message != "" { + errMsg = errors.New(message) + } + + val := toString(value) + + if !strings.Contains(rng[0], ".") || !strings.Contains(rng[1], ".") { + digit, errs := strconv.Atoi(val) + if errs != nil { + return errMsg + } + if !(digit >= min && digit <= max) { + return errMsg + } + } + // check for float value + minFloat, err := strconv.ParseFloat(rng[0], 64) + if err != nil { + panic(errStringToFloat) + } + maxFloat, err := strconv.ParseFloat(rng[1], 64) + if err != nil { + panic(errStringToFloat) + } + errMsg = fmt.Errorf("The %s field must be numeric value between %f and %f", field, minFloat, maxFloat) + if message != "" { + errMsg = errors.New(message) + } + + digit, err := strconv.ParseFloat(val, 64) + if err != nil { + return errMsg + } + if !(digit >= minFloat && digit <= maxFloat) { + return errMsg + } + return nil + }) + + // ValidateURL check if provided field is valid URL + AddCustomRule("url", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field format is invalid", field) + if message != "" { + err = errors.New(message) + } + if !isURL(str) { + return err + } + return nil + }) + + // UUID check if provided field contains valid UUID + AddCustomRule("uuid", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid UUID", field) + if message != "" { + err = errors.New(message) + } + if !isUUID(str) { + return err + } + return nil + }) + + // UUID3 check if provided field contains valid UUID of version 3 + AddCustomRule("uuid_v3", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid UUID V3", field) + if message != "" { + err = errors.New(message) + } + if !isUUID3(str) { + return err + } + return nil + }) + + // UUID4 check if provided field contains valid UUID of version 4 + AddCustomRule("uuid_v4", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid UUID V4", field) + if message != "" { + err = errors.New(message) + } + if !isUUID4(str) { + return err + } + return nil + }) + + // UUID5 check if provided field contains valid UUID of version 5 + AddCustomRule("uuid_v5", func(field string, rule string, message string, value interface{}) error { + str := toString(value) + err := fmt.Errorf("The %s field must contain valid UUID V5", field) + if message != "" { + err = errors.New(message) + } + if !isUUID5(str) { + return err + } + return nil + }) + + // In check if provided field equals one of the values specified in the rule + AddCustomRule("in", func(field string, rule string, message string, value interface{}) error { + rng := strings.Split(strings.TrimPrefix(rule, "in:"), ",") + if len(rng) == 0 { + panic(errInvalidArgument) + } + str := toString(value) + err := fmt.Errorf("The %s field must be one of %v", field, strings.Join(rng, ", ")) + if message != "" { + err = errors.New(message) + } + if !isIn(rng, str) { + return err + } + return nil + }) + + // In check if provided field equals one of the values specified in the rule + AddCustomRule("not_in", func(field string, rule string, message string, value interface{}) error { + rng := strings.Split(strings.TrimPrefix(rule, "not_in:"), ",") + if len(rng) == 0 { + panic(errInvalidArgument) + } + str := toString(value) + err := fmt.Errorf("The %s field must not be any of %v", field, strings.Join(rng, ", ")) + if message != "" { + err = errors.New(message) + } + if isIn(rng, str) { + return err + } + return nil + }) +} diff --git a/validationmdl/validationcore/rules_test.go b/validationmdl/validationcore/rules_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7de69d7bfed1bb497713fda178f580a1702ef801 --- /dev/null +++ b/validationmdl/validationcore/rules_test.go @@ -0,0 +1,1973 @@ +package govalidator + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "testing" +) + +func Test_AddCustomRule(t *testing.T) { + AddCustomRule("__x__", func(f string, rule string, message string, v interface{}) error { + if v.(string) != "xyz" { + return fmt.Errorf("The %s field must be xyz", f) + } + return nil + }) + if len(rulesFuncMap) <= 0 { + t.Error("AddCustomRule failed to add new rule") + } +} + +func Test_AddCustomRule_panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("AddCustomRule failed to panic") + } + }() + AddCustomRule("__x__", func(f string, rule string, message string, v interface{}) error { + if v.(string) != "xyz" { + return fmt.Errorf("The %s field must be xyz", f) + } + return nil + }) +} + +func Test_validateExtraRules(t *testing.T) { + errsBag := url.Values{} + validateCustomRules("f_field", "__x__", "a", "", errsBag) + if len(errsBag) != 1 { + t.Error("validateExtraRules failed") + } +} + +//================================= rules ================================= +func Test_Required(t *testing.T) { + type tRequired struct { + Str string `json:"_str"` + Int int `json:"_int"` + Int8 int8 `json:"_int8"` + Int16 int16 `json:"_int16"` + Int32 int32 `json:"_int32"` + Int64 int64 `json:"_int64"` + Uint uint `json:"_uint"` + Uint8 uint8 `json:"_uint8"` + Uint16 uint16 `json:"_uint16"` + Uint32 uint32 `json:"_uint32"` + Uint64 uint64 `json:"_uint64"` + Uintptr uintptr `json:"_uintptr"` + Flaot32 float32 `json:"_float32"` + Flaot64 float64 `json:"_float64"` + Integer Int `json:"integer"` + Integer64 Int64 `json:"integer64"` + Fpoint32 Float32 `json:"float32"` + Fpoint64 Float64 `json:"float64"` + Boolean Bool `json:"boolean"` + } + + rules := MapData{ + "_str": []string{"required"}, + "_int": []string{"required"}, + "_int8": []string{"required"}, + "_int16": []string{"required"}, + "_int32": []string{"required"}, + "_int64": []string{"required"}, + "_uint": []string{"required"}, + "_uint8": []string{"required"}, + "_uint16": []string{"required"}, + "_uint32": []string{"required"}, + "_uint64": []string{"required"}, + "_uintptr": []string{"required"}, + "_float32": []string{"required"}, + "_float64": []string{"required"}, + "integer": []string{"required"}, + "integer64": []string{"required"}, + "float32": []string{"required"}, + "float64": []string{"required"}, + "boolean": []string{"required"}, + } + + postRequired := map[string]string{} + + var trequired tRequired + + body, _ := json.Marshal(postRequired) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "_str": []string{"required:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &trequired, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 19 { + t.Log(validationErr) + t.Error("required validation failed!") + } + + if validationErr.Get("_str") != "custom_message" { + t.Error("required rule custom message failed") + } +} + +func Test_Regex(t *testing.T) { + type tRegex struct { + Name string `json:"name"` + } + + postRegex := tRegex{Name: "john"} + var tregex tRegex + + body, _ := json.Marshal(postRegex) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "name": []string{"regex:custom_message"}, + } + + rules := MapData{ + "name": []string{"regex:^[0-9]+$"}, + } + + opts := Options{ + Request: req, + Data: &tregex, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("regex validation failed!") + } + + if validationErr.Get("name") != "custom_message" { + t.Error("regex rule custom message failed") + } + +} + +func Test_Alpha(t *testing.T) { + type user struct { + Name string `json:"name"` + } + + postUser := user{Name: "9080"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "name": []string{"alpha:custom_message"}, + } + + rules := MapData{ + "name": []string{"alpha"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("alpha validation failed!") + } + + if validationErr.Get("name") != "custom_message" { + t.Error("alpha custom message failed!") + } +} + +func Test_AlphaDash(t *testing.T) { + type user struct { + Name string `json:"name"` + } + + postUser := user{Name: "9090$"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "name": []string{"alpha_dash:custom_message"}, + } + + rules := MapData{ + "name": []string{"alpha_dash"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("alpha_dash validation failed!") + } + + if validationErr.Get("name") != "custom_message" { + t.Error("alpha dash custom message failed!") + } +} + +func Test_AlphaNumeric(t *testing.T) { + type user struct { + Name string `json:"name"` + } + + postUser := user{Name: "aE*Sb$"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "name": []string{"alpha_num"}, + } + + messages := MapData{ + "name": []string{"alpha_num:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("alpha_num validation failed!") + } + + if validationErr.Get("name") != "custom_message" { + t.Error("alpha num custom message failed!") + } +} + +func Test_Boolean(t *testing.T) { + type Bools struct { + BoolStr string `json:"boolStr"` + BoolInt int `json:"boolInt"` + BoolInt8 int8 `json:"boolInt8"` + BoolInt16 int16 `json:"boolInt16"` + BoolInt32 int32 `json:"boolInt32"` + BoolInt64 int64 `json:"boolInt64"` + BoolUint uint `json:"boolUint"` + BoolUint8 uint8 `json:"boolUint8"` + BoolUint16 uint16 `json:"boolUint16"` + BoolUint32 uint32 `json:"boolUint32"` + BoolUint64 uint64 `json:"boolUint64"` + BoolUintptr uintptr `json:"boolUintptr"` + Bool bool `json:"_bool"` + } + + postBools := Bools{ + BoolStr: "abc", + BoolInt: 90, + BoolInt8: 10, + BoolInt16: 22, + BoolInt32: 76, + BoolInt64: 9, + BoolUint: 5, + BoolUint8: 9, + BoolUint16: 9, + BoolUint32: 9, + BoolUint64: 8, + BoolUintptr: 9, + } + var boolObj Bools + + body, _ := json.Marshal(postBools) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "boolStr": []string{"bool"}, + "boolInt": []string{"bool"}, + "boolInt8": []string{"bool"}, + "boolInt16": []string{"bool"}, + "boolInt32": []string{"bool"}, + "boolInt64": []string{"bool"}, + "boolUint": []string{"bool"}, + "boolUint8": []string{"bool"}, + "boolUint16": []string{"bool"}, + "boolUint32": []string{"bool"}, + "boolUint64": []string{"bool"}, + "boolUintptr": []string{"bool"}, + } + + messages := MapData{ + "boolStr": []string{"bool:custom_message"}, + "boolInt": []string{"bool:custom_message"}, + "boolUint": []string{"bool:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &boolObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 12 { + t.Error("bool validation failed!") + } + + if validationErr.Get("boolStr") != "custom_message" || + validationErr.Get("boolInt") != "custom_message" || + validationErr.Get("boolUint") != "custom_message" { + t.Error("bool custom message failed!") + } +} + +func Test_Between(t *testing.T) { + type user struct { + Str string `json:"str"` + Int int `json:"_int"` + Int8 int8 `json:"_int8"` + Int16 int16 `json:"_int16"` + Int32 int32 `json:"_int32"` + Int64 int64 `json:"_int64"` + Uint uint `json:"_uint"` + Uint8 uint8 `json:"_uint8"` + Uint16 uint16 `json:"_uint16"` + Uint32 uint32 `json:"_uint32"` + Uint64 uint64 `json:"_uint64"` + Uintptr uintptr `json:"_uintptr"` + Float32 float32 `json:"_float32"` + Float64 float64 `json:"_float64"` + Slice []int `json:"_slice"` + } + + postUser := user{} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "str": []string{"between:3,5"}, + "_int": []string{"between:3,5"}, + "_int8": []string{"between:3,5"}, + "_int16": []string{"between:3,5"}, + "_int32": []string{"between:3,5"}, + "_int64": []string{"between:3,5"}, + "_uint": []string{"between:3,5"}, + "_uint8": []string{"between:3,5"}, + "_uint16": []string{"between:3,5"}, + "_uint32": []string{"between:3,5"}, + "_uint64": []string{"between:3,5"}, + "_uintptr": []string{"between:3,5"}, + "_float32": []string{"between:3.5,5.9"}, + "_float64": []string{"between:3.3,6.2"}, + "_slice": []string{"between:3,5"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + vd.SetDefaultRequired(true) + validationErr := vd.ValidateJSON() + if len(validationErr) != 15 { + t.Error("between validation failed!") + } +} + +func Test_CreditCard(t *testing.T) { + type user struct { + CreditCard string `json:"credit_card"` + } + + postUser := user{CreditCard: "87080"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "credit_card": []string{"credit_card:custom_message"}, + } + + rules := MapData{ + "credit_card": []string{"credit_card"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("credit card validation failed!") + } + + if validationErr.Get("credit_card") != "custom_message" { + t.Error("credit_card custom message failed!") + } +} + +func Test_Coordinate(t *testing.T) { + type user struct { + Coordinate string `json:"coordinate"` + } + + postUser := user{Coordinate: "8080"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "coordinate": []string{"coordinate:custom_message"}, + } + + rules := MapData{ + "coordinate": []string{"coordinate"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("coordinate validation failed!") + } + + if validationErr.Get("coordinate") != "custom_message" { + t.Error("coordinate custom message failed!") + } +} + +func Test_CSSColor(t *testing.T) { + type user struct { + Color string `json:"color"` + } + + postUser := user{Color: "8080"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "color": []string{"css_color"}, + } + + messages := MapData{ + "color": []string{"css_color:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("CSS color validation failed!") + } + + if validationErr.Get("color") != "custom_message" { + t.Error("css_color custom message failed!") + } +} + +func Test_Digits(t *testing.T) { + type user struct { + Zip string `json:"zip"` + Level string `json:"level"` + } + + postUser := user{Zip: "8322", Level: "10"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "zip": []string{"digits:5"}, + "level": []string{"digits:1"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Error("Digits validation failed!") + } +} + +func Test_DigitsBetween(t *testing.T) { + type user struct { + Zip string `json:"zip"` + Level string `json:"level"` + } + + postUser := user{Zip: "8322", Level: "10"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "zip": []string{"digits_between:5,10"}, + "level": []string{"digits_between:5,10"}, + } + + messages := MapData{ + "zip": []string{"digits_between:custom_message"}, + "level": []string{"digits_between:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Error("digits between validation failed!") + } + + if validationErr.Get("zip") != "custom_message" || + validationErr.Get("level") != "custom_message" { + t.Error("digits_between custom message failed!") + } +} + +func Test_DigitsBetweenPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Digits between failed to panic!") + } + }() + type user struct { + Zip string `json:"zip"` + Level string `json:"level"` + } + + postUser := user{Zip: "8322", Level: "10"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "zip": []string{"digits_between:5"}, + "level": []string{"digits_between:i,k"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Error("Digits between panic failed!") + } +} + +func Test_Date(t *testing.T) { + type user struct { + DOB string `json:"dob"` + JoiningDate string `json:"joining_date"` + } + + postUser := user{DOB: "invalida date", JoiningDate: "10"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "dob": []string{"date"}, + "joining_date": []string{"date:dd-mm-yyyy"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Log(validationErr) + t.Error("Date validation failed!") + } +} + +func Test_Date_message(t *testing.T) { + type user struct { + DOB string `json:"dob"` + JoiningDate string `json:"joining_date"` + } + + postUser := user{DOB: "invalida date", JoiningDate: "10"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "dob": []string{"date"}, + "joining_date": []string{"date:dd-mm-yyyy"}, + } + + messages := MapData{ + "dob": []string{"date:custom_message"}, + "joining_date": []string{"date:dd-mm-yyyy:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("dob") != "custom_message" { + t.Error("Date custom message validation failed!") + } + if k := validationErr.Get("dob"); k != "custom_message" { + t.Error("Date date:dd-mm-yyyy custom message validation failed!") + } +} + +func Test_Email(t *testing.T) { + type user struct { + Email string `json:"email"` + } + + postUser := user{Email: "invalid email"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "email": []string{"email"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("Email validation failed!") + } +} + +func Test_Email_message(t *testing.T) { + type user struct { + Email string `json:"email"` + } + + postUser := user{Email: "invalid email"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "email": []string{"email"}, + } + + messages := MapData{ + "email": []string{"email:custom_message"}, + } + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("email") != "custom_message" { + t.Error("Email message validation failed!") + } +} + +func Test_Float(t *testing.T) { + type user struct { + CGPA string `json:"cgpa"` + } + + postUser := user{CGPA: "invalid cgpa"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "cgpa": []string{"float"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("Float validation failed!") + } +} + +func Test_Float_message(t *testing.T) { + type user struct { + CGPA string `json:"cgpa"` + } + + postUser := user{CGPA: "invalid cgpa"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "cgpa": []string{"float"}, + } + + messages := MapData{ + "cgpa": []string{"float:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("cgpa") != "custom_message" { + t.Error("Float custom message failed!") + } +} + +func Test_IP(t *testing.T) { + type user struct { + IP string `json:"ip"` + } + + postUser := user{IP: "invalid IP"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "ip": []string{"ip"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("IP validation failed!") + } +} + +func Test_IP_message(t *testing.T) { + type user struct { + IP string `json:"ip"` + } + + postUser := user{IP: "invalid IP"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "ip": []string{"ip:custom_message"}, + } + + rules := MapData{ + "ip": []string{"ip"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("ip") != "custom_message" { + t.Error("IP custom message failed!") + } +} + +func Test_IPv4(t *testing.T) { + type user struct { + IP string `json:"ip"` + } + + postUser := user{IP: "invalid IP"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "ip": []string{"ip_v4"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("IP v4 validation failed!") + } +} + +func Test_IPv4_message(t *testing.T) { + type user struct { + IP string `json:"ip"` + } + + postUser := user{IP: "invalid IP"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "ip": []string{"ip_v4:custom_message"}, + } + + rules := MapData{ + "ip": []string{"ip_v4"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("ip") != "custom_message" { + t.Error("IP v4 custom message failed!") + } +} + +func Test_IPv6(t *testing.T) { + type user struct { + IP string `json:"ip"` + } + + postUser := user{IP: "invalid IP"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "ip": []string{"ip_v6"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("IP v6 validation failed!") + } +} + +func Test_IPv6_message(t *testing.T) { + type user struct { + IP string `json:"ip"` + } + + postUser := user{IP: "invalid IP"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "ip": []string{"ip_v6:custom_message"}, + } + + rules := MapData{ + "ip": []string{"ip_v6"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("ip") != "custom_message" { + t.Error("IP v6 custom message failed!") + } +} + +func Test_JSON(t *testing.T) { + type user struct { + Settings string `json:"settings"` + } + + postUser := user{Settings: "invalid json"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "settings": []string{"json"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("JSON validation failed!") + } +} + +func Test_JSON_valid(t *testing.T) { + type user struct { + Settings string `json:"settings"` + } + + postUser := user{Settings: `{"name": "John Doe", "age": 30}`} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "settings": []string{"json"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Log(validationErr) + t.Error("Validation failed for valid JSON") + } +} + +func Test_JSON_message(t *testing.T) { + type user struct { + Settings string `json:"settings"` + } + + postUser := user{Settings: "invalid json"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "settings": []string{"json:custom_message"}, + } + + rules := MapData{ + "settings": []string{"json"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("settings") != "custom_message" { + t.Error("JSON custom message failed!") + } +} + +func Test_LatLon(t *testing.T) { + type Location struct { + Latitude string `json:"lat"` + Longitude string `json:"lon"` + } + + postLocation := Location{Latitude: "invalid lat", Longitude: "invalid lon"} + var loc Location + + body, _ := json.Marshal(postLocation) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "lat": []string{"lat"}, + "lon": []string{"lon"}, + } + + opts := Options{ + Request: req, + Data: &loc, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Log(validationErr) + t.Error("Lat Lon validation failed!") + } +} + +func Test_LatLon_valid(t *testing.T) { + type Location struct { + Latitude string `json:"lat"` + Longitude string `json:"lon"` + } + + postLocation := Location{Latitude: "23.810332", Longitude: "90.412518"} + var loc Location + + body, _ := json.Marshal(postLocation) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "lat": []string{"lat"}, + "lon": []string{"lon"}, + } + + opts := Options{ + Request: req, + Data: &loc, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Log(validationErr) + t.Error("Valid Lat Lon validation failed!") + } +} + +func Test_LatLon_message(t *testing.T) { + type Location struct { + Latitude string `json:"lat"` + Longitude string `json:"lon"` + } + + postLocation := Location{Latitude: "invalid lat", Longitude: "invalid lon"} + var loc Location + + body, _ := json.Marshal(postLocation) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "lat": []string{"lat:custom_message"}, + "lon": []string{"lon:custom_message"}, + } + + rules := MapData{ + "lat": []string{"lat"}, + "lon": []string{"lon"}, + } + + opts := Options{ + Request: req, + Data: &loc, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("lat") != "custom_message" || + validationErr.Get("lon") != "custom_message" { + t.Error("Lat lon custom message failed") + } +} + +func Test_Len(t *testing.T) { + type user struct { + Name string `json:"name"` + Roll int `json:"roll"` + Permissions []string `json:"permissions"` + } + + postUser := user{ + Name: "john", + Roll: 11, + Permissions: []string{"create", "delete", "update"}, + } + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "name": []string{"len:5"}, + "roll": []string{"len:5"}, + "permissions": []string{"len:10"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 3 { + t.Log(validationErr) + t.Error("Len validation failed!") + } +} + +func Test_Len_message(t *testing.T) { + type user struct { + Name string `json:"name"` + Roll int `json:"roll"` + Permissions []string `json:"permissions"` + } + + postUser := user{ + Name: "john", + Roll: 11, + Permissions: []string{"create", "delete", "update"}, + } + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "name": []string{"len:custom_message"}, + "roll": []string{"len:custom_message"}, + "permissions": []string{"len:custom_message"}, + } + + rules := MapData{ + "name": []string{"len:5"}, + "roll": []string{"len:5"}, + "permissions": []string{"len:10"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if validationErr.Get("name") != "custom_message" || + validationErr.Get("roll") != "custom_message" || + validationErr.Get("permissions") != "custom_message" { + t.Error("len custom message failed") + } +} + +func Test_Numeric(t *testing.T) { + type user struct { + NID string `json:"nid"` + } + + postUser := user{NID: "invalid nid"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "nid": []string{"numeric"}, + } + + messages := MapData{ + "nid": []string{"numeric:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("Numeric validation failed!") + } + + if validationErr.Get("nid") != "custom_message" { + t.Error("Numeric custom message failed!") + } +} + +func Test_Numeric_valid(t *testing.T) { + type user struct { + NID string `json:"nid"` + } + + postUser := user{NID: "109922"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "nid": []string{"numeric"}, + } + + messages := MapData{ + "nid": []string{"numeric:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Log(validationErr) + t.Error("Valid numeric validation failed!") + } +} + +func Test_NumericBetween(t *testing.T) { + type user struct { + Age int `json:"age"` + CGPA string `json:"cgpa"` + } + + postUser := user{Age: 77, CGPA: "2.90"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "age": []string{"numeric_between:18,60"}, + "cgpa": []string{"numeric_between:3.5,4.9"}, + } + + messages := MapData{ + "age": []string{"numeric_between:custom_message"}, + "cgpa": []string{"numeric_between:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Error("numeric_between validation failed!") + } + + if validationErr.Get("age") != "custom_message" || + validationErr.Get("cgpa") != "custom_message" { + t.Error("numeric_between custom message failed!") + } +} + +func Test_URL(t *testing.T) { + type user struct { + Web string `json:"web"` + } + + postUser := user{Web: "invalid url"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "web": []string{"url"}, + } + + messages := MapData{ + "web": []string{"url:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Log(validationErr) + t.Error("URL validation failed!") + } + + if validationErr.Get("web") != "custom_message" { + t.Error("URL custom message failed!") + } +} + +func Test_UR_valid(t *testing.T) { + type user struct { + Web string `json:"web"` + } + + postUser := user{Web: "www.google.com"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "web": []string{"url"}, + } + + messages := MapData{ + "web": []string{"url: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 URL validation failed!") + } +} + +func Test_UUIDS(t *testing.T) { + type user struct { + UUID string `json:"uuid"` + UUIDV3 string `json:"uuid3"` + UUIDV4 string `json:"uuid4"` + UUIDV5 string `json:"uuid5"` + } + + postUser := user{ + UUID: "invalid uuid", + UUIDV3: "invalid uuid", + UUIDV4: "invalid uuid", + UUIDV5: "invalid uuid", + } + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "uuid": []string{"uuid"}, + "uuid3": []string{"uuid_v3"}, + "uuid4": []string{"uuid_v4"}, + "uuid5": []string{"uuid_v5"}, + } + + messages := MapData{ + "uuid": []string{"uuid:custom_message"}, + "uuid3": []string{"uuid_v3:custom_message"}, + "uuid4": []string{"uuid_v4:custom_message"}, + "uuid5": []string{"uuid_v5:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 4 { + t.Error("UUID validation failed!") + } + + if validationErr.Get("uuid") != "custom_message" || + validationErr.Get("uuid3") != "custom_message" || + validationErr.Get("uuid4") != "custom_message" || + validationErr.Get("uuid5") != "custom_message" { + t.Error("UUID custom message failed!") + } + +} + +func Test_min(t *testing.T) { + type Body struct { + Str string `json:"_str"` + Slice []string `json:"_slice"` + Int int `json:"_int"` + Int8 int8 `json:"_int8"` + Int16 int16 `json:"_int16"` + Int32 int32 `json:"_int32"` + Int64 int64 `json:"_int64"` + Uint uint `json:"_uint"` + Uint8 uint8 `json:"_uint8"` + Uint16 uint16 `json:"_uint16"` + Uint32 uint32 `json:"_uint32"` + Uint64 uint64 `json:"_uint64"` + Uintptr uintptr `json:"_uintptr"` + Float32 float32 `json:"_float32"` + Float64 float64 `json:"_float64"` + } + + postBody := Body{ + Str: "xyz", + Slice: []string{"x", "y"}, + Int: 2, + Int8: 2, + Int16: 2, + Int32: 2, + Int64: 2, + Uint: 2, + Uint8: 2, + Uint16: 2, + Uint32: 2, + Uint64: 2, + Uintptr: 2, + Float32: 2.4, + Float64: 3.2, + } + + var bodyObj Body + + body, _ := json.Marshal(postBody) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "_str": []string{"min:5"}, + "_slice": []string{"min:5"}, + "_int": []string{"min:5"}, + "_int8": []string{"min:5"}, + "_int16": []string{"min:5"}, + "_int32": []string{"min:5"}, + "_int64": []string{"min:5"}, + "_uint": []string{"min:5"}, + "_uint8": []string{"min:5"}, + "_uint16": []string{"min:5"}, + "_uint32": []string{"min:5"}, + "_uint64": []string{"min:5"}, + "_uintptr": []string{"min:5"}, + "_float32": []string{"min:5"}, + "_float64": []string{"min:5"}, + } + + messages := MapData{ + "_str": []string{"min:custom_message"}, + "_slice": []string{"min:custom_message"}, + "_int": []string{"min:custom_message"}, + "_uint": []string{"min:custom_message"}, + "_float32": []string{"min:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &bodyObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 15 { + t.Error("min validation failed!") + } + + if validationErr.Get("_str") != "custom_message" || + validationErr.Get("_slice") != "custom_message" || + validationErr.Get("_int") != "custom_message" || + validationErr.Get("_uint") != "custom_message" || + validationErr.Get("_float32") != "custom_message" { + t.Error("min custom message failed!") + } +} + +func Test_max(t *testing.T) { + type Body struct { + Str string `json:"_str"` + Slice []string `json:"_slice"` + Int int `json:"_int"` + Int8 int8 `json:"_int8"` + Int16 int16 `json:"_int16"` + Int32 int32 `json:"_int32"` + Int64 int64 `json:"_int64"` + Uint uint `json:"_uint"` + Uint8 uint8 `json:"_uint8"` + Uint16 uint16 `json:"_uint16"` + Uint32 uint32 `json:"_uint32"` + Uint64 uint64 `json:"_uint64"` + Uintptr uintptr `json:"_uintptr"` + Float32 float32 `json:"_float32"` + Float64 float64 `json:"_float64"` + } + + postBody := Body{ + Str: "xyzabc", + Slice: []string{"x", "y", "z"}, + Int: 20, + Int8: 20, + Int16: 20, + Int32: 20, + Int64: 20, + Uint: 20, + Uint8: 20, + Uint16: 20, + Uint32: 20, + Uint64: 20, + Uintptr: 20, + Float32: 20.4, + Float64: 30.2, + } + + var bodyObj Body + + body, _ := json.Marshal(postBody) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + rules := MapData{ + "_str": []string{"max:5"}, + "_slice": []string{"max:2"}, + "_int": []string{"max:5"}, + "_int8": []string{"max:5"}, + "_int16": []string{"max:5"}, + "_int32": []string{"max:5"}, + "_int64": []string{"max:5"}, + "_uint": []string{"max:5"}, + "_uint8": []string{"max:5"}, + "_uint16": []string{"max:5"}, + "_uint32": []string{"max:5"}, + "_uint64": []string{"max:5"}, + "_uintptr": []string{"max:5"}, + "_float32": []string{"max:5"}, + "_float64": []string{"max:5"}, + } + + messages := MapData{ + "_str": []string{"max:custom_message"}, + "_slice": []string{"max:custom_message"}, + "_int": []string{"max:custom_message"}, + "_uint": []string{"max:custom_message"}, + "_float32": []string{"max:custom_message"}, + } + + opts := Options{ + Request: req, + Data: &bodyObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 15 { + t.Error(validationErr) + t.Error("max validation failed!") + } + + if validationErr.Get("_str") != "custom_message" || + validationErr.Get("_slice") != "custom_message" || + validationErr.Get("_int") != "custom_message" || + validationErr.Get("_uint") != "custom_message" || + validationErr.Get("_float32") != "custom_message" { + t.Error("max custom message failed!") + } +} + +func Test_In(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "4"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"in:custom_message"}, + } + + rules := MapData{ + "input": []string{"in:1,2,3"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("in validation failed!") + } + + if validationErr.Get("input") != "custom_message" { + t.Error("in custom message failed!") + } +} + +func Test_In_valid(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "1"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"in:custom_message"}, + } + + rules := MapData{ + "input": []string{"in:1,2,3"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Error("in validation was triggered when valid!") + } +} + +func Test_In_string(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "bob"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"in:custom_message"}, + } + + rules := MapData{ + "input": []string{"in:tom,dick,harry"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("in validation failed!") + } + + if validationErr.Get("input") != "custom_message" { + t.Error("in custom message failed!") + } +} + +func Test_In_string_valid(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "dick"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"in:custom_message"}, + } + + rules := MapData{ + "input": []string{"in:tom,dick,harry"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Error("in validation was triggered when valid!") + } +} + +func Test_NotIn(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "2"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"not_in:custom_message"}, + } + + rules := MapData{ + "input": []string{"not_in:1,2,3"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("not_in validation failed!") + } + + if validationErr.Get("input") != "custom_message" { + t.Error("not_in custom message failed!") + } +} + +func Test_NotIn_valid(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "4"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"not_in:custom_message"}, + } + + rules := MapData{ + "input": []string{"not_in:1,2,3"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Error("not_in validation was triggered when valid!") + } +} + +func Test_NotIn_string(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "harry"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"not_in:custom_message"}, + } + + rules := MapData{ + "input": []string{"not_in:tom,dick,harry"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 1 { + t.Error("not_in validation failed!") + } + + if validationErr.Get("input") != "custom_message" { + t.Error("not_in custom message failed!") + } +} + +func Test_NotIn_string_valid(t *testing.T) { + type user struct { + Input string `json:"input"` + } + + postUser := user{Input: "bob"} + var userObj user + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + messages := MapData{ + "input": []string{"not_in:custom_message"}, + } + + rules := MapData{ + "input": []string{"not_in:tom,dick,harry"}, + } + + opts := Options{ + Request: req, + Data: &userObj, + Rules: rules, + Messages: messages, + } + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 0 { + t.Error("not_in validation was triggered when valid!") + } +} diff --git a/validationmdl/validationcore/type.go b/validationmdl/validationcore/type.go new file mode 100644 index 0000000000000000000000000000000000000000..3f5109b7106da6300ce08a70c26dba68b25884b7 --- /dev/null +++ b/validationmdl/validationcore/type.go @@ -0,0 +1,133 @@ +package govalidator + +import ( + "bytes" + "encoding/json" +) + +// Int describes a custom type of built-in int data type +type Int struct { + Value int `json:"value"` + IsSet bool `json:"isSet"` +} + +var null = []byte("null") + +// UnmarshalJSON ... +func (i *Int) UnmarshalJSON(data []byte) error { + if bytes.Compare(data, null) == 0 { + return nil + } + i.IsSet = true + var temp int + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + i.Value = temp + return nil +} + +// MarshalJSON ... +func (i *Int) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Value) +} + +// Int64 describes a custom type of built-in int64 data type +type Int64 struct { + Value int64 `json:"value"` + IsSet bool `json:"isSet"` +} + +// UnmarshalJSON ... +func (i *Int64) UnmarshalJSON(data []byte) error { + if bytes.Compare(data, null) == 0 { + return nil + } + i.IsSet = true + var temp int64 + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + i.Value = temp + return nil +} + +// MarshalJSON ... +func (i *Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Value) +} + +// Float32 describes a custom type of built-in float32 data type +type Float32 struct { + Value float32 `json:"value"` + IsSet bool `json:"isSet"` +} + +// UnmarshalJSON ... +func (i *Float32) UnmarshalJSON(data []byte) error { + if bytes.Compare(data, null) == 0 { + return nil + } + i.IsSet = true + var temp float32 + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + i.Value = temp + return nil +} + +// MarshalJSON ... +func (i *Float32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Value) +} + +// Float64 describes a custom type of built-in float64 data type +type Float64 struct { + Value float64 `json:"value"` + IsSet bool `json:"isSet"` +} + +// UnmarshalJSON ... +func (i *Float64) UnmarshalJSON(data []byte) error { + if bytes.Compare(data, null) == 0 { + return nil + } + i.IsSet = true + var temp float64 + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + i.Value = temp + return nil +} + +// MarshalJSON ... +func (i *Float64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Value) +} + +// Bool describes a custom type of built-in bool data type +type Bool struct { + Value bool `json:"value"` + IsSet bool `json:"isSet"` +} + +// UnmarshalJSON ... +func (i *Bool) UnmarshalJSON(data []byte) error { + if bytes.Compare(data, null) == 0 { + return nil + } + i.IsSet = true + var temp bool + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + i.Value = temp + return nil +} + +// MarshalJSON ... +func (i *Bool) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Value) +} diff --git a/validationmdl/validationcore/utils.go b/validationmdl/validationcore/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..f8a271577dbba592e8964b6b666942cfbcc64d81 --- /dev/null +++ b/validationmdl/validationcore/utils.go @@ -0,0 +1,57 @@ +package govalidator + +import ( + "fmt" + "reflect" + "strings" +) + +// containsRequiredField check rules contain any required field +func isContainRequiredField(rules []string) bool { + for _, rule := range rules { + if rule == "required" { + return true + } + } + return false +} + +// isRuleExist check if the provided rule name is exist or not +func isRuleExist(rule string) bool { + if strings.Contains(rule, ":") { + rule = strings.Split(rule, ":")[0] + } + extendedRules := []string{"size", "mime", "ext"} + for _, r := range extendedRules { + if r == rule { + return true + } + } + if _, ok := rulesFuncMap[rule]; ok { + return true + } + return false +} + +// toString force data to be string +func toString(v interface{}) string { + str, ok := v.(string) + if !ok { + str = fmt.Sprintf("%#v", v) + } + return str +} + +// isEmpty check a type is Zero +func isEmpty(x interface{}) bool { + rt := reflect.TypeOf(x) + if rt == nil { + return true + } + rv := reflect.ValueOf(x) + switch rv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice: + return rv.Len() == 0 + } + return reflect.DeepEqual(x, reflect.Zero(rt).Interface()) +} diff --git a/validationmdl/validationcore/utils110.go b/validationmdl/validationcore/utils110.go new file mode 100644 index 0000000000000000000000000000000000000000..8742d7482eb7b90eafb2e812a67f548a4ec99d20 --- /dev/null +++ b/validationmdl/validationcore/utils110.go @@ -0,0 +1,55 @@ +// +build go1.10 + +package govalidator + +import ( + "io" + "net/http" + "path/filepath" + "strings" +) + +// getFileInfo read file from request and return file name, extension, mime and size +func getFileInfo(r *http.Request, field string) (bool, string, string, string, int64, error) { + file, multipartFileHeader, err := r.FormFile(field) + if err != nil { + return false, "", "", "", 0, err + } + // Create a buffer to store the header of the file in + fileHeader := make([]byte, 512) + + // Copy the headers into the FileHeader buffer + if _, err := file.Read(fileHeader); err != nil { + if err != io.EOF { + return false, "", "", "", 0, err + } + } + + // set position back to start. + if _, err := file.Seek(0, 0); err != nil { + return false, "", "", "", 0, err + } + + mime := http.DetectContentType(fileHeader) + if subs := "; charset=utf-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + if subs := ";charset=utf-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + if subs := "; charset=UTF-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + if subs := ";charset=UTF-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + fExist := false + if file != nil { + fExist = true + } + return fExist, multipartFileHeader.Filename, + strings.TrimPrefix(filepath.Ext(multipartFileHeader.Filename), "."), + strings.TrimSpace(mime), + multipartFileHeader.Size, + nil +} diff --git a/validationmdl/validationcore/utils_pre110.go b/validationmdl/validationcore/utils_pre110.go new file mode 100644 index 0000000000000000000000000000000000000000..58ce6cad8ac27d68c2d008837a738d8d83a4a30e --- /dev/null +++ b/validationmdl/validationcore/utils_pre110.go @@ -0,0 +1,60 @@ +// +build !go1.10 + +package govalidator + +import ( + "io" + "net/http" + "path/filepath" + "strings" +) + +// Sizer interface +type Sizer interface { + Size() int64 +} + +// getFileInfo read file from request and return file name, extension, mime and size +func getFileInfo(r *http.Request, field string) (bool, string, string, string, int64, error) { + file, multipartFileHeader, err := r.FormFile(field) + if err != nil { + return false, "", "", "", 0, err + } + // Create a buffer to store the header of the file in + fileHeader := make([]byte, 512) + + // Copy the headers into the FileHeader buffer + if _, err := file.Read(fileHeader); err != nil { + if err != io.EOF { + return false, "", "", "", 0, err + } + } + + // set position back to start. + if _, err := file.Seek(0, 0); err != nil { + return false, "", "", "", 0, err + } + + mime := http.DetectContentType(fileHeader) + if subs := "; charset=utf-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + if subs := ";charset=utf-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + if subs := "; charset=UTF-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + if subs := ";charset=UTF-8"; strings.Contains(mime, subs) { + mime = strings.Replace(mime, subs, "", -1) + } + fExist := false + if file != nil { + fExist = true + } + return fExist, multipartFileHeader.Filename, + strings.TrimPrefix(filepath.Ext(multipartFileHeader.Filename), "."), + strings.TrimSpace(mime), + file.(Sizer).Size(), + nil +} diff --git a/validationmdl/validationcore/utils_test.go b/validationmdl/validationcore/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4ef77a19f0e998ff4dc43a0a921af2bf5f9764d4 --- /dev/null +++ b/validationmdl/validationcore/utils_test.go @@ -0,0 +1,100 @@ +package govalidator + +import ( + "reflect" + "strings" + "testing" +) + +func Test_isContainRequiredField(t *testing.T) { + if !isContainRequiredField([]string{"required", "email"}) { + t.Error("isContainRequiredField failed!") + } + + if isContainRequiredField([]string{"numeric", "min:5"}) { + t.Error("isContainRequiredField failed!") + } +} + +func Benchmark_isContainRequiredField(b *testing.B) { + for n := 0; n < b.N; n++ { + isContainRequiredField([]string{"required", "email"}) + } +} + +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") + } + if isRuleExist("not exist") { + t.Error("isRuleExist failed for invalid rule") + } + if !isRuleExist("mime") { + t.Error("extended rules failed") + } +} + +func Test_toString(t *testing.T) { + Int := 100 + str := toString(Int) + typ := reflect.ValueOf(str).Kind() + if typ != reflect.String { + t.Error("toString failed!") + } +} + +func Test_isEmpty(t *testing.T) { + var Int int + var Int8 int + var Float32 float32 + var Str string + var Slice []int + var e interface{} + list := map[string]interface{}{ + "_int": Int, + "_int8": Int8, + "_float32": Float32, + "_str": Str, + "_slice": Slice, + "_empty_interface": e, + } + for k, v := range list { + if !isEmpty(v) { + t.Errorf("%v failed", k) + } + } +} + +func Test_getFileInfo(t *testing.T) { + req, err := buildMocFormReq() + if err != nil { + t.Error("request failed", err) + } + fExist, fn, ext, mime, size, _ := getFileInfo(req, "file") + if !fExist { + t.Error("file does not exist") + } + if fn != "BENCHMARK.md" { + t.Error("failed to get file name") + } + if ext != "md" { + t.Error("failed to get file extension") + } + if !strings.Contains(mime, "text/plain") { + t.Log(mime) + t.Error("failed to get file mime") + } + if size <= 0 { + t.Error("failed to get file size") + } +} diff --git a/validationmdl/validationcore/validate_file.go b/validationmdl/validationcore/validate_file.go new file mode 100644 index 0000000000000000000000000000000000000000..cedf4ed53a14342d673cf5ff3a5424ebc4106ae6 --- /dev/null +++ b/validationmdl/validationcore/validate_file.go @@ -0,0 +1,73 @@ +package govalidator + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +// validateFiles validate file size, mimes, extension etc +func validateFiles(r *http.Request, field, rule, msg string, errsBag url.Values) { + _, _, ext, mime, size, fErr := getFileInfo(r, field) + // check size + if strings.HasPrefix(rule, "size:") { + l, err := strconv.ParseInt(strings.TrimPrefix(rule, "size:"), 10, 64) + if err != nil { + panic(errStringToInt) + } + if size > l { + if msg != "" { + errsBag.Add(field, msg) + } else { + errsBag.Add(field, fmt.Sprintf("The %s field size is can not be greater than %d bytes", field, l)) + } + } + if fErr != nil { + errsBag.Add(field, fmt.Sprintf("The %s field failed to read file when fetching size", field)) + } + } + + // check extension + if strings.HasPrefix(rule, "ext:") { + exts := strings.Split(strings.TrimPrefix(rule, "ext:"), ",") + f := false + for _, e := range exts { + if e == ext { + f = true + } + } + if !f { + if msg != "" { + errsBag.Add(field, msg) + } else { + errsBag.Add(field, fmt.Sprintf("The %s field file extension %s is invalid", field, ext)) + } + } + if fErr != nil { + errsBag.Add(field, fmt.Sprintf("The %s field failed to read file when fetching extension", field)) + } + } + + // check mimes + if strings.HasPrefix(rule, "mime:") { + mimes := strings.Split(strings.TrimPrefix(rule, "mime:"), ",") + f := false + for _, m := range mimes { + if m == mime { + f = true + } + } + if !f { + if msg != "" { + errsBag.Add(field, msg) + } else { + errsBag.Add(field, fmt.Sprintf("The %s field file mime %s is invalid", field, mime)) + } + } + if fErr != nil { + errsBag.Add(field, fmt.Sprintf("The %s field failed to read file when fetching mime", field)) + } + } +} diff --git a/validationmdl/validationcore/validate_file_test.go b/validationmdl/validationcore/validate_file_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1f122876b6cebfb8d4f3f59b8aae7e05f2553956 --- /dev/null +++ b/validationmdl/validationcore/validate_file_test.go @@ -0,0 +1,141 @@ +package govalidator + +import ( + "bytes" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "testing" +) + +// buildMocFormReq prepare a moc form data request with a test file +func buildMocFormReq() (*http.Request, error) { + fPath := "doc/BENCHMARK.md" + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + file, err := os.Open(fPath) + if err != nil { + return nil, err + } + part, err := writer.CreateFormFile("file", filepath.Base(fPath)) + if err != nil { + return nil, err + } + io.Copy(part, file) + file.Close() + err = writer.Close() + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", "www.example.com", body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + return req, nil +} + +func Test_validateFiles(t *testing.T) { + req, err := buildMocFormReq() + if err != nil { + t.Error("request failed", err) + } + rules := MapData{ + "file:file": []string{"ext:jpg,pdf", "size:10", "mime:application/pdf", "required"}, + } + + opts := Options{ + Request: req, + Rules: rules, + } + + vd := New(opts) + validationErr := vd.Validate() + if len(validationErr) != 1 { + t.Error("file validation failed!") + } +} + +func Test_validateFiles_message(t *testing.T) { + req, err := buildMocFormReq() + if err != nil { + t.Error("request failed", err) + } + rules := MapData{ + "file:file": []string{"ext:jpg,pdf", "size:10", "mime:application/pdf", "required"}, + } + + msgs := MapData{ + "file:file": []string{"ext:custom_message"}, + } + + opts := Options{ + Request: req, + Rules: rules, + Messages: msgs, + } + + vd := New(opts) + validationErr := vd.Validate() + if len(validationErr) != 1 { + t.Error("file validation failed!") + } + if validationErr.Get("file") != "custom_message" { + t.Log(validationErr) + t.Error("failed custom message for file validation") + } +} + +func Test_validateFiles_CustomRule(t *testing.T) { + req, err := buildMocFormReq() + if err != nil { + t.Error("request failed", err) + } + + customRule1WasExecuted := false + isMultipartFile := false + AddCustomRule("customRule1", func(field string, rule string, message string, value interface{}) error { + customRule1WasExecuted = true + _, isMultipartFile = value.(multipart.File) + return nil + }) + + customRule2WasExecuted := false + isValueNil := false + AddCustomRule("customRule2", func(field string, rule string, message string, value interface{}) error { + customRule2WasExecuted = true + isValueNil = value == nil + return nil + }) + + rules := MapData{ + "file:file": []string{"customRule1"}, + "file:avatar": []string{"customRule2"}, + } + + opts := Options{ + Request: req, + Rules: rules, + } + + vd := New(opts) + vd.Validate() + if !customRule1WasExecuted { + t.Error("file validation performed without custom rule!") + } + + if !isMultipartFile { + t.Error("passed to custom rule value is not file!") + } + + if !customRule2WasExecuted { + t.Error("file validation performed without custom rule!") + } + + if !isValueNil { + t.Error("passed to custom rule value is not nil!") + } +} diff --git a/validationmdl/validationcore/validator.go b/validationmdl/validationcore/validator.go new file mode 100644 index 0000000000000000000000000000000000000000..503339040da07db6b64b1edb354258910b67850d --- /dev/null +++ b/validationmdl/validationcore/validator.go @@ -0,0 +1,294 @@ +package govalidator + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "reflect" + "strings" +) + +const ( + tagIdentifier = "json" //tagName idetify the struct tag for govalidator + tagSeparator = "|" //tagSeparator use to separate tags in struct + defaultFormSize int64 = 1024 * 1024 * 1 +) + +type ( + // MapData represents basic data structure for govalidator Rules and Messages + MapData map[string][]string + + // Options describes configuration option for validator + Options struct { + Data interface{} // Data represents structure for JSON body + Request *http.Request + RequiredDefault bool // RequiredDefault represents if all the fields are by default required or not + Rules MapData // Rules represents rules for form-data/x-url-encoded/query params data + Messages MapData // Messages represents custom/localize message for rules + TagIdentifier string // TagIdentifier represents struct tag identifier, e.g: json or validate etc + FormSize int64 //Form represents the multipart forom data max memory size in bytes + JSONData []byte // For validating JSON data + } + + // Validator represents a validator with options + Validator struct { + Opts Options // Opts contains all the options for validator + } +) + +// New return a new validator object using provided options +func New(opts Options) *Validator { + return &Validator{Opts: opts} +} + +// getMessage return if a custom message exist against the field name and rule +// if not available it return an empty string +func (v *Validator) getCustomMessage(field, rule string) string { + tmp := rule + ":" + if msgList, ok := v.Opts.Messages[field]; ok { + for _, m := range msgList { + //if rules has params, remove params. e.g: between:3,5 would be between + if strings.Contains(rule, ":") { + rule = strings.Split(rule, ":")[0] + } + if strings.HasPrefix(m, tmp) { + return strings.TrimPrefix(m, tmp) + } + } + } + return "" +} + +// SetDefaultRequired change the required behavior of fields +// Default value if false +// If SetDefaultRequired set to true then it will mark all the field in the rules list as required +func (v *Validator) SetDefaultRequired(required bool) { + v.Opts.RequiredDefault = required +} + +// SetTagIdentifier change the default tag identifier (json) to your custom tag. +func (v *Validator) SetTagIdentifier(identifier string) { + v.Opts.TagIdentifier = identifier +} + +// Validate validate request data like form-data, x-www-form-urlencoded and query params +// see example in README.md file +// ref: https://github.com/thedevsaddam/govalidator#example +func (v *Validator) Validate() url.Values { + // if request object and rules not passed rise a panic + if len(v.Opts.Rules) == 0 || v.Opts.Request == nil { + panic(errValidateArgsMismatch) + } + errsBag := url.Values{} + + // get non required rules + nr := v.getNonRequiredFields() + + for field, rules := range v.Opts.Rules { + if _, ok := nr[field]; ok { + continue + } + for _, rule := range rules { + if !isRuleExist(rule) { + panic(fmt.Errorf("govalidator: %s is not a valid rule", rule)) + } + msg := v.getCustomMessage(field, rule) + // validate file + if strings.HasPrefix(field, "file:") { + fld := strings.TrimPrefix(field, "file:") + file, fh, _ := v.Opts.Request.FormFile(fld) + if file != nil && fh.Filename != "" { + validateFiles(v.Opts.Request, fld, rule, msg, errsBag) + validateCustomRules(fld, rule, msg, file, errsBag) + } else { + validateCustomRules(fld, rule, msg, nil, errsBag) + } + } else { + // validate if custom rules exist + reqVal := strings.TrimSpace(v.Opts.Request.Form.Get(field)) + validateCustomRules(field, rule, msg, reqVal, errsBag) + } + } + } + + return errsBag +} + +// getNonRequiredFields remove non required rules fields from rules if requiredDefault field is false +// 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) + } else { + v.Opts.Request.ParseMultipartForm(defaultFormSize) + } + + inputs := v.Opts.Request.Form + nr := make(map[string]struct{}) + if !v.Opts.RequiredDefault { + for k, r := range v.Opts.Rules { + isFile := strings.HasPrefix(k, "file:") + if _, ok := inputs[k]; !ok && !isFile { + if !isContainRequiredField(r) { + nr[k] = struct{}{} + } + } + } + } + return nr +} + +// ValidateJSON validate request data from JSON body to Go struct +// see example in README.md file +func (v *Validator) ValidateJSON() url.Values { + if len(v.Opts.Rules) == 0 { + panic(errValidateArgsMismatch) + } + if len(v.Opts.Rules) == 0 || v.Opts.Request == nil { + panic(errValidateArgsMismatch) + } + if reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr { + panic(errRequirePtr) + } + 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 + } + r := roller{} + r.setTagIdentifier(tagIdentifier) + if v.Opts.TagIdentifier != "" { + r.setTagIdentifier(v.Opts.TagIdentifier) + } + r.setTagSeparator(tagSeparator) + r.start(v.Opts.Data) + + //clean if the key is not exist or value is empty or zero value + nr := v.getNonRequiredJSONFields(r.getFlatMap()) + + for field, rules := range v.Opts.Rules { + if _, ok := nr[field]; ok { + continue + } + value, _ := r.getFlatVal(field) + for _, rule := range rules { + if !isRuleExist(rule) { + panic(fmt.Errorf("govalidator: %s is not a valid rule", rule)) + } + msg := v.getCustomMessage(field, rule) + validateCustomRules(field, rule, msg, value, errsBag) + } + } + + return errsBag +} + +// getNonRequiredJSONFields get non required rules fields from rules if requiredDefault field is false +// and if the input data is empty for this field +func (v *Validator) getNonRequiredJSONFields(inputs map[string]interface{}) map[string]struct{} { + nr := make(map[string]struct{}) + if !v.Opts.RequiredDefault { + for k, r := range v.Opts.Rules { + if val := inputs[k]; isEmpty(val) { + if !isContainRequiredField(r) { + nr[k] = struct{}{} + } + } + } + } + return nr +} + +//ValidateJSONData method +func (v *Validator) ValidateJSONData() url.Values { + + if len(v.Opts.Rules) == 0 { + panic(errValidateArgsMismatch) + } + + errsBag := url.Values{} + + //This code is working - Do not delete + unmarshalError := json.Unmarshal(v.Opts.JSONData, &v.Opts.Data) + if unmarshalError != nil { + errsBag.Add("_error", unmarshalError.Error()) + return errsBag + } + + // data := make(map[string]interface{}, 0) + // v.Opts.Data = &data + + // decodeError := json.NewDecoder(bytes.NewBuffer(v.Opts.JSONData)).Decode(v.Opts.Data) + // if decodeError != nil { + // errsBag.Add("_error", decodeError.Error()) + // return errsBag + // } + + r := roller{} + r.setTagIdentifier(tagIdentifier) + if v.Opts.TagIdentifier != "" { + r.setTagIdentifier(v.Opts.TagIdentifier) + } + r.setTagSeparator(tagSeparator) + r.start(v.Opts.Data) + + //clean if the key is not exist or value is empty or zero value + nr := v.getNonRequiredJSONFields(r.getFlatMap()) + + for field, rules := range v.Opts.Rules { + if _, ok := nr[field]; ok { + continue + } + value, _ := r.getFlatVal(field) + for _, rule := range rules { + if !isRuleExist(rule) { + panic(fmt.Errorf("govalidator: %s is not a valid rule", rule)) + } + msg := v.getCustomMessage(field, rule) + validateCustomRules(field, rule, msg, value, errsBag) + } + } + + return errsBag +} + +//ValidateJSONData method +func (v *Validator) ValidateJSONString() url.Values { + + if len(v.Opts.Rules) == 0 { + panic(errValidateArgsMismatch) + } + + errsBag := url.Values{} + + r := roller{} + r.setTagIdentifier(tagIdentifier) + if v.Opts.TagIdentifier != "" { + r.setTagIdentifier(v.Opts.TagIdentifier) + } + r.setTagSeparator(tagSeparator) + r.start(v.Opts.Data) + + //clean if the key is not exist or value is empty or zero value + nr := v.getNonRequiredJSONFields(r.getFlatMap()) + + for field, rules := range v.Opts.Rules { + if _, ok := nr[field]; ok { + continue + } + value, _ := r.getFlatVal(field) + for _, rule := range rules { + if !isRuleExist(rule) { + panic(fmt.Errorf("govalidator: %s is not a valid rule", rule)) + } + msg := v.getCustomMessage(field, rule) + validateCustomRules(field, rule, msg, value, errsBag) + } + } + + return errsBag +} diff --git a/validationmdl/validationcore/validator_test.go b/validationmdl/validationcore/validator_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6d1397f0a09c8a24c5f0469a4ef320dce19ba519 --- /dev/null +++ b/validationmdl/validationcore/validator_test.go @@ -0,0 +1,186 @@ +package govalidator + +import ( + "bytes" + "encoding/json" + "net/http" + "net/url" + "testing" +) + +func TestValidator_SetDefaultRequired(t *testing.T) { + v := New(Options{}) + v.SetDefaultRequired(true) + if !v.Opts.RequiredDefault { + t.Error("SetDefaultRequired failed") + } +} + +func TestValidator_Validate(t *testing.T) { + var URL *url.URL + URL, _ = url.Parse("http://www.example.com") + params := url.Values{} + params.Add("name", "John Doe") + params.Add("username", "jhondoe") + params.Add("email", "john@mail.com") + params.Add("zip", "8233") + URL.RawQuery = params.Encode() + r, _ := http.NewRequest("GET", URL.String(), nil) + rulesList := MapData{ + "name": []string{"required"}, + "age": []string{"between:5,16"}, + "email": []string{"email"}, + "zip": []string{"digits:4"}, + } + + opts := Options{ + Request: r, + Rules: rulesList, + } + v := New(opts) + validationError := v.Validate() + if len(validationError) > 0 { + t.Error("Validate failed to validate correct inputs!") + } + + defer func() { + if r := recover(); r == nil { + t.Errorf("Validate did not panic") + } + }() + + v1 := New(Options{Rules: MapData{}}) + v1.Validate() +} + +func Benchmark_Validate(b *testing.B) { + var URL *url.URL + URL, _ = url.Parse("http://www.example.com") + params := url.Values{} + params.Add("name", "John Doe") + params.Add("age", "27") + params.Add("email", "john@mail.com") + params.Add("zip", "8233") + URL.RawQuery = params.Encode() + r, _ := http.NewRequest("GET", URL.String(), nil) + rulesList := MapData{ + "name": []string{"required"}, + "age": []string{"numeric_between:18,60"}, + "email": []string{"email"}, + "zip": []string{"digits:4"}, + } + + opts := Options{ + Request: r, + Rules: rulesList, + } + v := New(opts) + for n := 0; n < b.N; n++ { + v.Validate() + } +} + +//============ validate json test ==================== + +func TestValidator_ValidateJSON(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"` + } + + 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"}, + } + + var user User + + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + opts := Options{ + Request: req, + Data: &user, + Rules: rules, + } + + vd := New(opts) + vd.SetTagIdentifier("json") + validationErr := vd.ValidateJSON() + if len(validationErr) != 5 { + t.Error("ValidateJSON failed") + } +} + +func TestValidator_ValidateJSON_NULLValue(t *testing.T) { + type User struct { + Name string `json:"name"` + Count Int `json:"count"` + Option Int `json:"option"` + Active Bool `json:"active"` + } + + rules := MapData{ + "name": []string{"required"}, + "count": []string{"required"}, + "option": []string{"required"}, + "active": []string{"required"}, + } + + postUser := map[string]interface{}{ + "name": "John Doe", + "count": 0, + "option": nil, + "active": nil, + } + + var user User + body, _ := json.Marshal(postUser) + req, _ := http.NewRequest("POST", "http://www.example.com", bytes.NewReader(body)) + + opts := Options{ + Request: req, + Data: &user, + Rules: rules, + } + + vd := New(opts) + vd.SetTagIdentifier("json") + validationErr := vd.ValidateJSON() + if len(validationErr) != 2 { + t.Error("ValidateJSON failed") + } +} + +func TestValidator_ValidateJSON_panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("ValidateJSON did not panic") + } + }() + + opts := Options{} + + vd := New(opts) + validationErr := vd.ValidateJSON() + if len(validationErr) != 5 { + t.Error("ValidateJSON failed") + } +} diff --git a/validationmdl/validationmdl.go b/validationmdl/validationmdl.go index 3e84dec45515e2d71ac6aa5f7b01e329b179fdc5..c11f1e13ed6aff7b823be4db40f864e67c0ba6e0 100644 --- a/validationmdl/validationmdl.go +++ b/validationmdl/validationmdl.go @@ -1,47 +1,115 @@ package validationmdl import ( - "errors" - "net/http" "net/url" - "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/errormdl" - govalidator "github.com/asaskevich/govalidator" - requestValidator "github.com/thedevsaddam/govalidator" + "github.com/tidwall/gjson" + + "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl" + + requestValidator "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/validationmdl/validationcore" ) -//ValidateRequest func validates the given model -func ValidateRequest(httpRequest *http.Request, validationRules, validationMessages requestValidator.MapData) map[string]interface{} { +// var cnt int + +// //ValidateRequest func validates the given model +// func ValidateRequest(httpRequest *http.Request, validationRules, validationMessages requestValidator.MapData) map[string]interface{} { + +// cnt++ +// //Get the content type of the request as validations for content types are different +// contentType := httpRequest.Header.Get("Content-Type") + +// //Initialize the validation errors as blank +// var validationErrors url.Values + +// //Set validator options +// opts := requestValidator.Options{ +// Request: httpRequest, +// Rules: validationRules, +// } + +// //Set custom validation messages if sent from user +// if validationMessages != nil { +// opts.Messages = validationMessages +// } + +// if contentType == "application/json" || contentType == "text/plain" { +// //Validate request type json and text (RAW data from request) +// data := make(map[string]interface{}, 0) +// opts.Data = &data +// validator := requestValidator.New(opts) +// validationErrors = validator.ValidateJSON() + +// } else { +// //Validate request type form-data, form-urlencoded +// validator := requestValidator.New(opts) +// validationErrors = validator.Validate() +// } + +// if len(validationErrors) > 0 { +// errs := map[string]interface{}{"validationErrors": validationErrors} +// return errs +// } +// return nil +// } + +// // ValidateStruct validates the structures with govalidator +// func ValidateStruct(structToValidate interface{}) error { +// validationResult, err := govalidator.ValidateStruct(structToValidate) +// if err != nil { +// return err +// } +// if !errormdl.CheckBool(validationResult) { +// return errors.New("ERROR:ValidateStruct function error") +// } +// return nil +// } + +// //ValidateJSONData to validate JSON data +// func ValidateJSONData(jsonData []byte, validationRules, validationMessages requestValidator.MapData) map[string]interface{} { + +// //Initialize the validation errors as blank +// var validationErrors url.Values + +// //Set validator options +// opts := requestValidator.Options{ +// Rules: validationRules, +// JSONData: jsonData, +// } - //Get the content type of the request as validations for content types are different - contentType := httpRequest.Header.Get("Content-Type") +// //Set custom validation messages if sent from user +// if validationMessages != nil { +// opts.Messages = validationMessages +// } + +// validator := requestValidator.New(opts) +// validationErrors = validator.ValidateJSONData() + +// if len(validationErrors) > 0 { +// errs := map[string]interface{}{"validationErrors": validationErrors} +// return errs +// } + +// return nil +// } + +// ValidateJSONString to validate JSON data +func ValidateJSONString(jsonString string, validationRules, validationMessages requestValidator.MapData) map[string]interface{} { - //Initialize the validation errors as blank var validationErrors url.Values - //Set validator options opts := requestValidator.Options{ - Request: httpRequest, - Rules: validationRules, + Rules: validationRules, } - - //Set custom validation messages if sent from user - if validationMessages != nil { - opts.Messages = validationMessages + data, ok := gjson.Parse(jsonString).Value().(map[string]interface{}) + if !ok { + loggermdl.LogError("can not cast to map") + return nil } + opts.Data = data - if contentType == "application/json" || contentType == "text/plain" { - //Validate request type json and text (RAW data from request) - data := make(map[string]interface{}, 0) - opts.Data = &data - validator := requestValidator.New(opts) - validationErrors = validator.ValidateJSON() - - } else { - //Validate request type form-data, form-urlencoded - validator := requestValidator.New(opts) - validationErrors = validator.Validate() - } + validator := requestValidator.New(opts) + validationErrors = validator.ValidateJSONString() if len(validationErrors) > 0 { errs := map[string]interface{}{"validationErrors": validationErrors} @@ -50,14 +118,25 @@ func ValidateRequest(httpRequest *http.Request, validationRules, validationMessa return nil } -// ValidateStruct validates the structures with govalidator -func ValidateStruct(structToValidate interface{}) error { - validationResult, err := govalidator.ValidateStruct(structToValidate) - if err != nil { - return err +// ValidateGJSONResult to validate JSON data +func ValidateGJSONResult(rs *gjson.Result, validationRules, validationMessages requestValidator.MapData) map[string]interface{} { + + var validationErrors url.Values + opts := requestValidator.Options{ + Rules: validationRules, } - if !errormdl.CheckBool(validationResult) { - return errors.New("ERROR:ValidateStruct function error") + data, ok := rs.Value().(map[string]interface{}) + if !ok { + loggermdl.LogError("can not cast to map") + return nil + } + opts.Data = data + + validator := requestValidator.New(opts) + validationErrors = validator.ValidateJSONString() + if len(validationErrors) > 0 { + errs := map[string]interface{}{"validationErrors": validationErrors} + return errs } return nil } diff --git a/validationmdl/validationmdl_test.go b/validationmdl/validationmdl_test.go index 46bbdbb5cc41f90974d10a7123dfca08fd0025da..03230890080198ab6ad8efd19ae3634801bd0c62 100644 --- a/validationmdl/validationmdl_test.go +++ b/validationmdl/validationmdl_test.go @@ -1,18 +1,10 @@ package validationmdl import ( - "bytes" - "net/http" - "net/url" - "reflect" - "strings" "testing" - "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/errormdl" - "github.com/caibirdme/yql" - "github.com/pquerna/ffjson/ffjson" - "github.com/stretchr/testify/assert" - requestValidator "github.com/thedevsaddam/govalidator" + "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl" + requestValidator "corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/validationmdl/validationcore" ) type RequestBodyData struct { @@ -32,295 +24,401 @@ var validationMessages = requestValidator.MapData{ "password": []string{"required:Password required"}, } -func TestValidateRequest(t *testing.T) { - - //Cases for sending raw data in request - //Case 1: Http request for sunny day scenario - sunnyDayData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} - sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) - sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) - sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") - - //Case 2 : Http request for blank email - blankEmailData := RequestBodyData{Email: "", Password: "test"} - blankEmailByteArray, _ := ffjson.Marshal(&blankEmailData) - blankEmailHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(blankEmailByteArray))) - blankEmailHTTPRequest.Header.Set("Content-Type", "application/json") - blankEmailExpectedResult := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email required", "Email min len", "Invalid email"}, - }, - } +// func TestValidateRequest(t *testing.T) { + +// //Cases for sending raw data in request +// //Case 1: Http request for sunny day scenario +// sunnyDayData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} +// sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) +// sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) +// sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") + +// //Case 2 : Http request for blank email +// blankEmailData := RequestBodyData{Email: "", Password: "test"} +// blankEmailByteArray, _ := ffjson.Marshal(&blankEmailData) +// blankEmailHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(blankEmailByteArray))) +// blankEmailHTTPRequest.Header.Set("Content-Type", "application/json") +// blankEmailExpectedResult := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email required", "Email min len", "Invalid email"}, +// }, +// } + +// //Case 3 : Http request for blank password +// blankPasswordData := RequestBodyData{Email: "test@mkcl.org", Password: ""} +// blankPasswordByteArray, _ := ffjson.Marshal(&blankPasswordData) +// blankPasswordHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(blankPasswordByteArray))) +// blankPasswordHTTPRequest.Header.Set("Content-Type", "application/json") +// blankPasswordExpectedResult := map[string]interface{}{ +// "validationErrors": url.Values{ +// "password": []string{"Password required"}, +// }, +// } + +// //Case 4 : Http request for email with shorter length than required +// shortEmailLengthData := RequestBodyData{Email: "a@c.v", Password: "test"} +// shortEmailLengthByteArray, _ := ffjson.Marshal(&shortEmailLengthData) +// shortEmailLengthHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(shortEmailLengthByteArray))) +// shortEmailLengthHTTPRequest.Header.Set("Content-Type", "application/json") +// shortEmailLengthExpectedResult := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email min len"}, +// }, +// } + +// //Case 5 : Http request for email with longer length than required +// longEmailLengthData := RequestBodyData{Email: "testEmail@Testcompany.testdomain", Password: "test"} +// longEmailLengthByteArray, _ := ffjson.Marshal(&longEmailLengthData) +// longEmailLengthHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(longEmailLengthByteArray))) +// longEmailLengthHTTPRequest.Header.Set("Content-Type", "application/json") +// longEmailLengthExpectedResult := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email max len"}, +// }, +// } + +// //Case 6 : Http request for invalid email id +// invalidEmailData := RequestBodyData{Email: "testemail", Password: "test"} +// invalidEmailByteArray, _ := ffjson.Marshal(&invalidEmailData) +// invalidEmailHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(invalidEmailByteArray))) +// invalidEmailHTTPRequest.Header.Set("Content-Type", "application/json") +// invalidEmailExpectedResult := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Invalid email"}, +// }, +// } + +// //Case 7 : Http request for blank email using form encoding +// sunnyDayForm := url.Values{} +// sunnyDayForm.Add("email", "") +// sunnyDayForm.Add("password", "password") +// sunnyDayHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(sunnyDayForm.Encode())) +// sunnyDayHTTPRequestFormEnc.PostForm = sunnyDayForm +// sunnyDayHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") +// sunnyDayExpectedResultFormEnc := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email required", "Email min len", "Invalid email"}, +// }, +// } + +// //Case 8 : Http request for blank email using form encoding +// blankEmailForm := url.Values{} +// blankEmailForm.Add("email", "") +// blankEmailForm.Add("password", "password") +// blankEmailHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(blankEmailForm.Encode())) +// blankEmailHTTPRequestFormEnc.PostForm = blankEmailForm +// blankEmailHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") +// blankEmailExpectedResultFormEnc := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email required", "Email min len", "Invalid email"}, +// }, +// } + +// //Case 9 : Http request for blank password using form encoding +// blankPasswordForm := url.Values{} +// blankPasswordForm.Add("email", "test@mkcl.org") +// blankPasswordForm.Add("password", "") +// blankPasswordHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(blankPasswordForm.Encode())) +// blankPasswordHTTPRequestFormEnc.PostForm = blankPasswordForm +// blankPasswordHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") +// blankPasswordExpectedResultFormEnc := map[string]interface{}{ +// "validationErrors": url.Values{ +// "password": []string{"Password required"}, +// }, +// } + +// //Case 10 : Http request for email with shorter length than required using form encoding +// shortEmailLengthForm := url.Values{} +// shortEmailLengthForm.Add("email", "a@v.c") +// shortEmailLengthForm.Add("password", "testPass") +// shortEmailLengthHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(shortEmailLengthForm.Encode())) +// shortEmailLengthHTTPRequestFormEnc.PostForm = shortEmailLengthForm +// shortEmailLengthHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") +// shortEmailLengthExpectedResultFormEnc := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email min len"}, +// }, +// } + +// //Case 11 : Http request for email with longer length than required using form encoding +// longEmailLengthForm := url.Values{} +// longEmailLengthForm.Add("email", "testEmail@Testcompany.testdomain") +// longEmailLengthForm.Add("password", "testPass") +// longEmailLengthHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(longEmailLengthForm.Encode())) +// longEmailLengthHTTPRequestFormEnc.PostForm = longEmailLengthForm +// longEmailLengthHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") +// longEmailLengthExpectedResultFormEnc := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Email max len"}, +// }, +// } + +// //Case 12 : Http request for invalid email using form encoding +// invalidEmailLengthForm := url.Values{} +// invalidEmailLengthForm.Add("email", "testasdfasdf") +// invalidEmailLengthForm.Add("password", "test") +// invalidEmailLengthHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(invalidEmailLengthForm.Encode())) +// invalidEmailLengthHTTPRequestFormEnc.PostForm = invalidEmailLengthForm +// invalidEmailLengthHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") +// invalidEmailLengthExpectedResultFormEnc := map[string]interface{}{ +// "validationErrors": url.Values{ +// "email": []string{"Invalid email"}, +// }, +// } + +// type args struct { +// httpRequest *http.Request +// validationRules requestValidator.MapData +// validationMessages requestValidator.MapData +// } +// tests := []struct { +// name string +// args args +// want map[string]interface{} +// }{ +// {"TestSunnyDay", args{sunnyDayHTTPRequest, validationRules, validationMessages}, nil}, +// {"BlankEmailValidation", args{blankEmailHTTPRequest, validationRules, validationMessages}, blankEmailExpectedResult}, +// {"BlankPasswordValidation", args{blankPasswordHTTPRequest, validationRules, validationMessages}, blankPasswordExpectedResult}, +// {"ShortEmailLengthValidation", args{shortEmailLengthHTTPRequest, validationRules, validationMessages}, shortEmailLengthExpectedResult}, +// {"LongEmailLengthValidation", args{longEmailLengthHTTPRequest, validationRules, validationMessages}, longEmailLengthExpectedResult}, +// {"InvalidEmailValidation", args{invalidEmailHTTPRequest, validationRules, validationMessages}, invalidEmailExpectedResult}, +// {"SunnyDayFormEnc", args{sunnyDayHTTPRequestFormEnc, validationRules, validationMessages}, sunnyDayExpectedResultFormEnc}, +// {"BlankEmailValidationFormEnc", args{blankEmailHTTPRequestFormEnc, validationRules, validationMessages}, blankEmailExpectedResultFormEnc}, +// {"BlankPasswordValidationFormEnc", args{blankPasswordHTTPRequestFormEnc, validationRules, validationMessages}, blankPasswordExpectedResultFormEnc}, +// {"ShortEmailLengthValidationFormEnc", args{shortEmailLengthHTTPRequestFormEnc, validationRules, validationMessages}, shortEmailLengthExpectedResultFormEnc}, +// {"LongEmailLengthValidationFormEnc", args{longEmailLengthHTTPRequestFormEnc, validationRules, validationMessages}, longEmailLengthExpectedResultFormEnc}, +// {"InvalidEmailLengthValidationFormEnc", args{invalidEmailLengthHTTPRequestFormEnc, validationRules, validationMessages}, invalidEmailLengthExpectedResultFormEnc}, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := ValidateRequest(tt.args.httpRequest, tt.args.validationRules, tt.args.validationMessages); !reflect.DeepEqual(got, tt.want) { +// t.Errorf("ValidateRequest() = %v, want %v", got, tt.want) +// } +// }) +// } + +// } + +// func BenchmarkValidateRequest(b *testing.B) { + +// //Validation rules +// validationRules := requestValidator.MapData{ +// "email": []string{"required", "min:5", "max:20", "email"}, +// "password": []string{"required"}, +// } +// //Validation messages +// validationMessages := requestValidator.MapData{ +// "email": []string{"required:Email Id is required", "min:Min length 5 required", "max:Max length 20 allowed", "email:Enter a valid email"}, +// "password": []string{"required:Password is required"}, +// } + +// sunnyDayData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} +// sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) +// sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) +// sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") + +// for i := 0; i < b.N; i++ { + +// ValidateRequest(sunnyDayHTTPRequest, validationRules, validationMessages) + +// } +// } + +// func BenchmarkYQLTestAgainstValidator(b *testing.B) { + +// for i := 0; i < b.N; i++ { + +// rawYQL := `age>=23` +// yql.Match(rawYQL, map[string]interface{}{ +// "age": int64(24), +// }) +// } +// } + +// func BenchmarkValidationTestAgainstYQL(b *testing.B) { + +// type TestData struct { +// Age int64 `json:"age"` +// } + +// validationRules := requestValidator.MapData{ +// "age": []string{"required", "min:23"}, +// } + +// sunnyDayData := TestData{Age: 24} +// sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) +// sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) +// sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") + +// for i := 0; i < b.N; i++ { +// ValidateRequest(sunnyDayHTTPRequest, validationRules, nil) +// } +// } - //Case 3 : Http request for blank password - blankPasswordData := RequestBodyData{Email: "test@mkcl.org", Password: ""} - blankPasswordByteArray, _ := ffjson.Marshal(&blankPasswordData) - blankPasswordHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(blankPasswordByteArray))) - blankPasswordHTTPRequest.Header.Set("Content-Type", "application/json") - blankPasswordExpectedResult := map[string]interface{}{ - "validationErrors": url.Values{ - "password": []string{"Password required"}, - }, - } +type StructToValidate struct { + Name string `json:"name" valid:"required,alpha,length(4|8)"` + Email string `json:"email" valid:"required,email"` + Age int `json:"age" valid:"required,range(18|50)"` + Mobile string `json:"mobile" valid:"required"` +} - //Case 4 : Http request for email with shorter length than required - shortEmailLengthData := RequestBodyData{Email: "a@c.v", Password: "test"} - shortEmailLengthByteArray, _ := ffjson.Marshal(&shortEmailLengthData) - shortEmailLengthHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(shortEmailLengthByteArray))) - shortEmailLengthHTTPRequest.Header.Set("Content-Type", "application/json") - shortEmailLengthExpectedResult := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email min len"}, - }, - } +func GetProperStruct() StructToValidate { + structToValidate := StructToValidate{} + structToValidate.Name = "testmkcl" + structToValidate.Email = "test@mkcl.org" + structToValidate.Age = 40 + structToValidate.Mobile = "1234567890" + return structToValidate +} - //Case 5 : Http request for email with longer length than required - longEmailLengthData := RequestBodyData{Email: "testEmail@Testcompany.testdomain", Password: "test"} - longEmailLengthByteArray, _ := ffjson.Marshal(&longEmailLengthData) - longEmailLengthHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(longEmailLengthByteArray))) - longEmailLengthHTTPRequest.Header.Set("Content-Type", "application/json") - longEmailLengthExpectedResult := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email max len"}, - }, - } +func GetErrorStruct() StructToValidate { + structToValidate := StructToValidate{} + structToValidate.Name = "" + structToValidate.Email = "testmkcl.org" + structToValidate.Age = 40 + structToValidate.Mobile = "1234567890" + return structToValidate +} +func GetEmptyStruct() StructToValidate { + structToValidate := StructToValidate{} + return structToValidate +} - //Case 6 : Http request for invalid email id - invalidEmailData := RequestBodyData{Email: "testemail", Password: "test"} - invalidEmailByteArray, _ := ffjson.Marshal(&invalidEmailData) - invalidEmailHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(invalidEmailByteArray))) - invalidEmailHTTPRequest.Header.Set("Content-Type", "application/json") - invalidEmailExpectedResult := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Invalid email"}, - }, - } +// func TestValidateStruct(t *testing.T) { +// structToValidate := GetProperStruct() +// err := ValidateStruct(structToValidate) +// assert.NoError(t, err, "This should not return error") +// } +// func TestValidateStructEmptyStruct(t *testing.T) { +// errormdl.IsTestingNegetiveCaseOnCheckBool = true +// structToValidate := GetEmptyStruct() +// err := ValidateStruct(structToValidate) +// errormdl.IsTestingNegetiveCaseOnCheckBool = false +// assert.Error(t, err, "This should return error") +// } - //Case 7 : Http request for blank email using form encoding - sunnyDayForm := url.Values{} - sunnyDayForm.Add("email", "") - sunnyDayForm.Add("password", "password") - sunnyDayHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(sunnyDayForm.Encode())) - sunnyDayHTTPRequestFormEnc.PostForm = sunnyDayForm - sunnyDayHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") - sunnyDayExpectedResultFormEnc := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email required", "Email min len", "Invalid email"}, - }, - } +// func TestValidateStructInvalidStruct(t *testing.T) { +// errormdl.IsTestingNegetiveCaseOnCheckBool = true +// structToValidate := GetErrorStruct() +// err := ValidateStruct(structToValidate) +// errormdl.IsTestingNegetiveCaseOnCheckBool = false +// assert.Error(t, err, "This should return error") +// } +// func TestValidateStructTypeCheck(t *testing.T) { +// errormdl.IsTestingNegetiveCaseOnCheckBool = true +// structToValidate := GetProperStruct() +// err := ValidateStruct(structToValidate) +// errormdl.IsTestingNegetiveCaseOnCheckBool = false +// assert.Error(t, err, "This should return error") +// } - //Case 8 : Http request for blank email using form encoding - blankEmailForm := url.Values{} - blankEmailForm.Add("email", "") - blankEmailForm.Add("password", "password") - blankEmailHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(blankEmailForm.Encode())) - blankEmailHTTPRequestFormEnc.PostForm = blankEmailForm - blankEmailHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") - blankEmailExpectedResultFormEnc := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email required", "Email min len", "Invalid email"}, - }, - } +// func BenchmarkValidateStruct(b *testing.B) { +// structToValidate := GetProperStruct() +// for i := 0; i < b.N; i++ { +// ValidateStruct(structToValidate) +// } +// } - //Case 9 : Http request for blank password using form encoding - blankPasswordForm := url.Values{} - blankPasswordForm.Add("email", "test@mkcl.org") - blankPasswordForm.Add("password", "") - blankPasswordHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(blankPasswordForm.Encode())) - blankPasswordHTTPRequestFormEnc.PostForm = blankPasswordForm - blankPasswordHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") - blankPasswordExpectedResultFormEnc := map[string]interface{}{ - "validationErrors": url.Values{ - "password": []string{"Password required"}, - }, - } +// func TestNewJsonValidation(t *testing.T) { - //Case 10 : Http request for email with shorter length than required using form encoding - shortEmailLengthForm := url.Values{} - shortEmailLengthForm.Add("email", "a@v.c") - shortEmailLengthForm.Add("password", "testPass") - shortEmailLengthHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(shortEmailLengthForm.Encode())) - shortEmailLengthHTTPRequestFormEnc.PostForm = shortEmailLengthForm - shortEmailLengthHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") - shortEmailLengthExpectedResultFormEnc := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email min len"}, - }, - } +// // json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data) - //Case 11 : Http request for email with longer length than required using form encoding - longEmailLengthForm := url.Values{} - longEmailLengthForm.Add("email", "testEmail@Testcompany.testdomain") - longEmailLengthForm.Add("password", "testPass") - longEmailLengthHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(longEmailLengthForm.Encode())) - longEmailLengthHTTPRequestFormEnc.PostForm = longEmailLengthForm - longEmailLengthHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") - longEmailLengthExpectedResultFormEnc := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Email max len"}, - }, - } +// requestBodyData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} - //Case 12 : Http request for invalid email using form encoding - invalidEmailLengthForm := url.Values{} - invalidEmailLengthForm.Add("email", "testasdfasdf") - invalidEmailLengthForm.Add("password", "test") - invalidEmailLengthHTTPRequestFormEnc, _ := http.NewRequest("POST", "test.com", strings.NewReader(invalidEmailLengthForm.Encode())) - invalidEmailLengthHTTPRequestFormEnc.PostForm = invalidEmailLengthForm - invalidEmailLengthHTTPRequestFormEnc.Header.Set("Content-Type", "application/x-www-form-urlencoded") - invalidEmailLengthExpectedResultFormEnc := map[string]interface{}{ - "validationErrors": url.Values{ - "email": []string{"Invalid email"}, - }, - } +// dataByteArray, _ := ffjson.Marshal(&requestBodyData) - type args struct { - httpRequest *http.Request - validationRules requestValidator.MapData - validationMessages requestValidator.MapData - } - tests := []struct { - name string - args args - want map[string]interface{} - }{ - {"TestSunnyDay", args{sunnyDayHTTPRequest, validationRules, validationMessages}, nil}, - {"BlankEmailValidation", args{blankEmailHTTPRequest, validationRules, validationMessages}, blankEmailExpectedResult}, - {"BlankPasswordValidation", args{blankPasswordHTTPRequest, validationRules, validationMessages}, blankPasswordExpectedResult}, - {"ShortEmailLengthValidation", args{shortEmailLengthHTTPRequest, validationRules, validationMessages}, shortEmailLengthExpectedResult}, - {"LongEmailLengthValidation", args{longEmailLengthHTTPRequest, validationRules, validationMessages}, longEmailLengthExpectedResult}, - {"InvalidEmailValidation", args{invalidEmailHTTPRequest, validationRules, validationMessages}, invalidEmailExpectedResult}, - {"SunnyDayFormEnc", args{sunnyDayHTTPRequestFormEnc, validationRules, validationMessages}, sunnyDayExpectedResultFormEnc}, - {"BlankEmailValidationFormEnc", args{blankEmailHTTPRequestFormEnc, validationRules, validationMessages}, blankEmailExpectedResultFormEnc}, - {"BlankPasswordValidationFormEnc", args{blankPasswordHTTPRequestFormEnc, validationRules, validationMessages}, blankPasswordExpectedResultFormEnc}, - {"ShortEmailLengthValidationFormEnc", args{shortEmailLengthHTTPRequestFormEnc, validationRules, validationMessages}, shortEmailLengthExpectedResultFormEnc}, - {"LongEmailLengthValidationFormEnc", args{longEmailLengthHTTPRequestFormEnc, validationRules, validationMessages}, longEmailLengthExpectedResultFormEnc}, - {"InvalidEmailLengthValidationFormEnc", args{invalidEmailLengthHTTPRequestFormEnc, validationRules, validationMessages}, invalidEmailLengthExpectedResultFormEnc}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := ValidateRequest(tt.args.httpRequest, tt.args.validationRules, tt.args.validationMessages); !reflect.DeepEqual(got, tt.want) { - t.Errorf("ValidateRequest() = %v, want %v", got, tt.want) - } - }) - } +// opts := requestValidator.Options{ +// Rules: validationRules, +// JSONData: dataByteArray, +// } -} +// validator := requestValidator.New(opts) +// validationErrors := validator.ValidateJSONData() -func BenchmarkValidateRequest(b *testing.B) { +// loggermdl.LogSpot("Validation error : ", validationErrors) +// } - //Validation rules - validationRules := requestValidator.MapData{ - "email": []string{"required", "min:5", "max:20", "email"}, - "password": []string{"required"}, - } - //Validation messages - validationMessages := requestValidator.MapData{ - "email": []string{"required:Email Id is required", "min:Min length 5 required", "max:Max length 20 allowed", "email:Enter a valid email"}, - "password": []string{"required:Password is required"}, - } +// func TestNewJsonValidationMDL(t *testing.T) { - sunnyDayData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} - sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) - sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) - sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") +// // json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data) - for i := 0; i < b.N; i++ { +// // jsonData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} +// // dataByteArray, _ := ffjson.Marshal(&jsonData) - ValidateRequest(sunnyDayHTTPRequest, validationRules, validationMessages) +// jsonString := `{"email":"test@mkcl.org","password":"test"}` - } -} +// // fmt.Println("json string : ", string(dataByteArray)) -func BenchmarkYQLTestAgainstValidator(b *testing.B) { +// validationErrors := ValidateJSONString(jsonString, validationRules, validationMessages) - for i := 0; i < b.N; i++ { +// loggermdl.LogSpot("Validation error : ", validationErrors) +// } - rawYQL := `age>=23` - yql.Match(rawYQL, map[string]interface{}{ - "age": int64(24), - }) - } -} +//Benchmarking method to test JSON validation without using http request +// func BenchmarkValidateJSON(b *testing.B) { -func BenchmarkValidationTestAgainstYQL(b *testing.B) { +// jsonData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} +// dataByteArray, _ := ffjson.Marshal(&jsonData) - type TestData struct { - Age int64 `json:"age"` - } +// for i := 0; i < b.N; i++ { +// ValidateJSONData(dataByteArray, validationRules, validationMessages) +// } - validationRules := requestValidator.MapData{ - "age": []string{"required", "min:23"}, - } +// // loggermdl.LogError("Count : ", requestValidator) +// } - sunnyDayData := TestData{Age: 24} - sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) - sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) - sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") +//Benchmarking method to test JSON validtion using http request +// func BenchmarkValidateJSONRequest(b *testing.B) { + +// sunnyDayData := RequestBodyData{Email: "test@mkcl.org", Password: "test"} +// sunnyDayByteArray, _ := ffjson.Marshal(&sunnyDayData) +// sunnyDayHTTPRequest, _ := http.NewRequest("POST", "test.com", bytes.NewBufferString(string(sunnyDayByteArray))) +// sunnyDayHTTPRequest.Header.Set("Content-Type", "application/json") + +// for i := 0; i < b.N; i++ { +// ValidateRequest(sunnyDayHTTPRequest, validationRules, validationMessages) +// } +// // loggermdl.LogError("Count : ", requestValidator.Cnt) +// } + +// //Benckmarking method to testing JSON string validation + +func BenchmarkValidateJSONString(b *testing.B) { + + jsonString := `{"email":"tessdcet@mkcl.org"}` + // jsonString := `{"email":"test@mkcl.org","password":"test"}` + + var validationRules = requestValidator.MapData{ + "email": []string{"required", "email"}, + // "password": []string{"required"}, + } for i := 0; i < b.N; i++ { - ValidateRequest(sunnyDayHTTPRequest, validationRules, nil) + ValidateJSONString(jsonString, validationRules, validationMessages) } } -type StructToValidate struct { - Name string `json:"name" valid:"required,alpha,length(4|8)"` - Email string `json:"email" valid:"required,email"` - Age int `json:"age" valid:"required,range(18|50)"` - Mobile string `json:"mobile" valid:"required"` -} +func TestValidateJSONString(t *testing.T) { + jsonString := `{"email": "xcvbnm,./dfghjkl"}` + // jsonString1 := `{"password":"testrfew@mkcl.org"}` + // jsonString := `{"email":"test@mkcl.org","password":"test"}` -func GetProperStruct() StructToValidate { - structToValidate := StructToValidate{} - structToValidate.Name = "testmkcl" - structToValidate.Email = "test@mkcl.org" - structToValidate.Age = 40 - structToValidate.Mobile = "1234567890" - return structToValidate -} + var validationRules1 = requestValidator.MapData{ + "email": []string{"required"}, + // "name": []string{"required"}, + } -func GetErrorStruct() StructToValidate { - structToValidate := StructToValidate{} - structToValidate.Name = "" - structToValidate.Email = "testmkcl.org" - structToValidate.Age = 40 - structToValidate.Mobile = "1234567890" - return structToValidate -} -func GetEmptyStruct() StructToValidate { - structToValidate := StructToValidate{} - return structToValidate -} -func TestValidateStruct(t *testing.T) { - structToValidate := GetProperStruct() - err := ValidateStruct(structToValidate) - assert.NoError(t, err, "This should not return error") -} -func TestValidateStructEmptyStruct(t *testing.T) { - errormdl.IsTestingNegetiveCaseOnCheckBool = true - structToValidate := GetEmptyStruct() - err := ValidateStruct(structToValidate) - errormdl.IsTestingNegetiveCaseOnCheckBool = false - assert.Error(t, err, "This should return error") -} + // var validationRules2 = requestValidator.MapData{ + // "password": []string{"required"}, + // "email": []string{"required"}, + // } -func TestValidateStructInvalidStruct(t *testing.T) { - errormdl.IsTestingNegetiveCaseOnCheckBool = true - structToValidate := GetErrorStruct() - err := ValidateStruct(structToValidate) - errormdl.IsTestingNegetiveCaseOnCheckBool = false - assert.Error(t, err, "This should return error") -} -func TestValidateStructTypeCheck(t *testing.T) { - errormdl.IsTestingNegetiveCaseOnCheckBool = true - structToValidate := GetProperStruct() - err := ValidateStruct(structToValidate) - errormdl.IsTestingNegetiveCaseOnCheckBool = false - assert.Error(t, err, "This should return error") -} -func BenchmarkValidateStruct(b *testing.B) { - structToValidate := GetProperStruct() - for i := 0; i < b.N; i++ { - ValidateStruct(structToValidate) + mapErr := ValidateJSONString(jsonString, validationRules1, validationMessages) + if mapErr != nil { + loggermdl.LogError(mapErr) } + // mapErr = ValidateJSONString(jsonString1, validationRules2, validationMessages) + // if mapErr != nil { + // loggermdl.LogError(mapErr) + // } }