├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── speed_test.go ├── val.go └── val_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | instal: 4 | - go get -v github.com/michaeljs1990/val 5 | 6 | go: 7 | - 1.1 8 | - 1.2 9 | - 1.3 10 | - 1.4 11 | - 1.5 12 | - 1.6 13 | - tip 14 | 15 | script: 16 | - go test github.com/michaeljs1990/val 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | val 2 | === 3 | [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.png)](http://godoc.org/github.com/michaeljs1990/val) 4 | [![Build Status](https://travis-ci.org/michaeljs1990/val.svg?branch=master)](https://travis-ci.org/michaeljs1990/val) 5 | 6 | Go JSON validation library. 7 | 8 | This library was developed to meet some validation needs that I needed. However I would like to build this into a much more robust set of tools. Please feel free to ask for any feature or submit a pull request. 9 | 10 | ## Start using it 11 | Run the following in your terminal to start using val. 12 | 13 | ``` 14 | go get github.com/michaeljs1990/val 15 | ``` 16 | Then import it in your Go! code: 17 | 18 | ``` 19 | import "github.com/michaeljs1990/val" 20 | ``` 21 | 22 | ## Update from an old version 23 | Run the following in your terminal to update val to the current master branch. 24 | 25 | ``` 26 | go get -u github.com/michaeljs1990/val 27 | ``` 28 | 29 | ## Example Usage 30 | 31 | #### Basic example 32 | 33 | Pointers are needed so go's json.Decode() call can function as one would expect. Pointers are now required in version 0.1 they were not implimented which lead to issues if you wanted json that was not required since int's would be set to 0 even if nothing was passed in causing you to potentially fail a test. 34 | 35 | ```go 36 | package main 37 | 38 | import ( 39 | "fmt" 40 | "github.com/michaeljs1990/val" 41 | "net/http" 42 | ) 43 | 44 | func handler(w http.ResponseWriter, r *http.Request) { 45 | 46 | var Register struct { 47 | Username *string `json:"username" validate:"required"` 48 | Password *string `json:"password" validate:"required"` 49 | Email *string `json:"email" validate:"required|email"` 50 | Notify *string `json:"notify" validate:"required|in:yes,no"` 51 | } 52 | 53 | if err := val.Bind(r.Body, &Register); err != nil { 54 | fmt.Println(err) 55 | } else { 56 | fmt.Println(*Register.Username) 57 | fmt.Println("This validated!") 58 | } 59 | 60 | } 61 | 62 | func main() { 63 | http.HandleFunc("/", handler) 64 | http.ListenAndServe(":8080", nil) 65 | } 66 | 67 | ``` 68 | 69 | ## Performance 70 | I have created some benchmarks to see what really needs to be improved. Currently the benchmarks run 100,000 times and the performace is as follows. Below shows that email is an expensive call due to the non-optimized regex lib in go. Hopefully this will be improved over time. Other than that I am fairly happy with the current benchmarks. They would most likely be even a bit lower since on an http server you would not have to call a function every iteration to turn a string into a io.ReadCloser. 71 | 72 | ``` 73 | val general test took: 3.9768205s to run. 74 | val email test took: 3.3563791s to run. 75 | val in test took: 636.4503ms to run. 76 | val required test took: 635.4693ms to run. 77 | val length test took: 693.4902ms to run. 78 | val length between test took: 713.4889ms to run. 79 | 80 | ``` 81 | 82 | To get an idea of where this sits in regards to other validation. In PHP using laravel for this same type of validation it will take you 13+ seconds to run 'val general test' and 10+ seconds to run the required tests for the same number of iterations. 83 | 84 | ## Currently Supported Validation 85 | 86 | #### required 87 | This will ensure that the data is actually included in the json array. 88 | ``` 89 | Username *string `json:"username" validate:"required"` 90 | ``` 91 | 92 | #### email 93 | This checks to see if the passed in value is a valid email, it uses the following regex "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$". 94 | ``` 95 | Username *string `json:"username" validate:"email"` 96 | ``` 97 | 98 | #### in 99 | In support any length of arguments to validate a JSON string or int value against. 100 | ``` 101 | Username *string `json:"username" validate:"in:only,these,are,valid,strings"` 102 | ``` 103 | 104 | #### min 105 | Min works with ints and ensures that the number the user has entered is not under the specified min. If the number is under it will return an error. 106 | ``` 107 | Username *string `json:"username" validate:"min:10"` 108 | ``` 109 | 110 | #### max 111 | Max works with ints and ensures that the number the user has entered is not over the specified max. If the number is over it will return an error. 112 | ``` 113 | Username *string `json:"username" validate:"max:243"` 114 | ``` 115 | 116 | #### regex 117 | Regex ensures that the string the user has passed in matched the regex you have entered. Currently this is only tested with strings. 118 | ``` 119 | Username *string `json:"username" validate:"regex:\\d+"` 120 | ``` 121 | 122 | #### length 123 | Length ensures that the passed in string is equal to the length you have specified. 124 | ``` 125 | Username *string `json:"username" validate:"length:5"` 126 | ``` 127 | 128 | #### length_between 129 | Length between works much the same as length except it will return true if the number is equal to or inbetween the high and low bounds set. 130 | ``` 131 | Username *string `json:"username" validate:"length_between:2,5"` 132 | ``` 133 | 134 | #### combinations 135 | If you would like to ensure multiple conditions are met simply use the | character. 136 | ``` 137 | Username *string `json:"username" validate:"email|required|in:m@gmail.com,o@gmail.com"` 138 | ``` 139 | -------------------------------------------------------------------------------- /speed_test.go: -------------------------------------------------------------------------------- 1 | package val 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "strings" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // Make string into io.ReadCloser 13 | // this is just for convinience 14 | func jsonSpeedFactory(s string) io.ReadCloser { 15 | return ioutil.NopCloser(strings.NewReader(s)) 16 | } 17 | 18 | func TestSpeedAverage(t *testing.T) { 19 | 20 | type Register struct { 21 | Username *string `json:"username" validate:"required"` 22 | Password *string `json:"password" validate:"required"` 23 | Email *string `json:"email" validate:"required|email"` 24 | Type *string `json:"type" validate:"required|in:admin,user,guest"` 25 | } 26 | 27 | startTime := time.Now() 28 | 29 | // Benchmark an average JSON request 30 | // Currently ~3.7s for 100k requests 31 | for i := 0; i < 100000; i++ { 32 | 33 | var mockRegister Register 34 | 35 | testJSON := jsonSpeedFactory(`{"username": "michaeljs1990", "password": "secret", "email": "michaeljs1990@gmail.com", "type": "admin"}`) 36 | 37 | if err := Bind(testJSON, &mockRegister); err != nil { 38 | t.Error(err) 39 | } 40 | 41 | } 42 | 43 | endTime := time.Now() 44 | 45 | fmt.Printf("val general test took: %v to run.\n", endTime.Sub(startTime)) 46 | } 47 | 48 | func TestSpeedEmail(t *testing.T) { 49 | 50 | type Email struct { 51 | Email *string `json:"email" validate:"required|email"` 52 | } 53 | 54 | startTime := time.Now() 55 | 56 | // Benchmark an average JSON request 57 | // Currently ~3.7s for 100k requests 58 | for i := 0; i < 100000; i++ { 59 | 60 | var mockEmail Email 61 | 62 | testJSON := jsonSpeedFactory(`{"email": "michaeljs1990@gmail.com"}`) 63 | 64 | if err := Bind(testJSON, &mockEmail); err != nil { 65 | t.Error(err) 66 | } 67 | 68 | } 69 | 70 | endTime := time.Now() 71 | 72 | fmt.Printf("val email test took: %v to run.\n", endTime.Sub(startTime)) 73 | } 74 | 75 | func TestSpeedIn(t *testing.T) { 76 | 77 | type In struct { 78 | Type *string `json:"type" validate:"in:admin,user,guest"` 79 | } 80 | 81 | startTime := time.Now() 82 | 83 | // Benchmark an average JSON request 84 | // Currently ~3.7s for 100k requests 85 | for i := 0; i < 100000; i++ { 86 | 87 | var mockIn In 88 | 89 | testJSON := jsonSpeedFactory(`{"type": "admin"}`) 90 | 91 | if err := Bind(testJSON, &mockIn); err != nil { 92 | t.Error(err) 93 | } 94 | 95 | } 96 | 97 | endTime := time.Now() 98 | 99 | fmt.Printf("val in test took: %v to run.\n", endTime.Sub(startTime)) 100 | } 101 | 102 | func TestSpeedRequired(t *testing.T) { 103 | 104 | type Required struct { 105 | Type *string `json:"type" validate:"required"` 106 | } 107 | 108 | startTime := time.Now() 109 | 110 | // Benchmark an average JSON request 111 | // Currently ~3.7s for 100k requests 112 | for i := 0; i < 100000; i++ { 113 | 114 | var mockRequired Required 115 | 116 | testJSON := jsonSpeedFactory(`{"type": "admin"}`) 117 | 118 | if err := Bind(testJSON, &mockRequired); err != nil { 119 | t.Error(err) 120 | } 121 | 122 | } 123 | 124 | endTime := time.Now() 125 | 126 | fmt.Printf("val required test took: %v to run.\n", endTime.Sub(startTime)) 127 | } 128 | 129 | func TestSpeedLength(t *testing.T) { 130 | 131 | type DigitInt struct { 132 | Number *string `json:"number" validate:"length:4"` 133 | } 134 | 135 | startTime := time.Now() 136 | 137 | // Benchmark an average JSON request 138 | // Currently ~3.7s for 100k requests 139 | for i := 0; i < 100000; i++ { 140 | 141 | var mockDigitInt DigitInt 142 | 143 | testJSON := jsonSpeedFactory(`{"number": "1000"}`) 144 | 145 | if err := Bind(testJSON, &mockDigitInt); err != nil { 146 | t.Error(err) 147 | } 148 | 149 | } 150 | 151 | endTime := time.Now() 152 | 153 | fmt.Printf("val length test took: %v to run.\n", endTime.Sub(startTime)) 154 | } 155 | 156 | func TestSpeedLengthBetween(t *testing.T) { 157 | 158 | type DigitBetweenInt struct { 159 | Number *string `json:"number" validate:"length_between:4,6"` 160 | } 161 | 162 | startTime := time.Now() 163 | 164 | // Benchmark an average JSON request 165 | // Currently ~3.7s for 100k requests 166 | for i := 0; i < 100000; i++ { 167 | 168 | var mockDigitBetweenInt DigitBetweenInt 169 | 170 | testJSON := jsonSpeedFactory(`{"number": "aaaa"}`) 171 | 172 | if err := Bind(testJSON, &mockDigitBetweenInt); err != nil { 173 | t.Error(err) 174 | } 175 | 176 | } 177 | 178 | endTime := time.Now() 179 | 180 | fmt.Printf("val length between test took: %v to run.\n", endTime.Sub(startTime)) 181 | } 182 | -------------------------------------------------------------------------------- /val.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package allows for easy validation of passed in json. 3 | Val does not intend to be a robust solution but does seek to cover 95% of use cases. 4 | Val requires a structure to use pointers for validation. This may seem odd but if a pointer is 5 | not used you will run into some strange issues since json.Decode() will pass an int type back 6 | set as 0 giving no way to tell if a 0 was actually passed in or not. Using a pointer allows to 7 | check for a nil value before doing the validation and lets you have optional json parameters. 8 | 9 | Basic Struct Example. 10 | 11 | var Register struct { 12 | Username *string `json:"username" validate:"required"` 13 | Password *string `json:"password" validate:"required"` 14 | Email *string `json:"email" validate:"required|email"` 15 | Notify *string `json:"notify" validate:"required|in:yes,no"` 16 | } 17 | 18 | Normal Use Case. 19 | 20 | if err := val.Bind(r.Body, &Register); err != nil { 21 | fmt.Println(err) 22 | } 23 | 24 | */ 25 | 26 | package val 27 | 28 | import ( 29 | "bytes" 30 | "encoding/json" 31 | "errors" 32 | "io" 33 | "io/ioutil" 34 | "reflect" 35 | "regexp" 36 | "strconv" 37 | "strings" 38 | ) 39 | 40 | // Unpack JSON and call the validate function if no errors are found when unpacking it. 41 | // Bind kicks of the validation process. Note that Request.Body impliments an io.ReadCloser. 42 | // Look into ReadAll http://jmoiron.net/blog/crossing-streams-a-love-letter-to-ioreader/ 43 | func Bind(input io.ReadCloser, obj interface{}) error { 44 | // Don't go through any logic if nothing was passed in. 45 | if b, err := ioutil.ReadAll(input); err == nil && string(b) != "{}" && string(b) != "" { 46 | // Turn our string back into a io.Reader if it's valid 47 | decoder := json.NewDecoder(bytes.NewReader(b)) 48 | 49 | if err := decoder.Decode(obj); err == nil { 50 | return Validate(obj) 51 | } else { 52 | return err 53 | } 54 | } else if err == nil { 55 | return errors.New("Nothing was passed in or JSON featured an empty object.") 56 | } else { 57 | return err 58 | } 59 | } 60 | 61 | // In version 1.0 I exported the Validation function. This can be used when you may 62 | // not need to or want to have JSON first converted into a struct. 63 | func Validate(obj interface{}) error { 64 | 65 | typ := reflect.TypeOf(obj) 66 | value := reflect.ValueOf(obj) 67 | 68 | // Check to ensure we are getting a valid 69 | // pointer for manipulation. 70 | if typ.Kind() == reflect.Ptr { 71 | typ = typ.Elem() 72 | value = value.Elem() 73 | } 74 | 75 | // Kill process if obj did not pass in a scruct. 76 | // This happens when a pointer passed in. 77 | if value.Kind() != reflect.Struct { 78 | return nil 79 | } 80 | 81 | for i := 0; i < typ.NumField(); i++ { 82 | 83 | field := typ.Field(i) 84 | fieldValue := value.Field(i).Interface() 85 | zero := reflect.Zero(field.Type).Interface() 86 | 87 | // Validate nested and embedded structs (if pointer, only do so if not nil) 88 | if field.Type.Kind() == reflect.Struct || 89 | (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) { 90 | if err := Validate(fieldValue); err != nil { 91 | return err 92 | } 93 | } 94 | 95 | if field.Tag.Get("validate") != "" || field.Tag.Get("binding") != "" { 96 | // Break validate field into array 97 | array := strings.Split(field.Tag.Get("validate"), "|") 98 | 99 | // Legacy Support for binding. 100 | if array[0] == "" { 101 | array = strings.Split(field.Tag.Get("binding"), "|") 102 | } 103 | 104 | // Do the hard work of checking all assertions 105 | for setting := range array { 106 | 107 | match := array[setting] 108 | 109 | //Check that value was passed in and is not required. 110 | if match != "required" && null(fieldValue) == true { 111 | return nil 112 | } 113 | 114 | switch { 115 | case "required" == match: 116 | if err := required(field, fieldValue, zero); err != nil { 117 | return err 118 | } 119 | case "email" == match: 120 | if err := regex(`regex:^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$`, fieldValue); err != nil { 121 | return err 122 | } 123 | case "url" == match: 124 | if err := regex(`regex:/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/`, fieldValue); err != nil { 125 | return err 126 | } 127 | case "alpha" == match: 128 | if err := regex(`regex:\p{L}`, fieldValue); err != nil { 129 | return err 130 | } 131 | case "alphadash" == match: 132 | if err := regex(`regex:^[a-zA-Z0-9_]*$`, fieldValue); err != nil { 133 | return err 134 | } 135 | case "alphanumeric" == match: 136 | if err := regex(`regex:/[0-9a-zA-Z]/`, fieldValue); err != nil { 137 | return err 138 | } 139 | case strings.HasPrefix(match, "min:"): 140 | if err := min(match, fieldValue); err != nil { 141 | return err 142 | } 143 | case strings.HasPrefix(match, "max:"): 144 | if err := max(match, fieldValue); err != nil { 145 | return err 146 | } 147 | case strings.HasPrefix(match, "in:"): 148 | if err := in(match, fieldValue); err != nil { 149 | return err 150 | } 151 | case strings.HasPrefix(match, "regex:"): 152 | if err := regex(match, fieldValue); err != nil { 153 | return err 154 | } 155 | case strings.HasPrefix(match, "length:"): 156 | if err := length(match, fieldValue); err != nil { 157 | return err 158 | } 159 | case strings.HasPrefix(match, "length_between:"): 160 | if err := length_between(match, fieldValue); err != nil { 161 | return err 162 | } 163 | default: 164 | panic("The field " + match + " is not a valid validation check.") 165 | } 166 | } 167 | } 168 | } 169 | 170 | return nil 171 | } 172 | 173 | // Ensure that the value being passed in is not of type nil. 174 | func null(value interface{}) bool { 175 | if reflect.ValueOf(value).IsNil() { 176 | return true 177 | } 178 | 179 | return false 180 | } 181 | 182 | // Check that the following function features 183 | // the required field. May need to check for 184 | // more special cases like since passing in null 185 | // is the same as 0 for int type checking. 186 | func required(field reflect.StructField, value, zero interface{}) error { 187 | 188 | if reflect.DeepEqual(zero, value) { 189 | if _, ok := value.(int); !ok { 190 | return errors.New("The required field " + field.Name + " was not submitted.") 191 | } 192 | } 193 | 194 | return nil 195 | } 196 | 197 | // Check that the passed in field is a valid email 198 | // Need to improve error logging for this method 199 | // Currently only supports strings, ints 200 | func in(field string, value interface{}) error { 201 | 202 | if data, ok := value.(*string); ok { 203 | if len(*data) == 0 { 204 | return nil 205 | } 206 | 207 | valid := strings.Split(field[3:], ",") 208 | 209 | for option := range valid { 210 | if valid[option] == *data { 211 | return nil 212 | } 213 | } 214 | 215 | } else { 216 | return errors.New("The value passed in for IN could not be converted to a string.") 217 | } 218 | 219 | return errors.New("In did not match any of the expected values.") 220 | } 221 | 222 | func min(field string, value interface{}) error { 223 | 224 | if data, ok := value.(*int); ok { 225 | 226 | min := field[strings.Index(field, ":")+1:] 227 | 228 | if minNum, ok := strconv.ParseInt(min, 0, 64); ok == nil { 229 | 230 | if int64(*data) >= minNum { 231 | return nil 232 | } else { 233 | return errors.New("The data you passed in was smaller then the allowed minimum.") 234 | } 235 | 236 | } 237 | } 238 | 239 | return errors.New("The value passed in for MIN could not be converted to an int.") 240 | } 241 | 242 | func max(field string, value interface{}) error { 243 | 244 | if data, ok := value.(*int); ok { 245 | 246 | max := field[strings.Index(field, ":")+1:] 247 | 248 | if maxNum, ok := strconv.ParseInt(max, 0, 64); ok == nil { 249 | if int64(*data) <= maxNum { 250 | return nil 251 | } else { 252 | return errors.New("The data you passed in was larger than the maximum.") 253 | } 254 | 255 | } 256 | } 257 | 258 | return errors.New("The value passed in for MAX could not be converted to an int.") 259 | } 260 | 261 | // Regex handles the general regex call and also handles 262 | // the regex email. 263 | func regex(field string, value interface{}) error { 264 | 265 | reg := field[strings.Index(field, ":")+1:] 266 | 267 | if data, ok := value.(*string); ok { 268 | if len(*data) == 0 { 269 | return nil 270 | } else if err := match_regex(reg, []byte(*data)); err != nil { 271 | return err 272 | } 273 | } else if data, ok := value.(*int); ok { 274 | if err := match_regex(reg, []byte(strconv.Itoa(*data))); err != nil { 275 | return err 276 | } 277 | } else { 278 | return errors.New("The value passed in for REGEX could not be converted to a string or int.") 279 | } 280 | 281 | return nil 282 | } 283 | 284 | // Helper function for regex. 285 | func match_regex(reg string, data []byte) error { 286 | 287 | if match, err := regexp.Match(reg, []byte(data)); err == nil && match { 288 | return nil 289 | } else { 290 | return errors.New("Your regex did not match or was not valid.") 291 | } 292 | } 293 | 294 | // Check passed in json length string is exact value passed in. 295 | // Also checks if passed in values is between two different ones. 296 | func length(field string, value interface{}) error { 297 | 298 | length := field[strings.Index(field, ":")+1:] 299 | 300 | if data, ok := value.(*string); ok { 301 | if intdata, intok := strconv.Atoi(length); intok == nil { 302 | if len(*data) == intdata { 303 | return nil 304 | } else { 305 | return errors.New("The data passed in was not equal to the expected length.") 306 | } 307 | } else { 308 | return errors.New("The value passed in for LENGTH could not be converted to an int.") 309 | } 310 | } else { 311 | return errors.New("The value passed in for LENGTH could not be converted to a string.") 312 | } 313 | } 314 | 315 | // Check if the strings length is between high,low. 316 | func length_between(field string, value interface{}) error { 317 | 318 | length := field[strings.Index(field, ":")+1:] 319 | vals := strings.Split(length, ",") 320 | 321 | if len(vals) == 2 { 322 | 323 | if data, ok := value.(*string); ok { 324 | 325 | if lowerbound, lowok := strconv.Atoi(vals[0]); lowok == nil { 326 | 327 | if upperbound, upok := strconv.Atoi(vals[1]); upok == nil { 328 | 329 | if lowerbound <= len(*data) && upperbound >= len(*data) { 330 | return nil 331 | } else { 332 | return errors.New("The value passed in for LENGTH BETWEEN was not in bounds.") 333 | } 334 | 335 | } else { 336 | return errors.New("The value passed in for LENGTH BETWEEN could not be converted to an int.") 337 | } 338 | 339 | } else { 340 | return errors.New("The value passed in for LENGTH BETWEEN could not be converted to an int.") 341 | } 342 | 343 | } else { 344 | return errors.New("The value passed in for LENGTH BETWEEN could not be converted to a string.") 345 | } 346 | } else { 347 | return errors.New("LENGTH BETWEEN requires exactly two paramaters.") 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /val_test.go: -------------------------------------------------------------------------------- 1 | package val 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | // Make string into io.ReadCloser 12 | // this is just for convinience 13 | func jsonFactory(s string) io.ReadCloser { 14 | return ioutil.NopCloser(strings.NewReader(s)) 15 | } 16 | 17 | // Ensure all required fields are matching 18 | func TestRequired(t *testing.T) { 19 | 20 | // // Test if STRING required is valid 21 | var testString struct { 22 | Test *string `json:"something" validate:"required" ` 23 | } 24 | 25 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"something": "hello"}`)) 26 | 27 | if err := Bind(req.Body, &testString); err != nil { 28 | t.Error(err) 29 | } 30 | 31 | var testString2 struct { 32 | Test *string `json:"something" validate:"required" ` 33 | } 34 | 35 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{}`)) 36 | 37 | if err := Bind(req.Body, &testString2); err == nil { 38 | t.Error("Required string, empty JSON object should return error but did not.") 39 | } 40 | 41 | // Test if INT require is valid 42 | var testInt struct { 43 | Test *int `json:"something" validate:"required" ` 44 | } 45 | 46 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": 2}`)) 47 | 48 | if err := Bind(req.Body, &testInt); err != nil { 49 | t.Error(err) 50 | } 51 | 52 | // Test if BOOL required is valid 53 | var testBool struct { 54 | Test *bool `json:"something" validate:"required" ` 55 | } 56 | 57 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": true}`)) 58 | 59 | if err := Bind(req.Body, &testBool); err != nil { 60 | t.Error(err) 61 | } 62 | 63 | var testBool2 struct { 64 | Test *string `json:"something" validate:"required" ` 65 | } 66 | 67 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{}`)) 68 | 69 | if err := Bind(req.Body, &testBool2); err == nil { 70 | t.Error("Required bool, empty JSON object should return error but did not.") 71 | } 72 | 73 | // Test if ARRAY required is valid 74 | var testArray struct { 75 | Test *[]string `json:"something" validate:"required" ` 76 | } 77 | 78 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": ["test", "data"]}`)) 79 | 80 | if err := Bind(req.Body, &testArray); err != nil { 81 | t.Error(err) 82 | } 83 | 84 | // Test is OBJECT required is valid 85 | type testObjectTP struct { 86 | Name string `json:"name" validate:"required" ` 87 | } 88 | 89 | var testObject struct { 90 | Test testObjectTP `json:"something" validate:"required" ` 91 | } 92 | 93 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": {"name": "test"}}`)) 94 | 95 | if err := Bind(req.Body, &testObject); err != nil { 96 | t.Error(err) 97 | } 98 | } 99 | 100 | func TestEmail(t *testing.T) { 101 | 102 | var testValEmail struct { 103 | Test *string `json:"email" validate:"email" ` 104 | } 105 | 106 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"email": "michaeljs@gmail.com"}`)) 107 | 108 | if err := Bind(req.Body, &testValEmail); err != nil { 109 | t.Error(err) 110 | } 111 | 112 | var testValEmail2 struct { 113 | Test *string `json:"email" validate:"email" ` 114 | } 115 | 116 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"email": "michaeljs@gail.edu"}`)) 117 | 118 | if err := Bind(req.Body, &testValEmail2); err != nil { 119 | t.Error(err) 120 | } 121 | 122 | var testValEmail3 struct { 123 | Test *string `json:"email" validate:"email" ` 124 | } 125 | 126 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"email": "michaeljs.edu"}`)) 127 | 128 | if err := Bind(req.Body, &testValEmail3); err == nil { 129 | t.Error("Email test failed, michaeljs.edu passed as a valid email.") 130 | } 131 | 132 | // This should not return an error since email is not required. 133 | var testValEmail4 struct { 134 | Test *string `json:"email" validate:"email" ` 135 | } 136 | 137 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"jeff": "really"}`)) 138 | 139 | if err := Bind(req.Body, &testValEmail4); err != nil { 140 | t.Error(err) 141 | } 142 | 143 | } 144 | 145 | // Ensure In is matching properly 146 | // Supporting string and int currently 147 | func TestIn(t *testing.T) { 148 | 149 | var testValIn struct { 150 | Test *string `json:"special" validate:"in:admin,user,other" ` 151 | } 152 | 153 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"special": "admin"}`)) 154 | 155 | if err := Bind(req.Body, &testValIn); err != nil { 156 | t.Error(err) 157 | } 158 | 159 | var testValIn2 struct { 160 | Test *string `json:"special" validate:"in:1,3,2" ` 161 | } 162 | 163 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": "3"}`)) 164 | 165 | if err := Bind(req.Body, &testValIn2); err != nil { 166 | t.Error(err) 167 | } 168 | 169 | var testValIn3 struct { 170 | Test *int `json:"special" validate:"in:1,3,2" ` 171 | } 172 | 173 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": 6}`)) 174 | 175 | if err := Bind(req.Body, &testValIn3); err == nil { 176 | t.Error("6 is not in validate in call, err should not have been nil.") 177 | } 178 | 179 | var testValIn4 struct { 180 | Test2 *string `json:"what" validate:in:this,that` 181 | Test *string `json:"special" validate:"in:1,3,2" ` 182 | } 183 | 184 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": "3","what": "this"}`)) 185 | 186 | if err := Bind(req.Body, &testValIn4); err != nil { 187 | t.Error(err) 188 | } 189 | 190 | var testValIn5 struct { 191 | Test2 *string `json:"what" validate:in:this,that` 192 | Test *string `json:"special" validate:"in:1,3,2" ` 193 | } 194 | 195 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": "3"}`)) 196 | 197 | if err := Bind(req.Body, &testValIn5); err != nil { 198 | t.Error(err) 199 | } 200 | 201 | var testValIn6 struct { 202 | Test2 *string `json:"what" validate:"in:this,that"` 203 | Test3 *string `json:"what1" validate:"in:this,then"` 204 | Test4 *string `json:"what2" validate:"in:this,that"` 205 | Test5 *string `json:"what3" validate:"in:this,that"` 206 | Test *string `json:"special" validate:"in:1,3,2"` 207 | } 208 | 209 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"sa": 34, "what":"this", "what1":"then", "what2":"this"}`)) 210 | 211 | if err := Bind(req.Body, &testValIn6); err != nil { 212 | t.Error(err) 213 | } 214 | } 215 | 216 | // Check if the entered JSON is a data matching the one in a string. 217 | func TestMin(t *testing.T) { 218 | 219 | var testValMin struct { 220 | Test *int `json:"digit" validate:"min:23" ` 221 | } 222 | 223 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 24}`)) 224 | 225 | if err := Bind(req.Body, &testValMin); err != nil { 226 | t.Error(err) 227 | } 228 | 229 | var testValMin2 struct { 230 | Test *int `json:"digit" validate:"min:20" ` 231 | } 232 | 233 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": 19}`)) 234 | 235 | if err := Bind(req.Body, &testValMin2); err == nil { 236 | t.Error("Min was 20 digit of 19 should not have validated properly.") 237 | } 238 | 239 | var testValMin3 struct { 240 | Test *int `json:"digit" validate:"min:20" ` 241 | } 242 | 243 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"jeff":"greg"}`)) 244 | 245 | if err := Bind(req.Body, &testValMin3); err != nil { 246 | t.Error("Nothing was entered but min was not required. No error should be thrown.") 247 | } 248 | } 249 | 250 | func TestMax(t *testing.T) { 251 | 252 | var testValMin struct { 253 | Test *int `json:"digit" validate:"max:23" ` 254 | } 255 | 256 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 23}`)) 257 | 258 | if err := Bind(req.Body, &testValMin); err != nil { 259 | t.Error(err) 260 | } 261 | 262 | var testValMin2 struct { 263 | Test *int `json:"digit" validate:"max:20" ` 264 | } 265 | 266 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": 21}`)) 267 | 268 | if err := Bind(req.Body, &testValMin2); err == nil { 269 | t.Error("Max was 20 digit of 21 should not have validated properly.") 270 | } 271 | 272 | var testValMin3 struct { 273 | Test *int `json:"digit" validate:"max:20" ` 274 | } 275 | 276 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"jeff":"greg"}`)) 277 | 278 | if err := Bind(req.Body, &testValMin3); err != nil { 279 | t.Error("Nothing was entered but max was not required. No error should be thrown.") 280 | } 281 | } 282 | 283 | func TestRegex(t *testing.T) { 284 | 285 | var testValDigit struct { 286 | Test *int `json:"digit" validate:"regex:\\d+" ` 287 | } 288 | 289 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 23}`)) 290 | 291 | if err := Bind(req.Body, &testValDigit); err != nil { 292 | t.Error(err) 293 | } 294 | 295 | var testValDigit2 struct { 296 | Test *int `json:"digit" validate:"regex:\\d+" ` 297 | } 298 | 299 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": 2dsa3}`)) 300 | 301 | if err := Bind(req.Body, &testValDigit2); err == nil { 302 | t.Error("\\d+ regex should not match the string 2dsa3.") 303 | } 304 | } 305 | 306 | func TestMultiple(t *testing.T) { 307 | 308 | var testValMulti struct { 309 | Test *int `json:"digit" validate:"regex:\\d+|required|max:23" ` 310 | } 311 | 312 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 23}`)) 313 | 314 | if err := Bind(req.Body, &testValMulti); err != nil { 315 | t.Error(err) 316 | } 317 | 318 | var testValMulti2 struct { 319 | Test *string `json:"digit" validate:"email|required|regex:\\d+" ` 320 | } 321 | 322 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "m@g.com"}`)) 323 | 324 | if err := Bind(req.Body, &testValMulti2); err == nil { 325 | t.Error("Should have returned error but did not.") 326 | } 327 | 328 | } 329 | 330 | func TestPointers(t *testing.T) { 331 | 332 | var testValMulti struct { 333 | Test *string `json:"digit" validate:"in:3,4,5" ` 334 | } 335 | 336 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"invalid": "23"}`)) 337 | 338 | if err := Bind(req.Body, &testValMulti); err != nil { 339 | t.Error(err) 340 | } 341 | 342 | var testValMulti2 struct { 343 | Test *string `json:"digit" validate:"in:3,4,5" ` 344 | } 345 | 346 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "23"}`)) 347 | 348 | if err := Bind(req.Body, &testValMulti2); err == nil { 349 | t.Error("Value was passed in but did not match in:3,4,5 error should have been returned.") 350 | } 351 | 352 | var testValMulti3 struct { 353 | Test *string `json:"digit" validate:"in:3,4,5" ` 354 | } 355 | 356 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "4"}`)) 357 | 358 | if err := Bind(req.Body, &testValMulti3); err != nil { 359 | t.Error(err) 360 | } 361 | 362 | } 363 | 364 | func TestLength(t *testing.T) { 365 | 366 | var testValLength struct { 367 | Test *string `json:"username" validate:"length:5" ` 368 | } 369 | 370 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"username": "aaaaa"}`)) 371 | 372 | if err := Bind(req.Body, &testValLength); err != nil { 373 | t.Error(err) 374 | } 375 | 376 | var testValLength2 struct { 377 | Test *string `json:"username" validate:"length:4" ` 378 | } 379 | 380 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"username": "aaa"}`)) 381 | 382 | if err := Bind(req.Body, &testValLength2); err == nil { 383 | t.Error("Value was passed in but did not match length of 4 error should have been returned.") 384 | } 385 | 386 | var testValLength3 struct { 387 | Test *string `json:"username" validate:"length:23" ` 388 | } 389 | 390 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "4"}`)) 391 | 392 | if err := Bind(req.Body, &testValLength3); err != nil { 393 | t.Error(err) 394 | } 395 | 396 | } 397 | 398 | func TestLengthBetween(t *testing.T) { 399 | 400 | var testValLength struct { 401 | Test *string `json:"username" validate:"length_between:5,10" ` 402 | } 403 | 404 | req, _ := http.NewRequest("POST", "/", jsonFactory(`{"username": "aaaaaa"}`)) 405 | 406 | if err := Bind(req.Body, &testValLength); err != nil { 407 | t.Error(err) 408 | } 409 | 410 | var testValLength2 struct { 411 | Test *string `json:"username" validate:"length_between:4,5" ` 412 | } 413 | 414 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"username": "aaa"}`)) 415 | 416 | if err := Bind(req.Body, &testValLength2); err == nil { 417 | t.Error("Value was passed in but was not inbetween 4,5 should have returned error.") 418 | } 419 | 420 | var testValLength3 struct { 421 | Test *string `json:"username" validate:"length_between:2,3" ` 422 | } 423 | 424 | req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "4"}`)) 425 | 426 | if err := Bind(req.Body, &testValLength3); err != nil { 427 | t.Error(err) 428 | } 429 | 430 | } 431 | --------------------------------------------------------------------------------