├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── UPGRADE.md ├── absent.go ├── absent_test.go ├── date.go ├── date_test.go ├── each.go ├── each_test.go ├── error.go ├── error_test.go ├── example_test.go ├── go.mod ├── go.sum ├── in.go ├── in_test.go ├── is ├── rules.go └── rules_test.go ├── length.go ├── length_test.go ├── map.go ├── map_test.go ├── match.go ├── match_test.go ├── minmax.go ├── minmax_test.go ├── multipleof.go ├── multipleof_test.go ├── not_in.go ├── not_in_test.go ├── not_nil.go ├── not_nil_test.go ├── required.go ├── required_test.go ├── string.go ├── string_test.go ├── struct.go ├── struct_test.go ├── util.go ├── util_test.go ├── validation.go ├── validation_test.go ├── when.go └── when_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Idea Directory 7 | .idea 8 | 9 | # Folders 10 | _obj 11 | _test 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | 3 | language: go 4 | 5 | go: 6 | - 1.13.x 7 | 8 | install: 9 | - go get golang.org/x/tools/cmd/cover 10 | - go get github.com/mattn/goveralls 11 | - go get golang.org/x/lint/golint 12 | 13 | script: 14 | - test -z "`gofmt -l -d .`" 15 | - test -z "`golint ./...`" 16 | - go test -v -covermode=count -coverprofile=coverage.out 17 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016, Qiang Xue 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 5 | and associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 7 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or 11 | substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 14 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 16 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ozzo-validation 2 | 3 | [![GoDoc](https://godoc.org/github.com/go-ozzo/ozzo-validation?status.png)](http://godoc.org/github.com/go-ozzo/ozzo-validation) 4 | [![Build Status](https://travis-ci.org/go-ozzo/ozzo-validation.svg?branch=master)](https://travis-ci.org/go-ozzo/ozzo-validation) 5 | [![Coverage Status](https://coveralls.io/repos/github/go-ozzo/ozzo-validation/badge.svg?branch=master)](https://coveralls.io/github/go-ozzo/ozzo-validation?branch=master) 6 | [![Go Report](https://goreportcard.com/badge/github.com/go-ozzo/ozzo-validation)](https://goreportcard.com/report/github.com/go-ozzo/ozzo-validation) 7 | 8 | ## Description 9 | 10 | ozzo-validation is a Go package that provides configurable and extensible data validation capabilities. 11 | It has the following features: 12 | 13 | * use normal programming constructs rather than error-prone struct tags to specify how data should be validated. 14 | * can validate data of different types, e.g., structs, strings, byte slices, slices, maps, arrays. 15 | * can validate custom data types as long as they implement the `Validatable` interface. 16 | * can validate data types that implement the `sql.Valuer` interface (e.g. `sql.NullString`). 17 | * customizable and well-formatted validation errors. 18 | * error code and message translation support. 19 | * provide a rich set of validation rules right out of box. 20 | * extremely easy to create and use custom validation rules. 21 | 22 | For an example on how this library is used in an application, please refer to [go-rest-api](https://github.com/qiangxue/go-rest-api) which is a starter kit for building RESTful APIs in Go. 23 | 24 | ## Requirements 25 | 26 | Go 1.13 or above. 27 | 28 | 29 | ## Getting Started 30 | 31 | The ozzo-validation package mainly includes a set of validation rules and two validation methods. You use 32 | validation rules to describe how a value should be considered valid, and you call either `validation.Validate()` 33 | or `validation.ValidateStruct()` to validate the value. 34 | 35 | 36 | ### Installation 37 | 38 | Run the following command to install the package: 39 | 40 | ``` 41 | go get github.com/go-ozzo/ozzo-validation 42 | ``` 43 | 44 | ### Validating a Simple Value 45 | 46 | For a simple value, such as a string or an integer, you may use `validation.Validate()` to validate it. For example, 47 | 48 | ```go 49 | package main 50 | 51 | import ( 52 | "fmt" 53 | 54 | "github.com/go-ozzo/ozzo-validation/v4" 55 | "github.com/go-ozzo/ozzo-validation/v4/is" 56 | ) 57 | 58 | func main() { 59 | data := "example" 60 | err := validation.Validate(data, 61 | validation.Required, // not empty 62 | validation.Length(5, 100), // length between 5 and 100 63 | is.URL, // is a valid URL 64 | ) 65 | fmt.Println(err) 66 | // Output: 67 | // must be a valid URL 68 | } 69 | ``` 70 | 71 | The method `validation.Validate()` will run through the rules in the order that they are listed. If a rule fails 72 | the validation, the method will return the corresponding error and skip the rest of the rules. The method will 73 | return nil if the value passes all validation rules. 74 | 75 | 76 | ### Validating a Struct 77 | 78 | For a struct value, you usually want to check if its fields are valid. For example, in a RESTful application, you 79 | may unmarshal the request payload into a struct and then validate the struct fields. If one or multiple fields 80 | are invalid, you may want to get an error describing which fields are invalid. You can use `validation.ValidateStruct()` 81 | to achieve this purpose. A single struct can have rules for multiple fields, and a field can be associated with multiple 82 | rules. For example, 83 | 84 | ```go 85 | type Address struct { 86 | Street string 87 | City string 88 | State string 89 | Zip string 90 | } 91 | 92 | func (a Address) Validate() error { 93 | return validation.ValidateStruct(&a, 94 | // Street cannot be empty, and the length must between 5 and 50 95 | validation.Field(&a.Street, validation.Required, validation.Length(5, 50)), 96 | // City cannot be empty, and the length must between 5 and 50 97 | validation.Field(&a.City, validation.Required, validation.Length(5, 50)), 98 | // State cannot be empty, and must be a string consisting of two letters in upper case 99 | validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), 100 | // State cannot be empty, and must be a string consisting of five digits 101 | validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 102 | ) 103 | } 104 | 105 | a := Address{ 106 | Street: "123", 107 | City: "Unknown", 108 | State: "Virginia", 109 | Zip: "12345", 110 | } 111 | 112 | err := a.Validate() 113 | fmt.Println(err) 114 | // Output: 115 | // Street: the length must be between 5 and 50; State: must be in a valid format. 116 | ``` 117 | 118 | Note that when calling `validation.ValidateStruct` to validate a struct, you should pass to the method a pointer 119 | to the struct instead of the struct itself. Similarly, when calling `validation.Field` to specify the rules 120 | for a struct field, you should use a pointer to the struct field. 121 | 122 | When the struct validation is performed, the fields are validated in the order they are specified in `ValidateStruct`. 123 | And when each field is validated, its rules are also evaluated in the order they are associated with the field. 124 | If a rule fails, an error is recorded for that field, and the validation will continue with the next field. 125 | 126 | 127 | ### Validating a Map 128 | 129 | Sometimes you might need to work with dynamic data stored in maps rather than a typed model. You can use `validation.Map()` 130 | in this situation. A single map can have rules for multiple keys, and a key can be associated with multiple 131 | rules. For example, 132 | 133 | ```go 134 | c := map[string]interface{}{ 135 | "Name": "Qiang Xue", 136 | "Email": "q", 137 | "Address": map[string]interface{}{ 138 | "Street": "123", 139 | "City": "Unknown", 140 | "State": "Virginia", 141 | "Zip": "12345", 142 | }, 143 | } 144 | 145 | err := validation.Validate(c, 146 | validation.Map( 147 | // Name cannot be empty, and the length must be between 5 and 20. 148 | validation.Key("Name", validation.Required, validation.Length(5, 20)), 149 | // Email cannot be empty and should be in a valid email format. 150 | validation.Key("Email", validation.Required, is.Email), 151 | // Validate Address using its own validation rules 152 | validation.Key("Address", validation.Map( 153 | // Street cannot be empty, and the length must between 5 and 50 154 | validation.Key("Street", validation.Required, validation.Length(5, 50)), 155 | // City cannot be empty, and the length must between 5 and 50 156 | validation.Key("City", validation.Required, validation.Length(5, 50)), 157 | // State cannot be empty, and must be a string consisting of two letters in upper case 158 | validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), 159 | // State cannot be empty, and must be a string consisting of five digits 160 | validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 161 | )), 162 | ), 163 | ) 164 | fmt.Println(err) 165 | // Output: 166 | // Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address. 167 | ``` 168 | 169 | When the map validation is performed, the keys are validated in the order they are specified in `Map`. 170 | And when each key is validated, its rules are also evaluated in the order they are associated with the key. 171 | If a rule fails, an error is recorded for that key, and the validation will continue with the next key. 172 | 173 | 174 | ### Validation Errors 175 | 176 | The `validation.ValidateStruct` method returns validation errors found in struct fields in terms of `validation.Errors` 177 | which is a map of fields and their corresponding errors. Nil is returned if validation passes. 178 | 179 | By default, `validation.Errors` uses the struct tags named `json` to determine what names should be used to 180 | represent the invalid fields. The type also implements the `json.Marshaler` interface so that it can be marshaled 181 | into a proper JSON object. For example, 182 | 183 | ```go 184 | type Address struct { 185 | Street string `json:"street"` 186 | City string `json:"city"` 187 | State string `json:"state"` 188 | Zip string `json:"zip"` 189 | } 190 | 191 | // ...perform validation here... 192 | 193 | err := a.Validate() 194 | b, _ := json.Marshal(err) 195 | fmt.Println(string(b)) 196 | // Output: 197 | // {"street":"the length must be between 5 and 50","state":"must be in a valid format"} 198 | ``` 199 | 200 | You may modify `validation.ErrorTag` to use a different struct tag name. 201 | 202 | If you do not like the magic that `ValidateStruct` determines error keys based on struct field names or corresponding 203 | tag values, you may use the following alternative approach: 204 | 205 | ```go 206 | c := Customer{ 207 | Name: "Qiang Xue", 208 | Email: "q", 209 | Address: Address{ 210 | State: "Virginia", 211 | }, 212 | } 213 | 214 | err := validation.Errors{ 215 | "name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)), 216 | "email": validation.Validate(c.Name, validation.Required, is.Email), 217 | "zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 218 | }.Filter() 219 | fmt.Println(err) 220 | // Output: 221 | // email: must be a valid email address; zip: cannot be blank. 222 | ``` 223 | 224 | In the above example, we build a `validation.Errors` by a list of names and the corresponding validation results. 225 | At the end we call `Errors.Filter()` to remove from `Errors` all nils which correspond to those successful validation 226 | results. The method will return nil if `Errors` is empty. 227 | 228 | The above approach is very flexible as it allows you to freely build up your validation error structure. You can use 229 | it to validate both struct and non-struct values. Compared to using `ValidateStruct` to validate a struct, 230 | it has the drawback that you have to redundantly specify the error keys while `ValidateStruct` can automatically 231 | find them out. 232 | 233 | 234 | ### Internal Errors 235 | 236 | Internal errors are different from validation errors in that internal errors are caused by malfunctioning code (e.g. 237 | a validator making a remote call to validate some data when the remote service is down) rather 238 | than the data being validated. When an internal error happens during data validation, you may allow the user to resubmit 239 | the same data to perform validation again, hoping the program resumes functioning. On the other hand, if data validation 240 | fails due to data error, the user should generally not resubmit the same data again. 241 | 242 | To differentiate internal errors from validation errors, when an internal error occurs in a validator, wrap it 243 | into `validation.InternalError` by calling `validation.NewInternalError()`. The user of the validator can then check 244 | if a returned error is an internal error or not. For example, 245 | 246 | ```go 247 | if err := a.Validate(); err != nil { 248 | if e, ok := err.(validation.InternalError); ok { 249 | // an internal error happened 250 | fmt.Println(e.InternalError()) 251 | } 252 | } 253 | ``` 254 | 255 | 256 | ## Validatable Types 257 | 258 | A type is validatable if it implements the `validation.Validatable` interface. 259 | 260 | When `validation.Validate` is used to validate a validatable value, if it does not find any error with the 261 | given validation rules, it will further call the value's `Validate()` method. 262 | 263 | Similarly, when `validation.ValidateStruct` is validating a struct field whose type is validatable, it will call 264 | the field's `Validate` method after it passes the listed rules. 265 | 266 | > Note: When implementing `validation.Validatable`, do not call `validation.Validate()` to validate the value in its 267 | > original type because this will cause infinite loops. For example, if you define a new type `MyString` as `string` 268 | > and implement `validation.Validatable` for `MyString`, within the `Validate()` function you should cast the value 269 | > to `string` first before calling `validation.Validate()` to validate it. 270 | 271 | In the following example, the `Address` field of `Customer` is validatable because `Address` implements 272 | `validation.Validatable`. Therefore, when validating a `Customer` struct with `validation.ValidateStruct`, 273 | validation will "dive" into the `Address` field. 274 | 275 | ```go 276 | type Customer struct { 277 | Name string 278 | Gender string 279 | Email string 280 | Address Address 281 | } 282 | 283 | func (c Customer) Validate() error { 284 | return validation.ValidateStruct(&c, 285 | // Name cannot be empty, and the length must be between 5 and 20. 286 | validation.Field(&c.Name, validation.Required, validation.Length(5, 20)), 287 | // Gender is optional, and should be either "Female" or "Male". 288 | validation.Field(&c.Gender, validation.In("Female", "Male")), 289 | // Email cannot be empty and should be in a valid email format. 290 | validation.Field(&c.Email, validation.Required, is.Email), 291 | // Validate Address using its own validation rules 292 | validation.Field(&c.Address), 293 | ) 294 | } 295 | 296 | c := Customer{ 297 | Name: "Qiang Xue", 298 | Email: "q", 299 | Address: Address{ 300 | Street: "123 Main Street", 301 | City: "Unknown", 302 | State: "Virginia", 303 | Zip: "12345", 304 | }, 305 | } 306 | 307 | err := c.Validate() 308 | fmt.Println(err) 309 | // Output: 310 | // Address: (State: must be in a valid format.); Email: must be a valid email address. 311 | ``` 312 | 313 | Sometimes, you may want to skip the invocation of a type's `Validate` method. To do so, simply associate 314 | a `validation.Skip` rule with the value being validated. 315 | 316 | ### Maps/Slices/Arrays of Validatables 317 | 318 | When validating an iterable (map, slice, or array), whose element type implements the `validation.Validatable` interface, 319 | the `validation.Validate` method will call the `Validate` method of every non-nil element. 320 | The validation errors of the elements will be returned as `validation.Errors` which maps the keys of the 321 | invalid elements to their corresponding validation errors. For example, 322 | 323 | ```go 324 | addresses := []Address{ 325 | Address{State: "MD", Zip: "12345"}, 326 | Address{Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"}, 327 | Address{City: "Unknown", State: "NC", Zip: "123"}, 328 | } 329 | err := validation.Validate(addresses) 330 | fmt.Println(err) 331 | // Output: 332 | // 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.). 333 | ``` 334 | 335 | When using `validation.ValidateStruct` to validate a struct, the above validation procedure also applies to those struct 336 | fields which are map/slices/arrays of validatables. 337 | 338 | #### Each 339 | 340 | The `Each` validation rule allows you to apply a set of rules to each element of an array, slice, or map. 341 | 342 | ```go 343 | type Customer struct { 344 | Name string 345 | Emails []string 346 | } 347 | 348 | func (c Customer) Validate() error { 349 | return validation.ValidateStruct(&c, 350 | // Name cannot be empty, and the length must be between 5 and 20. 351 | validation.Field(&c.Name, validation.Required, validation.Length(5, 20)), 352 | // Emails are optional, but if given must be valid. 353 | validation.Field(&c.Emails, validation.Each(is.Email)), 354 | ) 355 | } 356 | 357 | c := Customer{ 358 | Name: "Qiang Xue", 359 | Emails: []Email{ 360 | "valid@example.com", 361 | "invalid", 362 | }, 363 | } 364 | 365 | err := c.Validate() 366 | fmt.Println(err) 367 | // Output: 368 | // Emails: (1: must be a valid email address.). 369 | ``` 370 | 371 | ### Pointers 372 | 373 | When a value being validated is a pointer, most validation rules will validate the actual value pointed to by the pointer. 374 | If the pointer is nil, these rules will skip the validation. 375 | 376 | An exception is the `validation.Required` and `validation.NotNil` rules. When a pointer is nil, they 377 | will report a validation error. 378 | 379 | 380 | ### Types Implementing `sql.Valuer` 381 | 382 | If a data type implements the `sql.Valuer` interface (e.g. `sql.NullString`), the built-in validation rules will handle 383 | it properly. In particular, when a rule is validating such data, it will call the `Value()` method and validate 384 | the returned value instead. 385 | 386 | 387 | ### Required vs. Not Nil 388 | 389 | When validating input values, there are two different scenarios about checking if input values are provided or not. 390 | 391 | In the first scenario, an input value is considered missing if it is not entered or it is entered as a zero value 392 | (e.g. an empty string, a zero integer). You can use the `validation.Required` rule in this case. 393 | 394 | In the second scenario, an input value is considered missing only if it is not entered. A pointer field is usually 395 | used in this case so that you can detect if a value is entered or not by checking if the pointer is nil or not. 396 | You can use the `validation.NotNil` rule to ensure a value is entered (even if it is a zero value). 397 | 398 | 399 | ### Embedded Structs 400 | 401 | The `validation.ValidateStruct` method will properly validate a struct that contains embedded structs. In particular, 402 | the fields of an embedded struct are treated as if they belong directly to the containing struct. For example, 403 | 404 | ```go 405 | type Employee struct { 406 | Name string 407 | } 408 | 409 | type Manager struct { 410 | Employee 411 | Level int 412 | } 413 | 414 | m := Manager{} 415 | err := validation.ValidateStruct(&m, 416 | validation.Field(&m.Name, validation.Required), 417 | validation.Field(&m.Level, validation.Required), 418 | ) 419 | fmt.Println(err) 420 | // Output: 421 | // Level: cannot be blank; Name: cannot be blank. 422 | ``` 423 | 424 | In the above code, we use `&m.Name` to specify the validation of the `Name` field of the embedded struct `Employee`. 425 | And the validation error uses `Name` as the key for the error associated with the `Name` field as if `Name` a field 426 | directly belonging to `Manager`. 427 | 428 | If `Employee` implements the `validation.Validatable` interface, we can also use the following code to validate 429 | `Manager`, which generates the same validation result: 430 | 431 | ```go 432 | func (e Employee) Validate() error { 433 | return validation.ValidateStruct(&e, 434 | validation.Field(&e.Name, validation.Required), 435 | ) 436 | } 437 | 438 | err := validation.ValidateStruct(&m, 439 | validation.Field(&m.Employee), 440 | validation.Field(&m.Level, validation.Required), 441 | ) 442 | fmt.Println(err) 443 | // Output: 444 | // Level: cannot be blank; Name: cannot be blank. 445 | ``` 446 | 447 | 448 | ### Conditional Validation 449 | 450 | Sometimes, we may want to validate a value only when certain condition is met. For example, we want to ensure the 451 | `unit` struct field is not empty only when the `quantity` field is not empty; or we may want to ensure either `email` 452 | or `phone` is provided. The so-called conditional validation can be achieved with the help of `validation.When`. 453 | The following code implements the aforementioned examples: 454 | 455 | ```go 456 | result := validation.ValidateStruct(&a, 457 | validation.Field(&a.Unit, validation.When(a.Quantity != "", validation.Required).Else(validation.Nil)), 458 | validation.Field(&a.Phone, validation.When(a.Email == "", validation.Required.Error('Either phone or Email is required.')), 459 | validation.Field(&a.Email, validation.When(a.Phone == "", validation.Required.Error('Either phone or Email is required.')), 460 | ) 461 | ``` 462 | 463 | Note that `validation.When` and `validation.When.Else` can take a list of validation rules. These rules will be executed only when the condition is true (When) or false (Else). 464 | 465 | The above code can also be simplified using the shortcut `validation.Required.When`: 466 | 467 | ```go 468 | result := validation.ValidateStruct(&a, 469 | validation.Field(&a.Unit, validation.Required.When(a.Quantity != ""), validation.Nil.When(a.Quantity == "")), 470 | validation.Field(&a.Phone, validation.Required.When(a.Email == "").Error('Either phone or Email is required.')), 471 | validation.Field(&a.Email, validation.Required.When(a.Phone == "").Error('Either phone or Email is required.')), 472 | ) 473 | ``` 474 | 475 | ### Customizing Error Messages 476 | 477 | All built-in validation rules allow you to customize their error messages. To do so, simply call the `Error()` method 478 | of the rules. For example, 479 | 480 | ```go 481 | data := "2123" 482 | err := validation.Validate(data, 483 | validation.Required.Error("is required"), 484 | validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("must be a string with five digits"), 485 | ) 486 | fmt.Println(err) 487 | // Output: 488 | // must be a string with five digits 489 | ``` 490 | 491 | You can also customize the pre-defined error(s) of a built-in rule such that the customization applies to *every* 492 | instance of the rule. For example, the `Required` rule uses the pre-defined error `ErrRequired`. You can customize it 493 | during the application initialization: 494 | ```go 495 | validation.ErrRequired = validation.ErrRequired.SetMessage("the value is required") 496 | ``` 497 | 498 | ### Error Code and Message Translation 499 | 500 | The errors returned by the validation rules implement the `Error` interface which contains the `Code()` method 501 | to provide the error code information. While the message of a validation error is often customized, the code is immutable. 502 | You can use error code to programmatically check a validation error or look for the translation of the corresponding message. 503 | 504 | If you are developing your own validation rules, you can use `validation.NewError()` to create a validation error which 505 | implements the aforementioned `Error` interface. 506 | 507 | ## Creating Custom Rules 508 | 509 | Creating a custom rule is as simple as implementing the `validation.Rule` interface. The interface contains a single 510 | method as shown below, which should validate the value and return the validation error, if any: 511 | 512 | ```go 513 | // Validate validates a value and returns an error if validation fails. 514 | Validate(value interface{}) error 515 | ``` 516 | 517 | If you already have a function with the same signature as shown above, you can call `validation.By()` to turn 518 | it into a validation rule. For example, 519 | 520 | ```go 521 | func checkAbc(value interface{}) error { 522 | s, _ := value.(string) 523 | if s != "abc" { 524 | return errors.New("must be abc") 525 | } 526 | return nil 527 | } 528 | 529 | err := validation.Validate("xyz", validation.By(checkAbc)) 530 | fmt.Println(err) 531 | // Output: must be abc 532 | ``` 533 | 534 | If your validation function takes additional parameters, you can use the following closure trick: 535 | 536 | ```go 537 | func stringEquals(str string) validation.RuleFunc { 538 | return func(value interface{}) error { 539 | s, _ := value.(string) 540 | if s != str { 541 | return errors.New("unexpected string") 542 | } 543 | return nil 544 | } 545 | } 546 | 547 | err := validation.Validate("xyz", validation.By(stringEquals("abc"))) 548 | fmt.Println(err) 549 | // Output: unexpected string 550 | ``` 551 | 552 | 553 | ### Rule Groups 554 | 555 | When a combination of several rules are used in multiple places, you may use the following trick to create a 556 | rule group so that your code is more maintainable. 557 | 558 | ```go 559 | var NameRule = []validation.Rule{ 560 | validation.Required, 561 | validation.Length(5, 20), 562 | } 563 | 564 | type User struct { 565 | FirstName string 566 | LastName string 567 | } 568 | 569 | func (u User) Validate() error { 570 | return validation.ValidateStruct(&u, 571 | validation.Field(&u.FirstName, NameRule...), 572 | validation.Field(&u.LastName, NameRule...), 573 | ) 574 | } 575 | ``` 576 | 577 | In the above example, we create a rule group `NameRule` which consists of two validation rules. We then use this rule 578 | group to validate both `FirstName` and `LastName`. 579 | 580 | 581 | ## Context-aware Validation 582 | 583 | While most validation rules are self-contained, some rules may depend dynamically on a context. A rule may implement the 584 | `validation.RuleWithContext` interface to support the so-called context-aware validation. 585 | 586 | To validate an arbitrary value with a context, call `validation.ValidateWithContext()`. The `context.Conext` parameter 587 | will be passed along to those rules that implement `validation.RuleWithContext`. 588 | 589 | To validate the fields of a struct with a context, call `validation.ValidateStructWithContext()`. 590 | 591 | You can define a context-aware rule from scratch by implementing both `validation.Rule` and `validation.RuleWithContext`. 592 | You can also use `validation.WithContext()` to turn a function into a context-aware rule. For example, 593 | 594 | 595 | ```go 596 | rule := validation.WithContext(func(ctx context.Context, value interface{}) error { 597 | if ctx.Value("secret") == value.(string) { 598 | return nil 599 | } 600 | return errors.New("value incorrect") 601 | }) 602 | value := "xyz" 603 | ctx := context.WithValue(context.Background(), "secret", "example") 604 | err := validation.ValidateWithContext(ctx, value, rule) 605 | fmt.Println(err) 606 | // Output: value incorrect 607 | ``` 608 | 609 | When performing context-aware validation, if a rule does not implement `validation.RuleWithContext`, its 610 | `validation.Rule` will be used instead. 611 | 612 | 613 | ## Built-in Validation Rules 614 | 615 | The following rules are provided in the `validation` package: 616 | 617 | * `In(...interface{})`: checks if a value can be found in the given list of values. 618 | * `NotIn(...interface{})`: checks if a value is NOT among the given list of values. 619 | * `Length(min, max int)`: checks if the length of a value is within the specified range. 620 | This rule should only be used for validating strings, slices, maps, and arrays. 621 | * `RuneLength(min, max int)`: checks if the length of a string is within the specified range. 622 | This rule is similar as `Length` except that when the value being validated is a string, it checks 623 | its rune length instead of byte length. 624 | * `Min(min interface{})` and `Max(max interface{})`: checks if a value is within the specified range. 625 | These two rules should only be used for validating int, uint, float and time.Time types. 626 | * `Match(*regexp.Regexp)`: checks if a value matches the specified regular expression. 627 | This rule should only be used for strings and byte slices. 628 | * `Date(layout string)`: checks if a string value is a date whose format is specified by the layout. 629 | By calling `Min()` and/or `Max()`, you can check additionally if the date is within the specified range. 630 | * `Required`: checks if a value is not empty (neither nil nor zero). 631 | * `NotNil`: checks if a pointer value is not nil. Non-pointer values are considered valid. 632 | * `NilOrNotEmpty`: checks if a value is a nil pointer or a non-empty value. This differs from `Required` in that it treats a nil pointer as valid. 633 | * `Nil`: checks if a value is a nil pointer. 634 | * `Empty`: checks if a value is empty. nil pointers are considered valid. 635 | * `Skip`: this is a special rule used to indicate that all rules following it should be skipped (including the nested ones). 636 | * `MultipleOf`: checks if the value is a multiple of the specified range. 637 | * `Each(rules ...Rule)`: checks the elements within an iterable (map/slice/array) with other rules. 638 | * `When(condition, rules ...Rule)`: validates with the specified rules only when the condition is true. 639 | * `Else(rules ...Rule)`: must be used with `When(condition, rules ...Rule)`, validates with the specified rules only when the condition is false. 640 | 641 | The `is` sub-package provides a list of commonly used string validation rules that can be used to check if the format 642 | of a value satisfies certain requirements. Note that these rules only handle strings and byte slices and if a string 643 | or byte slice is empty, it is considered valid. You may use a `Required` rule to ensure a value is not empty. 644 | Below is the whole list of the rules provided by the `is` package: 645 | 646 | * `Email`: validates if a string is an email or not. It also checks if the MX record exists for the email domain. 647 | * `EmailFormat`: validates if a string is an email or not. It does NOT check the existence of the MX record. 648 | * `URL`: validates if a string is a valid URL 649 | * `RequestURL`: validates if a string is a valid request URL 650 | * `RequestURI`: validates if a string is a valid request URI 651 | * `Alpha`: validates if a string contains English letters only (a-zA-Z) 652 | * `Digit`: validates if a string contains digits only (0-9) 653 | * `Alphanumeric`: validates if a string contains English letters and digits only (a-zA-Z0-9) 654 | * `UTFLetter`: validates if a string contains unicode letters only 655 | * `UTFDigit`: validates if a string contains unicode decimal digits only 656 | * `UTFLetterNumeric`: validates if a string contains unicode letters and numbers only 657 | * `UTFNumeric`: validates if a string contains unicode number characters (category N) only 658 | * `LowerCase`: validates if a string contains lower case unicode letters only 659 | * `UpperCase`: validates if a string contains upper case unicode letters only 660 | * `Hexadecimal`: validates if a string is a valid hexadecimal number 661 | * `HexColor`: validates if a string is a valid hexadecimal color code 662 | * `RGBColor`: validates if a string is a valid RGB color in the form of rgb(R, G, B) 663 | * `Int`: validates if a string is a valid integer number 664 | * `Float`: validates if a string is a floating point number 665 | * `UUIDv3`: validates if a string is a valid version 3 UUID 666 | * `UUIDv4`: validates if a string is a valid version 4 UUID 667 | * `UUIDv5`: validates if a string is a valid version 5 UUID 668 | * `UUID`: validates if a string is a valid UUID 669 | * `CreditCard`: validates if a string is a valid credit card number 670 | * `ISBN10`: validates if a string is an ISBN version 10 671 | * `ISBN13`: validates if a string is an ISBN version 13 672 | * `ISBN`: validates if a string is an ISBN (either version 10 or 13) 673 | * `JSON`: validates if a string is in valid JSON format 674 | * `ASCII`: validates if a string contains ASCII characters only 675 | * `PrintableASCII`: validates if a string contains printable ASCII characters only 676 | * `Multibyte`: validates if a string contains multibyte characters 677 | * `FullWidth`: validates if a string contains full-width characters 678 | * `HalfWidth`: validates if a string contains half-width characters 679 | * `VariableWidth`: validates if a string contains both full-width and half-width characters 680 | * `Base64`: validates if a string is encoded in Base64 681 | * `DataURI`: validates if a string is a valid base64-encoded data URI 682 | * `E164`: validates if a string is a valid E164 phone number (+19251232233) 683 | * `CountryCode2`: validates if a string is a valid ISO3166 Alpha 2 country code 684 | * `CountryCode3`: validates if a string is a valid ISO3166 Alpha 3 country code 685 | * `DialString`: validates if a string is a valid dial string that can be passed to Dial() 686 | * `MAC`: validates if a string is a MAC address 687 | * `IP`: validates if a string is a valid IP address (either version 4 or 6) 688 | * `IPv4`: validates if a string is a valid version 4 IP address 689 | * `IPv6`: validates if a string is a valid version 6 IP address 690 | * `Subdomain`: validates if a string is valid subdomain 691 | * `Domain`: validates if a string is valid domain 692 | * `DNSName`: validates if a string is valid DNS name 693 | * `Host`: validates if a string is a valid IP (both v4 and v6) or a valid DNS name 694 | * `Port`: validates if a string is a valid port number 695 | * `MongoID`: validates if a string is a valid Mongo ID 696 | * `Latitude`: validates if a string is a valid latitude 697 | * `Longitude`: validates if a string is a valid longitude 698 | * `SSN`: validates if a string is a social security number (SSN) 699 | * `Semver`: validates if a string is a valid semantic version 700 | 701 | ## Credits 702 | 703 | The `is` sub-package wraps the excellent validators provided by the [govalidator](https://github.com/asaskevich/govalidator) package. 704 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Instructions 2 | 3 | ## Upgrade from 3.x to 4.x 4 | 5 | If you are customizing the error messages of the following built-in validation rules, you may need to 6 | change the parameter placeholders from `%v` to the Go template variable placeholders. 7 | * `Length`: use `{{.min}}` and `{{.max}}` in the error message to refer to the minimum and maximum lengths. 8 | * `Min`: use `{{.threshold}}` in the error message to refer to the lower bound. 9 | * `Max`: use `{{.threshold}}` in the error message to refer to the upper bound 10 | * `MultipleOf`: use `{{.base}}` in the error message to refer to the base of the multiples. 11 | 12 | For example, 13 | ```go 14 | // 3.x 15 | lengthRule := validation.Length(2,10).Error("the length must be between %v and %v") 16 | 17 | // 4.x 18 | lengthRule := validation.Length(2,10).Error("the length must be between {{.min}} and {{.max}}") 19 | ``` 20 | 21 | ## Upgrade from 2.x to 3.x 22 | 23 | * Instead of using `StructRules` to define struct validation rules, use `ValidateStruct()` to declare and perform 24 | struct validation. The following code snippet shows how to modify your code: 25 | ```go 26 | // 2.x usage 27 | err := validation.StructRules{}. 28 | Add("Street", validation.Required, validation.Length(5, 50)). 29 | Add("City", validation.Required, validation.Length(5, 50)). 30 | Add("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))). 31 | Add("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))). 32 | Validate(a) 33 | 34 | // 3.x usage 35 | err := validation.ValidateStruct(&a, 36 | validation.Field(&a.Street, validation.Required, validation.Length(5, 50)), 37 | validation.Field(&a.City, validation.Required, validation.Length(5, 50)), 38 | validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), 39 | validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 40 | ) 41 | ``` 42 | 43 | * Instead of using `Rules` to declare a rule list and use it to validate a value, call `Validate()` with the rules directly. 44 | ```go 45 | data := "example" 46 | 47 | // 2.x usage 48 | rules := validation.Rules{ 49 | validation.Required, 50 | validation.Length(5, 100), 51 | is.URL, 52 | } 53 | err := rules.Validate(data) 54 | 55 | // 3.x usage 56 | err := validation.Validate(data, 57 | validation.Required, 58 | validation.Length(5, 100), 59 | is.URL, 60 | ) 61 | ``` 62 | 63 | * The default struct tags used for determining error keys is changed from `validation` to `json`. You may modify 64 | `validation.ErrorTag` to change it back. -------------------------------------------------------------------------------- /absent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | var ( 8 | // ErrNil is the error that returns when a value is not nil. 9 | ErrNil = NewError("validation_nil", "must be blank") 10 | // ErrEmpty is the error that returns when a not nil value is not empty. 11 | ErrEmpty = NewError("validation_empty", "must be blank") 12 | ) 13 | 14 | // Nil is a validation rule that checks if a value is nil. 15 | // It is the opposite of NotNil rule 16 | var Nil = absentRule{condition: true, skipNil: false} 17 | 18 | // Empty checks if a not nil value is empty. 19 | var Empty = absentRule{condition: true, skipNil: true} 20 | 21 | type absentRule struct { 22 | condition bool 23 | err Error 24 | skipNil bool 25 | } 26 | 27 | // Validate checks if the given value is valid or not. 28 | func (r absentRule) Validate(value interface{}) error { 29 | if r.condition { 30 | value, isNil := Indirect(value) 31 | if !r.skipNil && !isNil || r.skipNil && !isNil && !IsEmpty(value) { 32 | if r.err != nil { 33 | return r.err 34 | } 35 | if r.skipNil { 36 | return ErrEmpty 37 | } 38 | return ErrNil 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | // When sets the condition that determines if the validation should be performed. 45 | func (r absentRule) When(condition bool) absentRule { 46 | r.condition = condition 47 | return r 48 | } 49 | 50 | // Error sets the error message for the rule. 51 | func (r absentRule) Error(message string) absentRule { 52 | if r.err == nil { 53 | if r.skipNil { 54 | r.err = ErrEmpty 55 | } else { 56 | r.err = ErrNil 57 | } 58 | } 59 | r.err = r.err.SetMessage(message) 60 | return r 61 | } 62 | 63 | // ErrorObject sets the error struct for the rule. 64 | func (r absentRule) ErrorObject(err Error) absentRule { 65 | r.err = err 66 | return r 67 | } 68 | -------------------------------------------------------------------------------- /absent_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestNil(t *testing.T) { 15 | s1 := "123" 16 | s2 := "" 17 | var time1 time.Time 18 | tests := []struct { 19 | tag string 20 | value interface{} 21 | err string 22 | }{ 23 | {"t1", 123, "must be blank"}, 24 | {"t2", "", "must be blank"}, 25 | {"t3", &s1, "must be blank"}, 26 | {"t4", &s2, "must be blank"}, 27 | {"t5", nil, ""}, 28 | {"t6", time1, "must be blank"}, 29 | } 30 | 31 | for _, test := range tests { 32 | r := Nil 33 | err := r.Validate(test.value) 34 | assertError(t, test.err, err, test.tag) 35 | } 36 | } 37 | 38 | func TestEmpty(t *testing.T) { 39 | s1 := "123" 40 | s2 := "" 41 | time1 := time.Now() 42 | var time2 time.Time 43 | tests := []struct { 44 | tag string 45 | value interface{} 46 | err string 47 | }{ 48 | {"t1", 123, "must be blank"}, 49 | {"t2", "", ""}, 50 | {"t3", &s1, "must be blank"}, 51 | {"t4", &s2, ""}, 52 | {"t5", nil, ""}, 53 | {"t6", time1, "must be blank"}, 54 | {"t7", time2, ""}, 55 | } 56 | 57 | for _, test := range tests { 58 | r := Empty 59 | err := r.Validate(test.value) 60 | assertError(t, test.err, err, test.tag) 61 | } 62 | } 63 | 64 | func TestAbsentRule_When(t *testing.T) { 65 | r := Nil.When(false) 66 | err := Validate(42, r) 67 | assert.Nil(t, err) 68 | 69 | r = Nil.When(true) 70 | err = Validate(42, r) 71 | assert.Equal(t, ErrNil, err) 72 | } 73 | 74 | func Test_absentRule_Error(t *testing.T) { 75 | r := Nil 76 | assert.Equal(t, "must be blank", r.Validate("42").Error()) 77 | assert.False(t, r.skipNil) 78 | r2 := r.Error("123") 79 | assert.Equal(t, "must be blank", r.Validate("42").Error()) 80 | assert.False(t, r.skipNil) 81 | assert.Equal(t, "123", r2.err.Message()) 82 | assert.False(t, r2.skipNil) 83 | 84 | r = Empty 85 | assert.Equal(t, "must be blank", r.Validate("42").Error()) 86 | assert.True(t, r.skipNil) 87 | r2 = r.Error("123") 88 | assert.Equal(t, "must be blank", r.Validate("42").Error()) 89 | assert.True(t, r.skipNil) 90 | assert.Equal(t, "123", r2.err.Message()) 91 | assert.True(t, r2.skipNil) 92 | } 93 | 94 | func TestAbsentRule_Error(t *testing.T) { 95 | r := Nil 96 | 97 | err := NewError("code", "abc") 98 | r = r.ErrorObject(err) 99 | 100 | assert.Equal(t, err, r.err) 101 | assert.Equal(t, err.Code(), r.err.Code()) 102 | assert.Equal(t, err.Message(), r.err.Message()) 103 | assert.NotEqual(t, err, Nil.err) 104 | } 105 | -------------------------------------------------------------------------------- /date.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | var ( 12 | // ErrDateInvalid is the error that returns in case of an invalid date. 13 | ErrDateInvalid = NewError("validation_date_invalid", "must be a valid date") 14 | // ErrDateOutOfRange is the error that returns in case of an invalid date. 15 | ErrDateOutOfRange = NewError("validation_date_out_of_range", "the date is out of range") 16 | ) 17 | 18 | // DateRule is a validation rule that validates date/time string values. 19 | type DateRule struct { 20 | layout string 21 | min, max time.Time 22 | err, rangeErr Error 23 | } 24 | 25 | // Date returns a validation rule that checks if a string value is in a format that can be parsed into a date. 26 | // The format of the date should be specified as the layout parameter which accepts the same value as that for time.Parse. 27 | // For example, 28 | // validation.Date(time.ANSIC) 29 | // validation.Date("02 Jan 06 15:04 MST") 30 | // validation.Date("2006-01-02") 31 | // 32 | // By calling Min() and/or Max(), you can let the Date rule to check if a parsed date value is within 33 | // the specified date range. 34 | // 35 | // An empty value is considered valid. Use the Required rule to make sure a value is not empty. 36 | func Date(layout string) DateRule { 37 | return DateRule{ 38 | layout: layout, 39 | err: ErrDateInvalid, 40 | rangeErr: ErrDateOutOfRange, 41 | } 42 | } 43 | 44 | // Error sets the error message that is used when the value being validated is not a valid date. 45 | func (r DateRule) Error(message string) DateRule { 46 | r.err = r.err.SetMessage(message) 47 | return r 48 | } 49 | 50 | // ErrorObject sets the error struct that is used when the value being validated is not a valid date.. 51 | func (r DateRule) ErrorObject(err Error) DateRule { 52 | r.err = err 53 | return r 54 | } 55 | 56 | // RangeError sets the error message that is used when the value being validated is out of the specified Min/Max date range. 57 | func (r DateRule) RangeError(message string) DateRule { 58 | r.rangeErr = r.rangeErr.SetMessage(message) 59 | return r 60 | } 61 | 62 | // RangeErrorObject sets the error struct that is used when the value being validated is out of the specified Min/Max date range. 63 | func (r DateRule) RangeErrorObject(err Error) DateRule { 64 | r.rangeErr = err 65 | return r 66 | } 67 | 68 | // Min sets the minimum date range. A zero value means skipping the minimum range validation. 69 | func (r DateRule) Min(min time.Time) DateRule { 70 | r.min = min 71 | return r 72 | } 73 | 74 | // Max sets the maximum date range. A zero value means skipping the maximum range validation. 75 | func (r DateRule) Max(max time.Time) DateRule { 76 | r.max = max 77 | return r 78 | } 79 | 80 | // Validate checks if the given value is a valid date. 81 | func (r DateRule) Validate(value interface{}) error { 82 | value, isNil := Indirect(value) 83 | if isNil || IsEmpty(value) { 84 | return nil 85 | } 86 | 87 | str, err := EnsureString(value) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | date, err := time.Parse(r.layout, str) 93 | if err != nil { 94 | return r.err 95 | } 96 | 97 | if !r.min.IsZero() && r.min.After(date) || !r.max.IsZero() && date.After(r.max) { 98 | return r.rangeErr 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /date_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestDate(t *testing.T) { 15 | tests := []struct { 16 | tag string 17 | layout string 18 | value interface{} 19 | err string 20 | }{ 21 | {"t1", time.ANSIC, "", ""}, 22 | {"t2", time.ANSIC, "Wed Feb 4 21:00:57 2009", ""}, 23 | {"t3", time.ANSIC, "Wed Feb 29 21:00:57 2009", "must be a valid date"}, 24 | {"t4", "2006-01-02", "2009-11-12", ""}, 25 | {"t5", "2006-01-02", "2009-11-12 21:00:57", "must be a valid date"}, 26 | {"t6", "2006-01-02", "2009-1-12", "must be a valid date"}, 27 | {"t7", "2006-01-02", "2009-01-12", ""}, 28 | {"t8", "2006-01-02", "2009-01-32", "must be a valid date"}, 29 | {"t9", "2006-01-02", 1, "must be either a string or byte slice"}, 30 | } 31 | 32 | for _, test := range tests { 33 | r := Date(test.layout) 34 | err := r.Validate(test.value) 35 | assertError(t, test.err, err, test.tag) 36 | } 37 | } 38 | 39 | func TestDateRule_Error(t *testing.T) { 40 | r := Date(time.RFC3339) 41 | assert.Equal(t, "must be a valid date", r.Validate("0001-01-02T15:04:05Z07:00").Error()) 42 | r2 := r.Min(time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)) 43 | assert.Equal(t, "the date is out of range", r2.Validate("1999-01-02T15:04:05Z").Error()) 44 | r = r.Error("123") 45 | r = r.RangeError("456") 46 | assert.Equal(t, "123", r.err.Message()) 47 | assert.Equal(t, "456", r.rangeErr.Message()) 48 | } 49 | 50 | func TestDateRule_ErrorObject(t *testing.T) { 51 | r := Date(time.RFC3339) 52 | assert.Equal(t, "must be a valid date", r.Validate("0001-01-02T15:04:05Z07:00").Error()) 53 | 54 | r = r.ErrorObject(NewError("code", "abc")) 55 | 56 | assert.Equal(t, "code", r.err.Code()) 57 | assert.Equal(t, "abc", r.Validate("0001-01-02T15:04:05Z07:00").Error()) 58 | 59 | r2 := r.Min(time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)) 60 | assert.Equal(t, "the date is out of range", r2.Validate("1999-01-02T15:04:05Z").Error()) 61 | 62 | r = r.ErrorObject(NewError("C", "def")) 63 | r = r.RangeErrorObject(NewError("D", "123")) 64 | 65 | assert.Equal(t, "C", r.err.Code()) 66 | assert.Equal(t, "def", r.err.Message()) 67 | assert.Equal(t, "D", r.rangeErr.Code()) 68 | assert.Equal(t, "123", r.rangeErr.Message()) 69 | } 70 | 71 | func TestDateRule_MinMax(t *testing.T) { 72 | r := Date(time.ANSIC) 73 | assert.True(t, r.min.IsZero()) 74 | assert.True(t, r.max.IsZero()) 75 | r = r.Min(time.Now()) 76 | assert.False(t, r.min.IsZero()) 77 | assert.True(t, r.max.IsZero()) 78 | r = r.Max(time.Now()) 79 | assert.False(t, r.max.IsZero()) 80 | 81 | r2 := Date("2006-01-02").Min(time.Date(2000, 12, 1, 0, 0, 0, 0, time.UTC)).Max(time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC)) 82 | assert.Nil(t, r2.Validate("2010-01-02")) 83 | err := r2.Validate("1999-01-02") 84 | if assert.NotNil(t, err) { 85 | assert.Equal(t, "the date is out of range", err.Error()) 86 | } 87 | err = r2.Validate("2021-01-02") 88 | if assert.NotNil(t, err) { 89 | assert.Equal(t, "the date is out of range", err.Error()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /each.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "reflect" 11 | "strconv" 12 | ) 13 | 14 | // Each returns a validation rule that loops through an iterable (map, slice or array) 15 | // and validates each value inside with the provided rules. 16 | // An empty iterable is considered valid. Use the Required rule to make sure the iterable is not empty. 17 | func Each(rules ...Rule) EachRule { 18 | return EachRule{ 19 | rules: rules, 20 | } 21 | } 22 | 23 | // EachRule is a validation rule that validates elements in a map/slice/array using the specified list of rules. 24 | type EachRule struct { 25 | rules []Rule 26 | } 27 | 28 | // Validate loops through the given iterable and calls the Ozzo Validate() method for each value. 29 | func (r EachRule) Validate(value interface{}) error { 30 | return r.ValidateWithContext(nil, value) 31 | } 32 | 33 | // ValidateWithContext loops through the given iterable and calls the Ozzo ValidateWithContext() method for each value. 34 | func (r EachRule) ValidateWithContext(ctx context.Context, value interface{}) error { 35 | errs := Errors{} 36 | 37 | v := reflect.ValueOf(value) 38 | switch v.Kind() { 39 | case reflect.Map: 40 | for _, k := range v.MapKeys() { 41 | val := r.getInterface(v.MapIndex(k)) 42 | var err error 43 | if ctx == nil { 44 | err = Validate(val, r.rules...) 45 | } else { 46 | err = ValidateWithContext(ctx, val, r.rules...) 47 | } 48 | if err != nil { 49 | errs[r.getString(k)] = err 50 | } 51 | } 52 | case reflect.Slice, reflect.Array: 53 | for i := 0; i < v.Len(); i++ { 54 | val := r.getInterface(v.Index(i)) 55 | var err error 56 | if ctx == nil { 57 | err = Validate(val, r.rules...) 58 | } else { 59 | err = ValidateWithContext(ctx, val, r.rules...) 60 | } 61 | if err != nil { 62 | errs[strconv.Itoa(i)] = err 63 | } 64 | } 65 | default: 66 | return errors.New("must be an iterable (map, slice or array)") 67 | } 68 | 69 | if len(errs) > 0 { 70 | return errs 71 | } 72 | return nil 73 | } 74 | 75 | func (r EachRule) getInterface(value reflect.Value) interface{} { 76 | switch value.Kind() { 77 | case reflect.Ptr, reflect.Interface: 78 | if value.IsNil() { 79 | return nil 80 | } 81 | return value.Elem().Interface() 82 | default: 83 | return value.Interface() 84 | } 85 | } 86 | 87 | func (r EachRule) getString(value reflect.Value) string { 88 | switch value.Kind() { 89 | case reflect.Ptr, reflect.Interface: 90 | if value.IsNil() { 91 | return "" 92 | } 93 | return value.Elem().String() 94 | default: 95 | return value.String() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /each_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestEach(t *testing.T) { 11 | var a *int 12 | var f = func(v string) string { return v } 13 | var c0 chan int 14 | c1 := make(chan int) 15 | 16 | tests := []struct { 17 | tag string 18 | value interface{} 19 | err string 20 | }{ 21 | {"t1", nil, "must be an iterable (map, slice or array)"}, 22 | {"t2", map[string]string{}, ""}, 23 | {"t3", map[string]string{"key1": "value1", "key2": "value2"}, ""}, 24 | {"t4", map[string]string{"key1": "", "key2": "value2", "key3": ""}, "key1: cannot be blank; key3: cannot be blank."}, 25 | {"t5", map[string]map[string]string{"key1": {"key1.1": "value1"}, "key2": {"key2.1": "value1"}}, ""}, 26 | {"t6", map[string]map[string]string{"": nil}, ": cannot be blank."}, 27 | {"t7", map[interface{}]interface{}{}, ""}, 28 | {"t8", map[interface{}]interface{}{"key1": struct{ foo string }{"foo"}}, ""}, 29 | {"t9", map[interface{}]interface{}{nil: "", "": "", "key1": nil}, ": cannot be blank; key1: cannot be blank."}, 30 | {"t10", []string{"value1", "value2", "value3"}, ""}, 31 | {"t11", []string{"", "value2", ""}, "0: cannot be blank; 2: cannot be blank."}, 32 | {"t12", []interface{}{struct{ foo string }{"foo"}}, ""}, 33 | {"t13", []interface{}{nil, a}, "0: cannot be blank; 1: cannot be blank."}, 34 | {"t14", []interface{}{c0, c1, f}, "0: cannot be blank."}, 35 | } 36 | 37 | for _, test := range tests { 38 | r := Each(Required) 39 | err := r.Validate(test.value) 40 | assertError(t, test.err, err, test.tag) 41 | } 42 | } 43 | 44 | func TestEachWithContext(t *testing.T) { 45 | rule := Each(WithContext(func(ctx context.Context, value interface{}) error { 46 | if !strings.Contains(value.(string), ctx.Value(contains).(string)) { 47 | return errors.New("unexpected value") 48 | } 49 | return nil 50 | })) 51 | ctx1 := context.WithValue(context.Background(), contains, "abc") 52 | ctx2 := context.WithValue(context.Background(), contains, "xyz") 53 | 54 | tests := []struct { 55 | tag string 56 | value interface{} 57 | ctx context.Context 58 | err string 59 | }{ 60 | {"t1.1", map[string]string{"key": "abc"}, ctx1, ""}, 61 | {"t1.2", map[string]string{"key": "abc"}, ctx2, "key: unexpected value."}, 62 | {"t1.3", map[string]string{"key": "xyz"}, ctx1, "key: unexpected value."}, 63 | {"t1.4", map[string]string{"key": "xyz"}, ctx2, ""}, 64 | {"t1.5", []string{"abc"}, ctx1, ""}, 65 | {"t1.6", []string{"abc"}, ctx2, "0: unexpected value."}, 66 | {"t1.7", []string{"xyz"}, ctx1, "0: unexpected value."}, 67 | {"t1.8", []string{"xyz"}, ctx2, ""}, 68 | } 69 | 70 | for _, test := range tests { 71 | err := ValidateWithContext(test.ctx, test.value, rule) 72 | assertError(t, test.err, err, test.tag) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "fmt" 11 | "sort" 12 | "strings" 13 | "text/template" 14 | ) 15 | 16 | type ( 17 | // Error interface represents an validation error 18 | Error interface { 19 | Error() string 20 | Code() string 21 | Message() string 22 | SetMessage(string) Error 23 | Params() map[string]interface{} 24 | SetParams(map[string]interface{}) Error 25 | } 26 | 27 | // ErrorObject is the default validation error 28 | // that implements the Error interface. 29 | ErrorObject struct { 30 | code string 31 | message string 32 | params map[string]interface{} 33 | } 34 | 35 | // Errors represents the validation errors that are indexed by struct field names, map or slice keys. 36 | // values are Error or Errors (for map, slice and array error value is Errors). 37 | Errors map[string]error 38 | 39 | // InternalError represents an error that should NOT be treated as a validation error. 40 | InternalError interface { 41 | error 42 | InternalError() error 43 | } 44 | 45 | internalError struct { 46 | error 47 | } 48 | ) 49 | 50 | // NewInternalError wraps a given error into an InternalError. 51 | func NewInternalError(err error) InternalError { 52 | return internalError{error: err} 53 | } 54 | 55 | // InternalError returns the actual error that it wraps around. 56 | func (e internalError) InternalError() error { 57 | return e.error 58 | } 59 | 60 | // SetCode set the error's translation code. 61 | func (e ErrorObject) SetCode(code string) Error { 62 | e.code = code 63 | return e 64 | } 65 | 66 | // Code get the error's translation code. 67 | func (e ErrorObject) Code() string { 68 | return e.code 69 | } 70 | 71 | // SetParams set the error's params. 72 | func (e ErrorObject) SetParams(params map[string]interface{}) Error { 73 | e.params = params 74 | return e 75 | } 76 | 77 | // AddParam add parameter to the error's parameters. 78 | func (e ErrorObject) AddParam(name string, value interface{}) Error { 79 | if e.params == nil { 80 | e.params = make(map[string]interface{}) 81 | } 82 | 83 | e.params[name] = value 84 | return e 85 | } 86 | 87 | // Params returns the error's params. 88 | func (e ErrorObject) Params() map[string]interface{} { 89 | return e.params 90 | } 91 | 92 | // SetMessage set the error's message. 93 | func (e ErrorObject) SetMessage(message string) Error { 94 | e.message = message 95 | return e 96 | } 97 | 98 | // Message return the error's message. 99 | func (e ErrorObject) Message() string { 100 | return e.message 101 | } 102 | 103 | // Error returns the error message. 104 | func (e ErrorObject) Error() string { 105 | if len(e.params) == 0 { 106 | return e.message 107 | } 108 | 109 | res := bytes.Buffer{} 110 | _ = template.Must(template.New("err").Parse(e.message)).Execute(&res, e.params) 111 | 112 | return res.String() 113 | } 114 | 115 | // Error returns the error string of Errors. 116 | func (es Errors) Error() string { 117 | if len(es) == 0 { 118 | return "" 119 | } 120 | 121 | keys := make([]string, len(es)) 122 | i := 0 123 | for key := range es { 124 | keys[i] = key 125 | i++ 126 | } 127 | sort.Strings(keys) 128 | 129 | var s strings.Builder 130 | for i, key := range keys { 131 | if i > 0 { 132 | s.WriteString("; ") 133 | } 134 | if errs, ok := es[key].(Errors); ok { 135 | _, _ = fmt.Fprintf(&s, "%v: (%v)", key, errs) 136 | } else { 137 | _, _ = fmt.Fprintf(&s, "%v: %v", key, es[key].Error()) 138 | } 139 | } 140 | s.WriteString(".") 141 | return s.String() 142 | } 143 | 144 | // MarshalJSON converts the Errors into a valid JSON. 145 | func (es Errors) MarshalJSON() ([]byte, error) { 146 | errs := map[string]interface{}{} 147 | for key, err := range es { 148 | if ms, ok := err.(json.Marshaler); ok { 149 | errs[key] = ms 150 | } else { 151 | errs[key] = err.Error() 152 | } 153 | } 154 | return json.Marshal(errs) 155 | } 156 | 157 | // Filter removes all nils from Errors and returns back the updated Errors as an error. 158 | // If the length of Errors becomes 0, it will return nil. 159 | func (es Errors) Filter() error { 160 | for key, value := range es { 161 | if value == nil { 162 | delete(es, key) 163 | } 164 | } 165 | if len(es) == 0 { 166 | return nil 167 | } 168 | return es 169 | } 170 | 171 | // NewError create new validation error. 172 | func NewError(code, message string) Error { 173 | return ErrorObject{ 174 | code: code, 175 | message: message, 176 | } 177 | } 178 | 179 | // Assert that our ErrorObject implements the Error interface. 180 | var _ Error = ErrorObject{} 181 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "errors" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestNewInternalError(t *testing.T) { 15 | err := NewInternalError(errors.New("abc")) 16 | if assert.NotNil(t, err.InternalError()) { 17 | assert.Equal(t, "abc", err.InternalError().Error()) 18 | } 19 | } 20 | 21 | func TestErrors_Error(t *testing.T) { 22 | errs := Errors{ 23 | "B": errors.New("B1"), 24 | "C": errors.New("C1"), 25 | "A": errors.New("A1"), 26 | } 27 | assert.Equal(t, "A: A1; B: B1; C: C1.", errs.Error()) 28 | 29 | errs = Errors{ 30 | "B": errors.New("B1"), 31 | } 32 | assert.Equal(t, "B: B1.", errs.Error()) 33 | 34 | errs = Errors{} 35 | assert.Equal(t, "", errs.Error()) 36 | } 37 | 38 | func TestErrors_MarshalMessage(t *testing.T) { 39 | errs := Errors{ 40 | "A": errors.New("A1"), 41 | "B": Errors{ 42 | "2": errors.New("B1"), 43 | }, 44 | } 45 | errsJSON, err := errs.MarshalJSON() 46 | assert.Nil(t, err) 47 | assert.Equal(t, "{\"A\":\"A1\",\"B\":{\"2\":\"B1\"}}", string(errsJSON)) 48 | } 49 | 50 | func TestErrors_Filter(t *testing.T) { 51 | errs := Errors{ 52 | "B": errors.New("B1"), 53 | "C": nil, 54 | "A": errors.New("A1"), 55 | } 56 | err := errs.Filter() 57 | assert.Equal(t, 2, len(errs)) 58 | if assert.NotNil(t, err) { 59 | assert.Equal(t, "A: A1; B: B1.", err.Error()) 60 | } 61 | 62 | errs = Errors{} 63 | assert.Nil(t, errs.Filter()) 64 | 65 | errs = Errors{ 66 | "B": nil, 67 | "C": nil, 68 | } 69 | 70 | assert.Nil(t, errs.Filter()) 71 | } 72 | 73 | func TestErrorObject_SetCode(t *testing.T) { 74 | err := NewError("A", "msg").(ErrorObject) 75 | 76 | assert.Equal(t, err.code, "A") 77 | assert.Equal(t, err.Code(), "A") 78 | 79 | err = err.SetCode("B").(ErrorObject) 80 | assert.Equal(t, "B", err.code) 81 | } 82 | 83 | func TestErrorObject_Code(t *testing.T) { 84 | err := NewError("A", "msg").(ErrorObject) 85 | 86 | assert.Equal(t, err.Code(), "A") 87 | } 88 | 89 | func TestErrorObject_SetMessage(t *testing.T) { 90 | err := NewError("code", "A").(ErrorObject) 91 | 92 | assert.Equal(t, err.message, "A") 93 | assert.Equal(t, err.Message(), "A") 94 | 95 | err = err.SetMessage("abc").(ErrorObject) 96 | assert.Equal(t, err.message, "abc") 97 | assert.Equal(t, err.Message(), "abc") 98 | } 99 | 100 | func TestErrorObject_Message(t *testing.T) { 101 | err := NewError("code", "A").(ErrorObject) 102 | 103 | assert.Equal(t, err.message, "A") 104 | assert.Equal(t, err.Message(), "A") 105 | } 106 | 107 | func TestErrorObject_Params(t *testing.T) { 108 | p := map[string]interface{}{"A": "val1", "AA": "val2"} 109 | 110 | err := NewError("code", "A").(ErrorObject) 111 | err = err.SetParams(p).(ErrorObject) 112 | err = err.SetMessage("B").(ErrorObject) 113 | 114 | assert.Equal(t, err.params, p) 115 | assert.Equal(t, err.Params(), p) 116 | } 117 | 118 | func TestErrorObject_AddParam2(t *testing.T) { 119 | p := map[string]interface{}{"key": "val"} 120 | err := NewError("code", "A").(ErrorObject) 121 | err = err.AddParam("key", "val").(ErrorObject) 122 | 123 | assert.Equal(t, err.params, p) 124 | assert.Equal(t, err.Params(), p) 125 | } 126 | 127 | func TestErrorObject_AddParam(t *testing.T) { 128 | p := map[string]interface{}{"A": "val1", "B": "val2"} 129 | 130 | err := NewError("code", "A").(ErrorObject) 131 | err = err.SetParams(p).(ErrorObject) 132 | err = err.AddParam("C", "val3").(ErrorObject) 133 | 134 | p["C"] = "val3" 135 | 136 | assert.Equal(t, err.params, p) 137 | assert.Equal(t, err.Params(), p) 138 | } 139 | 140 | func TestError_Code(t *testing.T) { 141 | err := NewError("A", "msg") 142 | 143 | assert.Equal(t, err.Code(), "A") 144 | } 145 | 146 | func TestError_SetMessage(t *testing.T) { 147 | err := NewError("code", "A") 148 | 149 | assert.Equal(t, err.Message(), "A") 150 | 151 | err = err.SetMessage("abc") 152 | assert.Equal(t, err.Message(), "abc") 153 | } 154 | 155 | func TestError_Message(t *testing.T) { 156 | err := NewError("code", "A") 157 | 158 | assert.Equal(t, err.Message(), "A") 159 | } 160 | 161 | func TestError_Params(t *testing.T) { 162 | p := map[string]interface{}{"A": "val1", "AA": "val2"} 163 | 164 | err := NewError("code", "A") 165 | err = err.SetParams(p) 166 | err = err.SetMessage("B") 167 | 168 | assert.Equal(t, err.Params(), p) 169 | } 170 | 171 | func TestValidationError(t *testing.T) { 172 | params := map[string]interface{}{ 173 | "A": "B", 174 | } 175 | 176 | err := NewError("code", "msg") 177 | err = err.SetParams(params) 178 | 179 | assert.Equal(t, err.Code(), "code") 180 | assert.Equal(t, err.Message(), "msg") 181 | assert.Equal(t, err.Params(), params) 182 | 183 | params = map[string]interface{}{"min": 1} 184 | err = err.SetParams(params) 185 | 186 | assert.Equal(t, err.Params(), params) 187 | } 188 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package validation_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | 9 | "github.com/go-ozzo/ozzo-validation/v4" 10 | "github.com/go-ozzo/ozzo-validation/v4/is" 11 | ) 12 | 13 | type Address struct { 14 | Street string 15 | City string 16 | State string 17 | Zip string 18 | } 19 | 20 | type Customer struct { 21 | Name string 22 | Gender string 23 | Email string 24 | Address Address 25 | } 26 | 27 | func (a Address) Validate() error { 28 | return validation.ValidateStruct(&a, 29 | // Street cannot be empty, and the length must between 5 and 50 30 | validation.Field(&a.Street, validation.Required, validation.Length(5, 50)), 31 | // City cannot be empty, and the length must between 5 and 50 32 | validation.Field(&a.City, validation.Required, validation.Length(5, 50)), 33 | // State cannot be empty, and must be a string consisting of two letters in upper case 34 | validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), 35 | // State cannot be empty, and must be a string consisting of five digits 36 | validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 37 | ) 38 | } 39 | 40 | func (c Customer) Validate() error { 41 | return validation.ValidateStruct(&c, 42 | // Name cannot be empty, and the length must be between 5 and 20. 43 | validation.Field(&c.Name, validation.Required, validation.Length(5, 20)), 44 | // Gender is optional, and should be either "Female" or "Male". 45 | validation.Field(&c.Gender, validation.In("Female", "Male")), 46 | // Email cannot be empty and should be in a valid email format. 47 | validation.Field(&c.Email, validation.Required, is.Email), 48 | // Validate Address using its own validation rules 49 | validation.Field(&c.Address), 50 | ) 51 | } 52 | 53 | func Example() { 54 | c := Customer{ 55 | Name: "Qiang Xue", 56 | Email: "q", 57 | Address: Address{ 58 | Street: "123 Main Street", 59 | City: "Unknown", 60 | State: "Virginia", 61 | Zip: "12345", 62 | }, 63 | } 64 | 65 | err := c.Validate() 66 | fmt.Println(err) 67 | // Output: 68 | // Address: (State: must be in a valid format.); Email: must be a valid email address. 69 | } 70 | 71 | func Example_second() { 72 | data := "example" 73 | err := validation.Validate(data, 74 | validation.Required, // not empty 75 | validation.Length(5, 100), // length between 5 and 100 76 | is.URL, // is a valid URL 77 | ) 78 | fmt.Println(err) 79 | // Output: 80 | // must be a valid URL 81 | } 82 | 83 | func Example_third() { 84 | addresses := []Address{ 85 | {State: "MD", Zip: "12345"}, 86 | {Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"}, 87 | {City: "Unknown", State: "NC", Zip: "123"}, 88 | } 89 | err := validation.Validate(addresses) 90 | fmt.Println(err) 91 | // Output: 92 | // 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.). 93 | } 94 | 95 | func Example_four() { 96 | c := Customer{ 97 | Name: "Qiang Xue", 98 | Email: "q", 99 | Address: Address{ 100 | State: "Virginia", 101 | }, 102 | } 103 | 104 | err := validation.Errors{ 105 | "name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)), 106 | "email": validation.Validate(c.Name, validation.Required, is.Email), 107 | "zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 108 | }.Filter() 109 | fmt.Println(err) 110 | // Output: 111 | // email: must be a valid email address; zip: cannot be blank. 112 | } 113 | 114 | func Example_five() { 115 | type Employee struct { 116 | Name string 117 | } 118 | 119 | type Manager struct { 120 | Employee 121 | Level int 122 | } 123 | 124 | m := Manager{} 125 | err := validation.ValidateStruct(&m, 126 | validation.Field(&m.Name, validation.Required), 127 | validation.Field(&m.Level, validation.Required), 128 | ) 129 | fmt.Println(err) 130 | // Output: 131 | // Level: cannot be blank; Name: cannot be blank. 132 | } 133 | 134 | type contextKey int 135 | 136 | func Example_six() { 137 | key := contextKey(1) 138 | rule := validation.WithContext(func(ctx context.Context, value interface{}) error { 139 | s, _ := value.(string) 140 | if ctx.Value(key) == s { 141 | return nil 142 | } 143 | return errors.New("unexpected value") 144 | }) 145 | ctx := context.WithValue(context.Background(), key, "good sample") 146 | 147 | err1 := validation.ValidateWithContext(ctx, "bad sample", rule) 148 | fmt.Println(err1) 149 | 150 | err2 := validation.ValidateWithContext(ctx, "good sample", rule) 151 | fmt.Println(err2) 152 | 153 | // Output: 154 | // unexpected value 155 | // 156 | } 157 | 158 | func Example_seven() { 159 | c := map[string]interface{}{ 160 | "Name": "Qiang Xue", 161 | "Email": "q", 162 | "Address": map[string]interface{}{ 163 | "Street": "123", 164 | "City": "Unknown", 165 | "State": "Virginia", 166 | "Zip": "12345", 167 | }, 168 | } 169 | 170 | err := validation.Validate(c, 171 | validation.Map( 172 | // Name cannot be empty, and the length must be between 5 and 20. 173 | validation.Key("Name", validation.Required, validation.Length(5, 20)), 174 | // Email cannot be empty and should be in a valid email format. 175 | validation.Key("Email", validation.Required, is.Email), 176 | // Validate Address using its own validation rules 177 | validation.Key("Address", validation.Map( 178 | // Street cannot be empty, and the length must between 5 and 50 179 | validation.Key("Street", validation.Required, validation.Length(5, 50)), 180 | // City cannot be empty, and the length must between 5 and 50 181 | validation.Key("City", validation.Required, validation.Length(5, 50)), 182 | // State cannot be empty, and must be a string consisting of two letters in upper case 183 | validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), 184 | // State cannot be empty, and must be a string consisting of five digits 185 | validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), 186 | )), 187 | ), 188 | ) 189 | fmt.Println(err) 190 | // Output: 191 | // Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address. 192 | } 193 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-ozzo/ozzo-validation/v4 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 7 | github.com/stretchr/testify v1.4.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= 2 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 10 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 14 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 15 | -------------------------------------------------------------------------------- /in.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "reflect" 9 | ) 10 | 11 | // ErrInInvalid is the error that returns in case of an invalid value for "in" rule. 12 | var ErrInInvalid = NewError("validation_in_invalid", "must be a valid value") 13 | 14 | // In returns a validation rule that checks if a value can be found in the given list of values. 15 | // reflect.DeepEqual() will be used to determine if two values are equal. 16 | // For more details please refer to https://golang.org/pkg/reflect/#DeepEqual 17 | // An empty value is considered valid. Use the Required rule to make sure a value is not empty. 18 | func In(values ...interface{}) InRule { 19 | return InRule{ 20 | elements: values, 21 | err: ErrInInvalid, 22 | } 23 | } 24 | 25 | // InRule is a validation rule that validates if a value can be found in the given list of values. 26 | type InRule struct { 27 | elements []interface{} 28 | err Error 29 | } 30 | 31 | // Validate checks if the given value is valid or not. 32 | func (r InRule) Validate(value interface{}) error { 33 | value, isNil := Indirect(value) 34 | if isNil || IsEmpty(value) { 35 | return nil 36 | } 37 | 38 | for _, e := range r.elements { 39 | if reflect.DeepEqual(e, value) { 40 | return nil 41 | } 42 | } 43 | 44 | return r.err 45 | } 46 | 47 | // Error sets the error message for the rule. 48 | func (r InRule) Error(message string) InRule { 49 | r.err = r.err.SetMessage(message) 50 | return r 51 | } 52 | 53 | // ErrorObject sets the error struct for the rule. 54 | func (r InRule) ErrorObject(err Error) InRule { 55 | r.err = err 56 | return r 57 | } 58 | -------------------------------------------------------------------------------- /in_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestIn(t *testing.T) { 14 | var v = 1 15 | var v2 *int 16 | tests := []struct { 17 | tag string 18 | values []interface{} 19 | value interface{} 20 | err string 21 | }{ 22 | {"t0", []interface{}{1, 2}, 0, ""}, 23 | {"t1", []interface{}{1, 2}, 1, ""}, 24 | {"t2", []interface{}{1, 2}, 2, ""}, 25 | {"t3", []interface{}{1, 2}, 3, "must be a valid value"}, 26 | {"t4", []interface{}{}, 3, "must be a valid value"}, 27 | {"t5", []interface{}{1, 2}, "1", "must be a valid value"}, 28 | {"t6", []interface{}{1, 2}, &v, ""}, 29 | {"t7", []interface{}{1, 2}, v2, ""}, 30 | {"t8", []interface{}{[]byte{1}, 1, 2}, []byte{1}, ""}, 31 | } 32 | 33 | for _, test := range tests { 34 | r := In(test.values...) 35 | err := r.Validate(test.value) 36 | assertError(t, test.err, err, test.tag) 37 | } 38 | } 39 | 40 | func Test_InRule_Error(t *testing.T) { 41 | r := In(1, 2, 3) 42 | val := 4 43 | assert.Equal(t, "must be a valid value", r.Validate(&val).Error()) 44 | r = r.Error("123") 45 | assert.Equal(t, "123", r.err.Message()) 46 | } 47 | 48 | func TestInRule_ErrorObject(t *testing.T) { 49 | r := In(1, 2, 3) 50 | 51 | err := NewError("code", "abc") 52 | r = r.ErrorObject(err) 53 | 54 | assert.Equal(t, err, r.err) 55 | assert.Equal(t, err.Code(), r.err.Code()) 56 | assert.Equal(t, err.Message(), r.err.Message()) 57 | } 58 | -------------------------------------------------------------------------------- /is/rules.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package is provides a list of commonly used string validation rules. 6 | package is 7 | 8 | import ( 9 | "regexp" 10 | "unicode" 11 | 12 | "github.com/asaskevich/govalidator" 13 | validation "github.com/go-ozzo/ozzo-validation/v4" 14 | ) 15 | 16 | var ( 17 | // ErrEmail is the error that returns in case of an invalid email. 18 | ErrEmail = validation.NewError("validation_is_email", "must be a valid email address") 19 | // ErrURL is the error that returns in case of an invalid URL. 20 | ErrURL = validation.NewError("validation_is_url", "must be a valid URL") 21 | // ErrRequestURL is the error that returns in case of an invalid request URL. 22 | ErrRequestURL = validation.NewError("validation_is_request_url", "must be a valid request URL") 23 | // ErrRequestURI is the error that returns in case of an invalid request URI. 24 | ErrRequestURI = validation.NewError("validation_request_is_request_uri", "must be a valid request URI") 25 | // ErrAlpha is the error that returns in case of an invalid alpha value. 26 | ErrAlpha = validation.NewError("validation_is_alpha", "must contain English letters only") 27 | // ErrDigit is the error that returns in case of an invalid digit value. 28 | ErrDigit = validation.NewError("validation_is_digit", "must contain digits only") 29 | // ErrAlphanumeric is the error that returns in case of an invalid alphanumeric value. 30 | ErrAlphanumeric = validation.NewError("validation_is_alphanumeric", "must contain English letters and digits only") 31 | // ErrUTFLetter is the error that returns in case of an invalid utf letter value. 32 | ErrUTFLetter = validation.NewError("validation_is_utf_letter", "must contain unicode letter characters only") 33 | // ErrUTFDigit is the error that returns in case of an invalid utf digit value. 34 | ErrUTFDigit = validation.NewError("validation_is_utf_digit", "must contain unicode decimal digits only") 35 | // ErrUTFLetterNumeric is the error that returns in case of an invalid utf numeric or letter value. 36 | ErrUTFLetterNumeric = validation.NewError("validation_is utf_letter_numeric", "must contain unicode letters and numbers only") 37 | // ErrUTFNumeric is the error that returns in case of an invalid utf numeric value. 38 | ErrUTFNumeric = validation.NewError("validation_is_utf_numeric", "must contain unicode number characters only") 39 | // ErrLowerCase is the error that returns in case of an invalid lower case value. 40 | ErrLowerCase = validation.NewError("validation_is_lower_case", "must be in lower case") 41 | // ErrUpperCase is the error that returns in case of an invalid upper case value. 42 | ErrUpperCase = validation.NewError("validation_is_upper_case", "must be in upper case") 43 | // ErrHexadecimal is the error that returns in case of an invalid hexadecimal number. 44 | ErrHexadecimal = validation.NewError("validation_is_hexadecimal", "must be a valid hexadecimal number") 45 | // ErrHexColor is the error that returns in case of an invalid hexadecimal color code. 46 | ErrHexColor = validation.NewError("validation_is_hex_color", "must be a valid hexadecimal color code") 47 | // ErrRGBColor is the error that returns in case of an invalid RGB color code. 48 | ErrRGBColor = validation.NewError("validation_is_rgb_color", "must be a valid RGB color code") 49 | // ErrInt is the error that returns in case of an invalid integer value. 50 | ErrInt = validation.NewError("validation_is_int", "must be an integer number") 51 | // ErrFloat is the error that returns in case of an invalid float value. 52 | ErrFloat = validation.NewError("validation_is_float", "must be a floating point number") 53 | // ErrUUIDv3 is the error that returns in case of an invalid UUIDv3 value. 54 | ErrUUIDv3 = validation.NewError("validation_is_uuid_v3", "must be a valid UUID v3") 55 | // ErrUUIDv4 is the error that returns in case of an invalid UUIDv4 value. 56 | ErrUUIDv4 = validation.NewError("validation_is_uuid_v4", "must be a valid UUID v4") 57 | // ErrUUIDv5 is the error that returns in case of an invalid UUIDv5 value. 58 | ErrUUIDv5 = validation.NewError("validation_is_uuid_v5", "must be a valid UUID v5") 59 | // ErrUUID is the error that returns in case of an invalid UUID value. 60 | ErrUUID = validation.NewError("validation_is_uuid", "must be a valid UUID") 61 | // ErrCreditCard is the error that returns in case of an invalid credit card number. 62 | ErrCreditCard = validation.NewError("validation_is_credit_card", "must be a valid credit card number") 63 | // ErrISBN10 is the error that returns in case of an invalid ISBN-10 value. 64 | ErrISBN10 = validation.NewError("validation_is_isbn_10", "must be a valid ISBN-10") 65 | // ErrISBN13 is the error that returns in case of an invalid ISBN-13 value. 66 | ErrISBN13 = validation.NewError("validation_is_isbn_13", "must be a valid ISBN-13") 67 | // ErrISBN is the error that returns in case of an invalid ISBN value. 68 | ErrISBN = validation.NewError("validation_is_isbn", "must be a valid ISBN") 69 | // ErrJSON is the error that returns in case of an invalid JSON. 70 | ErrJSON = validation.NewError("validation_is_json", "must be in valid JSON format") 71 | // ErrASCII is the error that returns in case of an invalid ASCII. 72 | ErrASCII = validation.NewError("validation_is_ascii", "must contain ASCII characters only") 73 | // ErrPrintableASCII is the error that returns in case of an invalid printable ASCII value. 74 | ErrPrintableASCII = validation.NewError("validation_is_printable_ascii", "must contain printable ASCII characters only") 75 | // ErrMultibyte is the error that returns in case of an invalid multibyte value. 76 | ErrMultibyte = validation.NewError("validation_is_multibyte", "must contain multibyte characters") 77 | // ErrFullWidth is the error that returns in case of an invalid full-width value. 78 | ErrFullWidth = validation.NewError("validation_is_full_width", "must contain full-width characters") 79 | // ErrHalfWidth is the error that returns in case of an invalid half-width value. 80 | ErrHalfWidth = validation.NewError("validation_is_half_width", "must contain half-width characters") 81 | // ErrVariableWidth is the error that returns in case of an invalid variable width value. 82 | ErrVariableWidth = validation.NewError("validation_is_variable_width", "must contain both full-width and half-width characters") 83 | // ErrBase64 is the error that returns in case of an invalid base54 value. 84 | ErrBase64 = validation.NewError("validation_is_base64", "must be encoded in Base64") 85 | // ErrDataURI is the error that returns in case of an invalid data URI. 86 | ErrDataURI = validation.NewError("validation_is_data_uri", "must be a Base64-encoded data URI") 87 | // ErrE164 is the error that returns in case of an invalid e165. 88 | ErrE164 = validation.NewError("validation_is_e164_number", "must be a valid E164 number") 89 | // ErrCountryCode2 is the error that returns in case of an invalid two-letter country code. 90 | ErrCountryCode2 = validation.NewError("validation_is_country_code_2_letter", "must be a valid two-letter country code") 91 | // ErrCountryCode3 is the error that returns in case of an invalid three-letter country code. 92 | ErrCountryCode3 = validation.NewError("validation_is_country_code_3_letter", "must be a valid three-letter country code") 93 | // ErrCurrencyCode is the error that returns in case of an invalid currency code. 94 | ErrCurrencyCode = validation.NewError("validation_is_currency_code", "must be valid ISO 4217 currency code") 95 | // ErrDialString is the error that returns in case of an invalid string. 96 | ErrDialString = validation.NewError("validation_is_dial_string", "must be a valid dial string") 97 | // ErrMac is the error that returns in case of an invalid mac address. 98 | ErrMac = validation.NewError("validation_is_mac_address", "must be a valid MAC address") 99 | // ErrIP is the error that returns in case of an invalid IP. 100 | ErrIP = validation.NewError("validation_is_ip", "must be a valid IP address") 101 | // ErrIPv4 is the error that returns in case of an invalid IPv4. 102 | ErrIPv4 = validation.NewError("validation_is_ipv4", "must be a valid IPv4 address") 103 | // ErrIPv6 is the error that returns in case of an invalid IPv6. 104 | ErrIPv6 = validation.NewError("validation_is_ipv6", "must be a valid IPv6 address") 105 | // ErrSubdomain is the error that returns in case of an invalid subdomain. 106 | ErrSubdomain = validation.NewError("validation_is_sub_domain", "must be a valid subdomain") 107 | // ErrDomain is the error that returns in case of an invalid domain. 108 | ErrDomain = validation.NewError("validation_is_domain", "must be a valid domain") 109 | // ErrDNSName is the error that returns in case of an invalid DNS name. 110 | ErrDNSName = validation.NewError("validation_is_dns_name", "must be a valid DNS name") 111 | // ErrHost is the error that returns in case of an invalid host. 112 | ErrHost = validation.NewError("validation_is_host", "must be a valid IP address or DNS name") 113 | // ErrPort is the error that returns in case of an invalid port. 114 | ErrPort = validation.NewError("validation_is_port", "must be a valid port number") 115 | // ErrMongoID is the error that returns in case of an invalid MongoID. 116 | ErrMongoID = validation.NewError("validation_is_mongo_id", "must be a valid hex-encoded MongoDB ObjectId") 117 | // ErrLatitude is the error that returns in case of an invalid latitude. 118 | ErrLatitude = validation.NewError("validation_is_latitude", "must be a valid latitude") 119 | // ErrLongitude is the error that returns in case of an invalid longitude. 120 | ErrLongitude = validation.NewError("validation_is_longitude", "must be a valid longitude") 121 | // ErrSSN is the error that returns in case of an invalid SSN. 122 | ErrSSN = validation.NewError("validation_is_ssn", "must be a valid social security number") 123 | // ErrSemver is the error that returns in case of an invalid semver. 124 | ErrSemver = validation.NewError("validation_is_semver", "must be a valid semantic version") 125 | ) 126 | 127 | var ( 128 | // Email validates if a string is an email or not. It also checks if the MX record exists for the email domain. 129 | Email = validation.NewStringRuleWithError(govalidator.IsExistingEmail, ErrEmail) 130 | // EmailFormat validates if a string is an email or not. Note that it does NOT check if the MX record exists or not. 131 | EmailFormat = validation.NewStringRuleWithError(govalidator.IsEmail, ErrEmail) 132 | // URL validates if a string is a valid URL 133 | URL = validation.NewStringRuleWithError(govalidator.IsURL, ErrURL) 134 | // RequestURL validates if a string is a valid request URL 135 | RequestURL = validation.NewStringRuleWithError(govalidator.IsRequestURL, ErrRequestURL) 136 | // RequestURI validates if a string is a valid request URI 137 | RequestURI = validation.NewStringRuleWithError(govalidator.IsRequestURI, ErrRequestURI) 138 | // Alpha validates if a string contains English letters only (a-zA-Z) 139 | Alpha = validation.NewStringRuleWithError(govalidator.IsAlpha, ErrAlpha) 140 | // Digit validates if a string contains digits only (0-9) 141 | Digit = validation.NewStringRuleWithError(isDigit, ErrDigit) 142 | // Alphanumeric validates if a string contains English letters and digits only (a-zA-Z0-9) 143 | Alphanumeric = validation.NewStringRuleWithError(govalidator.IsAlphanumeric, ErrAlphanumeric) 144 | // UTFLetter validates if a string contains unicode letters only 145 | UTFLetter = validation.NewStringRuleWithError(govalidator.IsUTFLetter, ErrUTFLetter) 146 | // UTFDigit validates if a string contains unicode decimal digits only 147 | UTFDigit = validation.NewStringRuleWithError(govalidator.IsUTFDigit, ErrUTFDigit) 148 | // UTFLetterNumeric validates if a string contains unicode letters and numbers only 149 | UTFLetterNumeric = validation.NewStringRuleWithError(govalidator.IsUTFLetterNumeric, ErrUTFLetterNumeric) 150 | // UTFNumeric validates if a string contains unicode number characters (category N) only 151 | UTFNumeric = validation.NewStringRuleWithError(isUTFNumeric, ErrUTFNumeric) 152 | // LowerCase validates if a string contains lower case unicode letters only 153 | LowerCase = validation.NewStringRuleWithError(govalidator.IsLowerCase, ErrLowerCase) 154 | // UpperCase validates if a string contains upper case unicode letters only 155 | UpperCase = validation.NewStringRuleWithError(govalidator.IsUpperCase, ErrUpperCase) 156 | // Hexadecimal validates if a string is a valid hexadecimal number 157 | Hexadecimal = validation.NewStringRuleWithError(govalidator.IsHexadecimal, ErrHexadecimal) 158 | // HexColor validates if a string is a valid hexadecimal color code 159 | HexColor = validation.NewStringRuleWithError(govalidator.IsHexcolor, ErrHexColor) 160 | // RGBColor validates if a string is a valid RGB color in the form of rgb(R, G, B) 161 | RGBColor = validation.NewStringRuleWithError(govalidator.IsRGBcolor, ErrRGBColor) 162 | // Int validates if a string is a valid integer number 163 | Int = validation.NewStringRuleWithError(govalidator.IsInt, ErrInt) 164 | // Float validates if a string is a floating point number 165 | Float = validation.NewStringRuleWithError(govalidator.IsFloat, ErrFloat) 166 | // UUIDv3 validates if a string is a valid version 3 UUID 167 | UUIDv3 = validation.NewStringRuleWithError(govalidator.IsUUIDv3, ErrUUIDv3) 168 | // UUIDv4 validates if a string is a valid version 4 UUID 169 | UUIDv4 = validation.NewStringRuleWithError(govalidator.IsUUIDv4, ErrUUIDv4) 170 | // UUIDv5 validates if a string is a valid version 5 UUID 171 | UUIDv5 = validation.NewStringRuleWithError(govalidator.IsUUIDv5, ErrUUIDv5) 172 | // UUID validates if a string is a valid UUID 173 | UUID = validation.NewStringRuleWithError(govalidator.IsUUID, ErrUUID) 174 | // CreditCard validates if a string is a valid credit card number 175 | CreditCard = validation.NewStringRuleWithError(govalidator.IsCreditCard, ErrCreditCard) 176 | // ISBN10 validates if a string is an ISBN version 10 177 | ISBN10 = validation.NewStringRuleWithError(govalidator.IsISBN10, ErrISBN10) 178 | // ISBN13 validates if a string is an ISBN version 13 179 | ISBN13 = validation.NewStringRuleWithError(govalidator.IsISBN13, ErrISBN13) 180 | // ISBN validates if a string is an ISBN (either version 10 or 13) 181 | ISBN = validation.NewStringRuleWithError(isISBN, ErrISBN) 182 | // JSON validates if a string is in valid JSON format 183 | JSON = validation.NewStringRuleWithError(govalidator.IsJSON, ErrJSON) 184 | // ASCII validates if a string contains ASCII characters only 185 | ASCII = validation.NewStringRuleWithError(govalidator.IsASCII, ErrASCII) 186 | // PrintableASCII validates if a string contains printable ASCII characters only 187 | PrintableASCII = validation.NewStringRuleWithError(govalidator.IsPrintableASCII, ErrPrintableASCII) 188 | // Multibyte validates if a string contains multibyte characters 189 | Multibyte = validation.NewStringRuleWithError(govalidator.IsMultibyte, ErrMultibyte) 190 | // FullWidth validates if a string contains full-width characters 191 | FullWidth = validation.NewStringRuleWithError(govalidator.IsFullWidth, ErrFullWidth) 192 | // HalfWidth validates if a string contains half-width characters 193 | HalfWidth = validation.NewStringRuleWithError(govalidator.IsHalfWidth, ErrHalfWidth) 194 | // VariableWidth validates if a string contains both full-width and half-width characters 195 | VariableWidth = validation.NewStringRuleWithError(govalidator.IsVariableWidth, ErrVariableWidth) 196 | // Base64 validates if a string is encoded in Base64 197 | Base64 = validation.NewStringRuleWithError(govalidator.IsBase64, ErrBase64) 198 | // DataURI validates if a string is a valid base64-encoded data URI 199 | DataURI = validation.NewStringRuleWithError(govalidator.IsDataURI, ErrDataURI) 200 | // E164 validates if a string is a valid ISO3166 Alpha 2 country code 201 | E164 = validation.NewStringRuleWithError(isE164Number, ErrE164) 202 | // CountryCode2 validates if a string is a valid ISO3166 Alpha 2 country code 203 | CountryCode2 = validation.NewStringRuleWithError(govalidator.IsISO3166Alpha2, ErrCountryCode2) 204 | // CountryCode3 validates if a string is a valid ISO3166 Alpha 3 country code 205 | CountryCode3 = validation.NewStringRuleWithError(govalidator.IsISO3166Alpha3, ErrCountryCode3) 206 | // CurrencyCode validates if a string is a valid IsISO4217 currency code. 207 | CurrencyCode = validation.NewStringRuleWithError(govalidator.IsISO4217, ErrCurrencyCode) 208 | // DialString validates if a string is a valid dial string that can be passed to Dial() 209 | DialString = validation.NewStringRuleWithError(govalidator.IsDialString, ErrDialString) 210 | // MAC validates if a string is a MAC address 211 | MAC = validation.NewStringRuleWithError(govalidator.IsMAC, ErrMac) 212 | // IP validates if a string is a valid IP address (either version 4 or 6) 213 | IP = validation.NewStringRuleWithError(govalidator.IsIP, ErrIP) 214 | // IPv4 validates if a string is a valid version 4 IP address 215 | IPv4 = validation.NewStringRuleWithError(govalidator.IsIPv4, ErrIPv4) 216 | // IPv6 validates if a string is a valid version 6 IP address 217 | IPv6 = validation.NewStringRuleWithError(govalidator.IsIPv6, ErrIPv6) 218 | // Subdomain validates if a string is valid subdomain 219 | Subdomain = validation.NewStringRuleWithError(isSubdomain, ErrSubdomain) 220 | // Domain validates if a string is valid domain 221 | Domain = validation.NewStringRuleWithError(isDomain, ErrDomain) 222 | // DNSName validates if a string is valid DNS name 223 | DNSName = validation.NewStringRuleWithError(govalidator.IsDNSName, ErrDNSName) 224 | // Host validates if a string is a valid IP (both v4 and v6) or a valid DNS name 225 | Host = validation.NewStringRuleWithError(govalidator.IsHost, ErrHost) 226 | // Port validates if a string is a valid port number 227 | Port = validation.NewStringRuleWithError(govalidator.IsPort, ErrPort) 228 | // MongoID validates if a string is a valid Mongo ID 229 | MongoID = validation.NewStringRuleWithError(govalidator.IsMongoID, ErrMongoID) 230 | // Latitude validates if a string is a valid latitude 231 | Latitude = validation.NewStringRuleWithError(govalidator.IsLatitude, ErrLatitude) 232 | // Longitude validates if a string is a valid longitude 233 | Longitude = validation.NewStringRuleWithError(govalidator.IsLongitude, ErrLongitude) 234 | // SSN validates if a string is a social security number (SSN) 235 | SSN = validation.NewStringRuleWithError(govalidator.IsSSN, ErrSSN) 236 | // Semver validates if a string is a valid semantic version 237 | Semver = validation.NewStringRuleWithError(govalidator.IsSemver, ErrSemver) 238 | ) 239 | 240 | var ( 241 | reDigit = regexp.MustCompile("^[0-9]+$") 242 | // Subdomain regex source: https://stackoverflow.com/a/7933253 243 | reSubdomain = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`) 244 | // E164 regex source: https://stackoverflow.com/a/23299989 245 | reE164 = regexp.MustCompile(`^\+?[1-9]\d{1,14}$`) 246 | // Domain regex source: https://stackoverflow.com/a/7933253 247 | // Slightly modified: Removed 255 max length validation since Go regex does not 248 | // support lookarounds. More info: https://stackoverflow.com/a/38935027 249 | reDomain = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-zA-Z]{1,63}| xn--[a-z0-9]{1,59})$`) 250 | ) 251 | 252 | func isISBN(value string) bool { 253 | return govalidator.IsISBN(value, 10) || govalidator.IsISBN(value, 13) 254 | } 255 | 256 | func isDigit(value string) bool { 257 | return reDigit.MatchString(value) 258 | } 259 | 260 | func isE164Number(value string) bool { 261 | return reE164.MatchString(value) 262 | } 263 | 264 | func isSubdomain(value string) bool { 265 | return reSubdomain.MatchString(value) 266 | } 267 | 268 | func isDomain(value string) bool { 269 | if len(value) > 255 { 270 | return false 271 | } 272 | 273 | return reDomain.MatchString(value) 274 | } 275 | 276 | func isUTFNumeric(value string) bool { 277 | for _, c := range value { 278 | if unicode.IsNumber(c) == false { 279 | return false 280 | } 281 | } 282 | return true 283 | } 284 | -------------------------------------------------------------------------------- /is/rules_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package is 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | 11 | "github.com/go-ozzo/ozzo-validation/v4" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestAll(t *testing.T) { 16 | tests := []struct { 17 | tag string 18 | rule validation.Rule 19 | valid, invalid string 20 | err string 21 | }{ 22 | {"Email", Email, "test@example.com", "example.com", "must be a valid email address"}, 23 | {"EmailFormat", EmailFormat, "test@example.com", "example.com", "must be a valid email address"}, 24 | {"URL", URL, "http://example.com", "examplecom", "must be a valid URL"}, 25 | {"RequestURL", RequestURL, "http://example.com", "examplecom", "must be a valid request URL"}, 26 | {"RequestURI", RequestURI, "http://example.com", "examplecom", "must be a valid request URI"}, 27 | {"Alpha", Alpha, "abcd", "ab12", "must contain English letters only"}, 28 | {"Digit", Digit, "123", "12ab", "must contain digits only"}, 29 | {"Alphanumeric", Alphanumeric, "abc123", "abc.123", "must contain English letters and digits only"}, 30 | {"UTFLetter", UTFLetter, "abc", "123", "must contain unicode letter characters only"}, 31 | {"UTFDigit", UTFDigit, "123", "abc", "must contain unicode decimal digits only"}, 32 | {"UTFNumeric", UTFNumeric, "123", "abc.123", "must contain unicode number characters only"}, 33 | {"UTFLetterNumeric", UTFLetterNumeric, "abc123", "abc.123", "must contain unicode letters and numbers only"}, 34 | {"LowerCase", LowerCase, "abc", "Abc", "must be in lower case"}, 35 | {"UpperCase", UpperCase, "ABC", "ABc", "must be in upper case"}, 36 | {"IP", IP, "74.125.19.99", "74.125.19.999", "must be a valid IP address"}, 37 | {"IPv4", IPv4, "74.125.19.99", "2001:4860:0:2001::68", "must be a valid IPv4 address"}, 38 | {"IPv6", IPv6, "2001:4860:0:2001::68", "74.125.19.99", "must be a valid IPv6 address"}, 39 | {"MAC", MAC, "0123.4567.89ab", "74.125.19.99", "must be a valid MAC address"}, 40 | {"Subdomain", Subdomain, "example-subdomain", "example.com", "must be a valid subdomain"}, 41 | {"Domain", Domain, "example-domain.com", "localhost", "must be a valid domain"}, 42 | {"Domain", Domain, "example-domain.com", strings.Repeat("a", 256), "must be a valid domain"}, 43 | {"DNSName", DNSName, "example.com", "abc%", "must be a valid DNS name"}, 44 | {"Host", Host, "example.com", "abc%", "must be a valid IP address or DNS name"}, 45 | {"Port", Port, "123", "99999", "must be a valid port number"}, 46 | {"Latitude", Latitude, "23.123", "100", "must be a valid latitude"}, 47 | {"Longitude", Longitude, "123.123", "abc", "must be a valid longitude"}, 48 | {"SSN", SSN, "100-00-1000", "100-0001000", "must be a valid social security number"}, 49 | {"Semver", Semver, "1.0.0", "1.0.0.0", "must be a valid semantic version"}, 50 | {"ISBN", ISBN, "1-61729-085-8", "1-61729-085-81", "must be a valid ISBN"}, 51 | {"ISBN10", ISBN10, "1-61729-085-8", "1-61729-085-81", "must be a valid ISBN-10"}, 52 | {"ISBN13", ISBN13, "978-4-87311-368-5", "978-4-87311-368-a", "must be a valid ISBN-13"}, 53 | {"UUID", UUID, "a987fbc9-4bed-3078-cf07-9141ba07c9f1", "a987fbc9-4bed-3078-cf07-9141ba07c9f3a", "must be a valid UUID"}, 54 | {"UUIDv3", UUIDv3, "b987fbc9-4bed-3078-cf07-9141ba07c9f3", "b987fbc9-4bed-4078-cf07-9141ba07c9f3", "must be a valid UUID v3"}, 55 | {"UUIDv4", UUIDv4, "57b73598-8764-4ad0-a76a-679bb6640eb1", "b987fbc9-4bed-3078-cf07-9141ba07c9f3", "must be a valid UUID v4"}, 56 | {"UUIDv5", UUIDv5, "987fbc97-4bed-5078-af07-9141ba07c9f3", "b987fbc9-4bed-3078-cf07-9141ba07c9f3", "must be a valid UUID v5"}, 57 | {"MongoID", MongoID, "507f1f77bcf86cd799439011", "507f1f77bcf86cd79943901", "must be a valid hex-encoded MongoDB ObjectId"}, 58 | {"CreditCard", CreditCard, "375556917985515", "375556917985516", "must be a valid credit card number"}, 59 | {"JSON", JSON, "[1, 2]", "[1, 2,]", "must be in valid JSON format"}, 60 | {"ASCII", ASCII, "abc", "aabc", "must contain ASCII characters only"}, 61 | {"PrintableASCII", PrintableASCII, "abc", "aabc", "must contain printable ASCII characters only"}, 62 | {"E164", E164, "+19251232233", "+00124222333", "must be a valid E164 number"}, 63 | {"CountryCode2", CountryCode2, "US", "XY", "must be a valid two-letter country code"}, 64 | {"CountryCode3", CountryCode3, "USA", "XYZ", "must be a valid three-letter country code"}, 65 | {"CurrencyCode", CurrencyCode, "USD", "USS", "must be valid ISO 4217 currency code"}, 66 | {"DialString", DialString, "localhost.local:1", "localhost.loc:100000", "must be a valid dial string"}, 67 | {"DataURI", DataURI, "data:image/png;base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", "image/gif;base64,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", "must be a Base64-encoded data URI"}, 68 | {"Base64", Base64, "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", "image", "must be encoded in Base64"}, 69 | {"Multibyte", Multibyte, "abc", "abc", "must contain multibyte characters"}, 70 | {"FullWidth", FullWidth, "3ー0", "abc", "must contain full-width characters"}, 71 | {"HalfWidth", HalfWidth, "abc123い", "0011", "must contain half-width characters"}, 72 | {"VariableWidth", VariableWidth, "3ー0123", "abc", "must contain both full-width and half-width characters"}, 73 | {"Hexadecimal", Hexadecimal, "FEF", "FTF", "must be a valid hexadecimal number"}, 74 | {"HexColor", HexColor, "F00", "FTF", "must be a valid hexadecimal color code"}, 75 | {"RGBColor", RGBColor, "rgb(100, 200, 1)", "abc", "must be a valid RGB color code"}, 76 | {"Int", Int, "100", "1.1", "must be an integer number"}, 77 | {"Float", Float, "1.1", "a.1", "must be a floating point number"}, 78 | {"VariableWidth", VariableWidth, "", "", ""}, 79 | } 80 | 81 | for _, test := range tests { 82 | err := test.rule.Validate("") 83 | assert.Nil(t, err, test.tag) 84 | err = test.rule.Validate(test.valid) 85 | assert.Nil(t, err, test.tag) 86 | err = test.rule.Validate(&test.valid) 87 | assert.Nil(t, err, test.tag) 88 | err = test.rule.Validate(test.invalid) 89 | assertError(t, test.err, err, test.tag) 90 | err = test.rule.Validate(&test.invalid) 91 | assertError(t, test.err, err, test.tag) 92 | } 93 | } 94 | 95 | func assertError(t *testing.T, expected string, err error, tag string) { 96 | if expected == "" { 97 | assert.Nil(t, err, tag) 98 | } else if assert.NotNil(t, err, tag) { 99 | assert.Equal(t, expected, err.Error(), tag) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /length.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "unicode/utf8" 9 | ) 10 | 11 | var ( 12 | // ErrLengthTooLong is the error that returns in case of too long length. 13 | ErrLengthTooLong = NewError("validation_length_too_long", "the length must be no more than {{.max}}") 14 | // ErrLengthTooShort is the error that returns in case of too short length. 15 | ErrLengthTooShort = NewError("validation_length_too_short", "the length must be no less than {{.min}}") 16 | // ErrLengthInvalid is the error that returns in case of an invalid length. 17 | ErrLengthInvalid = NewError("validation_length_invalid", "the length must be exactly {{.min}}") 18 | // ErrLengthOutOfRange is the error that returns in case of out of range length. 19 | ErrLengthOutOfRange = NewError("validation_length_out_of_range", "the length must be between {{.min}} and {{.max}}") 20 | // ErrLengthEmptyRequired is the error that returns in case of non-empty value. 21 | ErrLengthEmptyRequired = NewError("validation_length_empty_required", "the value must be empty") 22 | ) 23 | 24 | // Length returns a validation rule that checks if a value's length is within the specified range. 25 | // If max is 0, it means there is no upper bound for the length. 26 | // This rule should only be used for validating strings, slices, maps, and arrays. 27 | // An empty value is considered valid. Use the Required rule to make sure a value is not empty. 28 | func Length(min, max int) LengthRule { 29 | return LengthRule{min: min, max: max, err: buildLengthRuleError(min, max)} 30 | } 31 | 32 | // RuneLength returns a validation rule that checks if a string's rune length is within the specified range. 33 | // If max is 0, it means there is no upper bound for the length. 34 | // This rule should only be used for validating strings, slices, maps, and arrays. 35 | // An empty value is considered valid. Use the Required rule to make sure a value is not empty. 36 | // If the value being validated is not a string, the rule works the same as Length. 37 | func RuneLength(min, max int) LengthRule { 38 | r := Length(min, max) 39 | r.rune = true 40 | 41 | return r 42 | } 43 | 44 | // LengthRule is a validation rule that checks if a value's length is within the specified range. 45 | type LengthRule struct { 46 | err Error 47 | 48 | min, max int 49 | rune bool 50 | } 51 | 52 | // Validate checks if the given value is valid or not. 53 | func (r LengthRule) Validate(value interface{}) error { 54 | value, isNil := Indirect(value) 55 | if isNil || IsEmpty(value) { 56 | return nil 57 | } 58 | 59 | var ( 60 | l int 61 | err error 62 | ) 63 | if s, ok := value.(string); ok && r.rune { 64 | l = utf8.RuneCountInString(s) 65 | } else if l, err = LengthOfValue(value); err != nil { 66 | return err 67 | } 68 | 69 | if r.min > 0 && l < r.min || r.max > 0 && l > r.max || r.min == 0 && r.max == 0 && l > 0 { 70 | return r.err 71 | } 72 | 73 | return nil 74 | } 75 | 76 | // Error sets the error message for the rule. 77 | func (r LengthRule) Error(message string) LengthRule { 78 | r.err = r.err.SetMessage(message) 79 | return r 80 | } 81 | 82 | // ErrorObject sets the error struct for the rule. 83 | func (r LengthRule) ErrorObject(err Error) LengthRule { 84 | r.err = err 85 | return r 86 | } 87 | 88 | func buildLengthRuleError(min, max int) (err Error) { 89 | if min == 0 && max > 0 { 90 | err = ErrLengthTooLong 91 | } else if min > 0 && max == 0 { 92 | err = ErrLengthTooShort 93 | } else if min > 0 && max > 0 { 94 | if min == max { 95 | err = ErrLengthInvalid 96 | } else { 97 | err = ErrLengthOutOfRange 98 | } 99 | } else { 100 | err = ErrLengthEmptyRequired 101 | } 102 | 103 | return err.SetParams(map[string]interface{}{"min": min, "max": max}) 104 | } 105 | -------------------------------------------------------------------------------- /length_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "database/sql" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestLength(t *testing.T) { 15 | var v *string 16 | tests := []struct { 17 | tag string 18 | min, max int 19 | value interface{} 20 | err string 21 | }{ 22 | {"t1", 2, 4, "abc", ""}, 23 | {"t2", 2, 4, "", ""}, 24 | {"t3", 2, 4, "abcdf", "the length must be between 2 and 4"}, 25 | {"t4", 0, 4, "ab", ""}, 26 | {"t5", 0, 4, "abcde", "the length must be no more than 4"}, 27 | {"t6", 2, 0, "ab", ""}, 28 | {"t7", 2, 0, "a", "the length must be no less than 2"}, 29 | {"t8", 2, 0, v, ""}, 30 | {"t9", 2, 0, 123, "cannot get the length of int"}, 31 | {"t10", 2, 4, sql.NullString{String: "abc", Valid: true}, ""}, 32 | {"t11", 2, 4, sql.NullString{String: "", Valid: true}, ""}, 33 | {"t12", 2, 4, &sql.NullString{String: "abc", Valid: true}, ""}, 34 | {"t13", 2, 2, "abcdf", "the length must be exactly 2"}, 35 | {"t14", 2, 2, "ab", ""}, 36 | {"t15", 0, 0, "", ""}, 37 | {"t16", 0, 0, "ab", "the value must be empty"}, 38 | } 39 | 40 | for _, test := range tests { 41 | r := Length(test.min, test.max) 42 | err := r.Validate(test.value) 43 | assertError(t, test.err, err, test.tag) 44 | } 45 | } 46 | 47 | func TestRuneLength(t *testing.T) { 48 | var v *string 49 | tests := []struct { 50 | tag string 51 | min, max int 52 | value interface{} 53 | err string 54 | }{ 55 | {"t1", 2, 4, "abc", ""}, 56 | {"t1.1", 2, 3, "💥💥", ""}, 57 | {"t1.2", 2, 3, "💥💥💥", ""}, 58 | {"t1.3", 2, 3, "💥", "the length must be between 2 and 3"}, 59 | {"t1.4", 2, 3, "💥💥💥💥", "the length must be between 2 and 3"}, 60 | {"t2", 2, 4, "", ""}, 61 | {"t3", 2, 4, "abcdf", "the length must be between 2 and 4"}, 62 | {"t4", 0, 4, "ab", ""}, 63 | {"t5", 0, 4, "abcde", "the length must be no more than 4"}, 64 | {"t6", 2, 0, "ab", ""}, 65 | {"t7", 2, 0, "a", "the length must be no less than 2"}, 66 | {"t8", 2, 0, v, ""}, 67 | {"t9", 2, 0, 123, "cannot get the length of int"}, 68 | {"t10", 2, 4, sql.NullString{String: "abc", Valid: true}, ""}, 69 | {"t11", 2, 4, sql.NullString{String: "", Valid: true}, ""}, 70 | {"t12", 2, 4, &sql.NullString{String: "abc", Valid: true}, ""}, 71 | {"t13", 2, 3, &sql.NullString{String: "💥💥", Valid: true}, ""}, 72 | {"t14", 2, 3, &sql.NullString{String: "💥", Valid: true}, "the length must be between 2 and 3"}, 73 | } 74 | 75 | for _, test := range tests { 76 | r := RuneLength(test.min, test.max) 77 | err := r.Validate(test.value) 78 | assertError(t, test.err, err, test.tag) 79 | } 80 | } 81 | 82 | func Test_LengthRule_Error(t *testing.T) { 83 | r := Length(10, 20) 84 | assert.Equal(t, "the length must be between 10 and 20", r.Validate("abc").Error()) 85 | 86 | r = Length(0, 20) 87 | assert.Equal(t, "the length must be no more than 20", r.Validate(make([]string, 21)).Error()) 88 | 89 | r = Length(10, 0) 90 | assert.Equal(t, "the length must be no less than 10", r.Validate([9]string{}).Error()) 91 | 92 | r = Length(0, 0) 93 | assert.Equal(t, "validation_length_empty_required", r.err.Code()) 94 | 95 | r = r.Error("123") 96 | assert.Equal(t, "123", r.err.Message()) 97 | } 98 | 99 | func TestLengthRule_ErrorObject(t *testing.T) { 100 | r := Length(10, 20) 101 | err := NewError("code", "abc") 102 | r = r.ErrorObject(err) 103 | 104 | assert.Equal(t, err, r.err) 105 | assert.Equal(t, err.Code(), r.err.Code()) 106 | assert.Equal(t, err.Message(), r.err.Message()) 107 | } 108 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | ) 9 | 10 | var ( 11 | // ErrNotMap is the error that the value being validated is not a map. 12 | ErrNotMap = errors.New("only a map can be validated") 13 | 14 | // ErrKeyWrongType is the error returned in case of an incorrect key type. 15 | ErrKeyWrongType = NewError("validation_key_wrong_type", "key not the correct type") 16 | 17 | // ErrKeyMissing is the error returned in case of a missing key. 18 | ErrKeyMissing = NewError("validation_key_missing", "required key is missing") 19 | 20 | // ErrKeyUnexpected is the error returned in case of an unexpected key. 21 | ErrKeyUnexpected = NewError("validation_key_unexpected", "key not expected") 22 | ) 23 | 24 | type ( 25 | // MapRule represents a rule set associated with a map. 26 | MapRule struct { 27 | keys []*KeyRules 28 | allowExtraKeys bool 29 | } 30 | 31 | // KeyRules represents a rule set associated with a map key. 32 | KeyRules struct { 33 | key interface{} 34 | optional bool 35 | rules []Rule 36 | } 37 | ) 38 | 39 | // Map returns a validation rule that checks the keys and values of a map. 40 | // This rule should only be used for validating maps, or a validation error will be reported. 41 | // Use Key() to specify map keys that need to be validated. Each Key() call specifies a single key which can 42 | // be associated with multiple rules. 43 | // For example, 44 | // validation.Map( 45 | // validation.Key("Name", validation.Required), 46 | // validation.Key("Value", validation.Required, validation.Length(5, 10)), 47 | // ) 48 | // 49 | // A nil value is considered valid. Use the Required rule to make sure a map value is present. 50 | func Map(keys ...*KeyRules) MapRule { 51 | return MapRule{keys: keys} 52 | } 53 | 54 | // AllowExtraKeys configures the rule to ignore extra keys. 55 | func (r MapRule) AllowExtraKeys() MapRule { 56 | r.allowExtraKeys = true 57 | return r 58 | } 59 | 60 | // Validate checks if the given value is valid or not. 61 | func (r MapRule) Validate(m interface{}) error { 62 | return r.ValidateWithContext(nil, m) 63 | } 64 | 65 | // ValidateWithContext checks if the given value is valid or not. 66 | func (r MapRule) ValidateWithContext(ctx context.Context, m interface{}) error { 67 | value := reflect.ValueOf(m) 68 | if value.Kind() == reflect.Ptr { 69 | value = value.Elem() 70 | } 71 | if value.Kind() != reflect.Map { 72 | // must be a map 73 | return NewInternalError(ErrNotMap) 74 | } 75 | if value.IsNil() { 76 | // treat a nil map as valid 77 | return nil 78 | } 79 | 80 | errs := Errors{} 81 | kt := value.Type().Key() 82 | 83 | var extraKeys map[interface{}]bool 84 | if !r.allowExtraKeys { 85 | extraKeys = make(map[interface{}]bool, value.Len()) 86 | for _, k := range value.MapKeys() { 87 | extraKeys[k.Interface()] = true 88 | } 89 | } 90 | 91 | for _, kr := range r.keys { 92 | var err error 93 | if kv := reflect.ValueOf(kr.key); !kt.AssignableTo(kv.Type()) { 94 | err = ErrKeyWrongType 95 | } else if vv := value.MapIndex(kv); !vv.IsValid() { 96 | if !kr.optional { 97 | err = ErrKeyMissing 98 | } 99 | } else if ctx == nil { 100 | err = Validate(vv.Interface(), kr.rules...) 101 | } else { 102 | err = ValidateWithContext(ctx, vv.Interface(), kr.rules...) 103 | } 104 | if err != nil { 105 | if ie, ok := err.(InternalError); ok && ie.InternalError() != nil { 106 | return err 107 | } 108 | errs[getErrorKeyName(kr.key)] = err 109 | } 110 | if !r.allowExtraKeys { 111 | delete(extraKeys, kr.key) 112 | } 113 | } 114 | 115 | if !r.allowExtraKeys { 116 | for key := range extraKeys { 117 | errs[getErrorKeyName(key)] = ErrKeyUnexpected 118 | } 119 | } 120 | 121 | if len(errs) > 0 { 122 | return errs 123 | } 124 | return nil 125 | } 126 | 127 | // Key specifies a map key and the corresponding validation rules. 128 | func Key(key interface{}, rules ...Rule) *KeyRules { 129 | return &KeyRules{ 130 | key: key, 131 | rules: rules, 132 | } 133 | } 134 | 135 | // Optional configures the rule to ignore the key if missing. 136 | func (r *KeyRules) Optional() *KeyRules { 137 | r.optional = true 138 | return r 139 | } 140 | 141 | // getErrorKeyName returns the name that should be used to represent the validation error of a map key. 142 | func getErrorKeyName(key interface{}) string { 143 | return fmt.Sprintf("%v", key) 144 | } 145 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMap(t *testing.T) { 11 | var m0 map[string]interface{} 12 | m1 := map[string]interface{}{"A": "abc", "B": "xyz", "c": "abc", "D": (*string)(nil), "F": (*String123)(nil), "H": []string{"abc", "abc"}, "I": map[string]string{"foo": "abc"}} 13 | m2 := map[string]interface{}{"E": String123("xyz"), "F": (*String123)(nil)} 14 | m3 := map[string]interface{}{"M3": Model3{}} 15 | m4 := map[string]interface{}{"M3": Model3{A: "abc"}} 16 | m5 := map[string]interface{}{"A": "internal", "B": ""} 17 | m6 := map[int]string{11: "abc", 22: "xyz"} 18 | tests := []struct { 19 | tag string 20 | model interface{} 21 | rules []*KeyRules 22 | err string 23 | }{ 24 | // empty rules 25 | {"t1.1", m1, []*KeyRules{}, ""}, 26 | {"t1.2", m1, []*KeyRules{Key("A"), Key("B")}, ""}, 27 | // normal rules 28 | {"t2.1", m1, []*KeyRules{Key("A", &validateAbc{}), Key("B", &validateXyz{})}, ""}, 29 | {"t2.2", m1, []*KeyRules{Key("A", &validateXyz{}), Key("B", &validateAbc{})}, "A: error xyz; B: error abc."}, 30 | {"t2.3", m1, []*KeyRules{Key("A", &validateXyz{}), Key("c", &validateXyz{})}, "A: error xyz; c: error xyz."}, 31 | {"t2.4", m1, []*KeyRules{Key("D", Length(0, 5))}, ""}, 32 | {"t2.5", m1, []*KeyRules{Key("F", Length(0, 5))}, ""}, 33 | {"t2.6", m1, []*KeyRules{Key("H", Each(&validateAbc{})), Key("I", Each(&validateAbc{}))}, ""}, 34 | {"t2.7", m1, []*KeyRules{Key("H", Each(&validateXyz{})), Key("I", Each(&validateXyz{}))}, "H: (0: error xyz; 1: error xyz.); I: (foo: error xyz.)."}, 35 | {"t2.8", m1, []*KeyRules{Key("I", Map(Key("foo", &validateAbc{})))}, ""}, 36 | {"t2.9", m1, []*KeyRules{Key("I", Map(Key("foo", &validateXyz{})))}, "I: (foo: error xyz.)."}, 37 | // non-map value 38 | {"t3.1", &m1, []*KeyRules{}, ""}, 39 | {"t3.2", nil, []*KeyRules{}, ErrNotMap.Error()}, 40 | {"t3.3", m0, []*KeyRules{}, ""}, 41 | {"t3.4", &m0, []*KeyRules{}, ""}, 42 | {"t3.5", 123, []*KeyRules{}, ErrNotMap.Error()}, 43 | // invalid key spec 44 | {"t4.1", m1, []*KeyRules{Key(123)}, "123: key not the correct type."}, 45 | {"t4.2", m1, []*KeyRules{Key("X")}, "X: required key is missing."}, 46 | {"t4.3", m1, []*KeyRules{Key("X").Optional()}, ""}, 47 | // non-string keys 48 | {"t5.1", m6, []*KeyRules{Key(11, &validateAbc{}), Key(22, &validateXyz{})}, ""}, 49 | {"t5.2", m6, []*KeyRules{Key(11, &validateXyz{}), Key(22, &validateAbc{})}, "11: error xyz; 22: error abc."}, 50 | // validatable value 51 | {"t6.1", m2, []*KeyRules{Key("E")}, "E: error 123."}, 52 | {"t6.2", m2, []*KeyRules{Key("E", Skip)}, ""}, 53 | {"t6.3", m2, []*KeyRules{Key("E", Skip.When(true))}, ""}, 54 | {"t6.4", m2, []*KeyRules{Key("E", Skip.When(false))}, "E: error 123."}, 55 | // Required, NotNil 56 | {"t7.1", m2, []*KeyRules{Key("F", Required)}, "F: cannot be blank."}, 57 | {"t7.2", m2, []*KeyRules{Key("F", NotNil)}, "F: is required."}, 58 | {"t7.3", m2, []*KeyRules{Key("F", Skip, Required)}, ""}, 59 | {"t7.4", m2, []*KeyRules{Key("F", Skip, NotNil)}, ""}, 60 | {"t7.5", m2, []*KeyRules{Key("F", Skip.When(true), Required)}, ""}, 61 | {"t7.6", m2, []*KeyRules{Key("F", Skip.When(true), NotNil)}, ""}, 62 | {"t7.7", m2, []*KeyRules{Key("F", Skip.When(false), Required)}, "F: cannot be blank."}, 63 | {"t7.8", m2, []*KeyRules{Key("F", Skip.When(false), NotNil)}, "F: is required."}, 64 | // validatable structs 65 | {"t8.1", m3, []*KeyRules{Key("M3", Skip)}, ""}, 66 | {"t8.2", m3, []*KeyRules{Key("M3")}, "M3: (A: error abc.)."}, 67 | {"t8.3", m4, []*KeyRules{Key("M3")}, ""}, 68 | // internal error 69 | {"t9.1", m5, []*KeyRules{Key("A", &validateAbc{}), Key("B", Required), Key("A", &validateInternalError{})}, "error internal"}, 70 | } 71 | for _, test := range tests { 72 | err1 := Validate(test.model, Map(test.rules...).AllowExtraKeys()) 73 | err2 := ValidateWithContext(context.Background(), test.model, Map(test.rules...).AllowExtraKeys()) 74 | assertError(t, test.err, err1, test.tag) 75 | assertError(t, test.err, err2, test.tag) 76 | } 77 | 78 | a := map[string]interface{}{"Name": "name", "Value": "demo", "Extra": true} 79 | err := Validate(a, Map( 80 | Key("Name", Required), 81 | Key("Value", Required, Length(5, 10)), 82 | )) 83 | assert.EqualError(t, err, "Extra: key not expected; Value: the length must be between 5 and 10.") 84 | } 85 | 86 | func TestMapWithContext(t *testing.T) { 87 | m1 := map[string]interface{}{"A": "abc", "B": "xyz", "c": "abc", "g": "xyz"} 88 | m2 := map[string]interface{}{"A": "internal", "B": ""} 89 | tests := []struct { 90 | tag string 91 | model interface{} 92 | rules []*KeyRules 93 | err string 94 | }{ 95 | // normal rules 96 | {"t1.1", m1, []*KeyRules{Key("A", &validateContextAbc{}), Key("B", &validateContextXyz{})}, ""}, 97 | {"t1.2", m1, []*KeyRules{Key("A", &validateContextXyz{}), Key("B", &validateContextAbc{})}, "A: error xyz; B: error abc."}, 98 | {"t1.3", m1, []*KeyRules{Key("A", &validateContextXyz{}), Key("c", &validateContextXyz{})}, "A: error xyz; c: error xyz."}, 99 | {"t1.4", m1, []*KeyRules{Key("g", &validateContextAbc{})}, "g: error abc."}, 100 | // skip rule 101 | {"t2.1", m1, []*KeyRules{Key("g", Skip, &validateContextAbc{})}, ""}, 102 | {"t2.2", m1, []*KeyRules{Key("g", &validateContextAbc{}, Skip)}, "g: error abc."}, 103 | // internal error 104 | {"t3.1", m2, []*KeyRules{Key("A", &validateContextAbc{}), Key("B", Required), Key("A", &validateInternalError{})}, "error internal"}, 105 | } 106 | for _, test := range tests { 107 | err := ValidateWithContext(context.Background(), test.model, Map(test.rules...).AllowExtraKeys()) 108 | assertError(t, test.err, err, test.tag) 109 | } 110 | 111 | a := map[string]interface{}{"Name": "name", "Value": "demo", "Extra": true} 112 | err := ValidateWithContext(context.Background(), a, Map( 113 | Key("Name", Required), 114 | Key("Value", Required, Length(5, 10)), 115 | )) 116 | if assert.NotNil(t, err) { 117 | assert.Equal(t, "Extra: key not expected; Value: the length must be between 5 and 10.", err.Error()) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /match.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "regexp" 9 | ) 10 | 11 | // ErrMatchInvalid is the error that returns in case of invalid format. 12 | var ErrMatchInvalid = NewError("validation_match_invalid", "must be in a valid format") 13 | 14 | // Match returns a validation rule that checks if a value matches the specified regular expression. 15 | // This rule should only be used for validating strings and byte slices, or a validation error will be reported. 16 | // An empty value is considered valid. Use the Required rule to make sure a value is not empty. 17 | func Match(re *regexp.Regexp) MatchRule { 18 | return MatchRule{ 19 | re: re, 20 | err: ErrMatchInvalid, 21 | } 22 | } 23 | 24 | // MatchRule is a validation rule that checks if a value matches the specified regular expression. 25 | type MatchRule struct { 26 | re *regexp.Regexp 27 | err Error 28 | } 29 | 30 | // Validate checks if the given value is valid or not. 31 | func (r MatchRule) Validate(value interface{}) error { 32 | value, isNil := Indirect(value) 33 | if isNil { 34 | return nil 35 | } 36 | 37 | isString, str, isBytes, bs := StringOrBytes(value) 38 | if isString && (str == "" || r.re.MatchString(str)) { 39 | return nil 40 | } else if isBytes && (len(bs) == 0 || r.re.Match(bs)) { 41 | return nil 42 | } 43 | return r.err 44 | } 45 | 46 | // Error sets the error message for the rule. 47 | func (r MatchRule) Error(message string) MatchRule { 48 | r.err = r.err.SetMessage(message) 49 | return r 50 | } 51 | 52 | // ErrorObject sets the error struct for the rule. 53 | func (r MatchRule) ErrorObject(err Error) MatchRule { 54 | r.err = err 55 | return r 56 | } 57 | -------------------------------------------------------------------------------- /match_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "regexp" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestMatch(t *testing.T) { 15 | var v2 *string 16 | tests := []struct { 17 | tag string 18 | re string 19 | value interface{} 20 | err string 21 | }{ 22 | {"t1", "[a-z]+", "abc", ""}, 23 | {"t2", "[a-z]+", "", ""}, 24 | {"t3", "[a-z]+", v2, ""}, 25 | {"t4", "[a-z]+", "123", "must be in a valid format"}, 26 | {"t5", "[a-z]+", []byte("abc"), ""}, 27 | {"t6", "[a-z]+", []byte("123"), "must be in a valid format"}, 28 | {"t7", "[a-z]+", []byte(""), ""}, 29 | {"t8", "[a-z]+", nil, ""}, 30 | } 31 | 32 | for _, test := range tests { 33 | r := Match(regexp.MustCompile(test.re)) 34 | err := r.Validate(test.value) 35 | assertError(t, test.err, err, test.tag) 36 | } 37 | } 38 | 39 | func Test_MatchRule_Error(t *testing.T) { 40 | r := Match(regexp.MustCompile("[a-z]+")) 41 | assert.Equal(t, "must be in a valid format", r.Validate("13").Error()) 42 | r = r.Error("123") 43 | assert.Equal(t, "123", r.err.Message()) 44 | } 45 | 46 | func TestMatchRule_ErrorObject(t *testing.T) { 47 | r := Match(regexp.MustCompile("[a-z]+")) 48 | 49 | err := NewError("code", "abc") 50 | r = r.ErrorObject(err) 51 | 52 | assert.Equal(t, err, r.err) 53 | assert.Equal(t, err.Code(), r.err.Code()) 54 | assert.Equal(t, err.Message(), r.err.Message()) 55 | } 56 | -------------------------------------------------------------------------------- /minmax.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "time" 11 | ) 12 | 13 | var ( 14 | // ErrMinGreaterEqualThanRequired is the error that returns when a value is less than a specified threshold. 15 | ErrMinGreaterEqualThanRequired = NewError("validation_min_greater_equal_than_required", "must be no less than {{.threshold}}") 16 | // ErrMaxLessEqualThanRequired is the error that returns when a value is greater than a specified threshold. 17 | ErrMaxLessEqualThanRequired = NewError("validation_max_less_equal_than_required", "must be no greater than {{.threshold}}") 18 | // ErrMinGreaterThanRequired is the error that returns when a value is less than or equal to a specified threshold. 19 | ErrMinGreaterThanRequired = NewError("validation_min_greater_than_required", "must be greater than {{.threshold}}") 20 | // ErrMaxLessThanRequired is the error that returns when a value is greater than or equal to a specified threshold. 21 | ErrMaxLessThanRequired = NewError("validation_max_less_than_required", "must be less than {{.threshold}}") 22 | ) 23 | 24 | // ThresholdRule is a validation rule that checks if a value satisfies the specified threshold requirement. 25 | type ThresholdRule struct { 26 | threshold interface{} 27 | operator int 28 | err Error 29 | } 30 | 31 | const ( 32 | greaterThan = iota 33 | greaterEqualThan 34 | lessThan 35 | lessEqualThan 36 | ) 37 | 38 | // Min returns a validation rule that checks if a value is greater or equal than the specified value. 39 | // By calling Exclusive, the rule will check if the value is strictly greater than the specified value. 40 | // Note that the value being checked and the threshold value must be of the same type. 41 | // Only int, uint, float and time.Time types are supported. 42 | // An empty value is considered valid. Please use the Required rule to make sure a value is not empty. 43 | func Min(min interface{}) ThresholdRule { 44 | return ThresholdRule{ 45 | threshold: min, 46 | operator: greaterEqualThan, 47 | err: ErrMinGreaterEqualThanRequired, 48 | } 49 | 50 | } 51 | 52 | // Max returns a validation rule that checks if a value is less or equal than the specified value. 53 | // By calling Exclusive, the rule will check if the value is strictly less than the specified value. 54 | // Note that the value being checked and the threshold value must be of the same type. 55 | // Only int, uint, float and time.Time types are supported. 56 | // An empty value is considered valid. Please use the Required rule to make sure a value is not empty. 57 | func Max(max interface{}) ThresholdRule { 58 | return ThresholdRule{ 59 | threshold: max, 60 | operator: lessEqualThan, 61 | err: ErrMaxLessEqualThanRequired, 62 | } 63 | } 64 | 65 | // Exclusive sets the comparison to exclude the boundary value. 66 | func (r ThresholdRule) Exclusive() ThresholdRule { 67 | if r.operator == greaterEqualThan { 68 | r.operator = greaterThan 69 | r.err = ErrMinGreaterThanRequired 70 | } else if r.operator == lessEqualThan { 71 | r.operator = lessThan 72 | r.err = ErrMaxLessThanRequired 73 | } 74 | return r 75 | } 76 | 77 | // Validate checks if the given value is valid or not. 78 | func (r ThresholdRule) Validate(value interface{}) error { 79 | value, isNil := Indirect(value) 80 | if isNil || IsEmpty(value) { 81 | return nil 82 | } 83 | 84 | rv := reflect.ValueOf(r.threshold) 85 | switch rv.Kind() { 86 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 87 | v, err := ToInt(value) 88 | if err != nil { 89 | return err 90 | } 91 | if r.compareInt(rv.Int(), v) { 92 | return nil 93 | } 94 | 95 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 96 | v, err := ToUint(value) 97 | if err != nil { 98 | return err 99 | } 100 | if r.compareUint(rv.Uint(), v) { 101 | return nil 102 | } 103 | 104 | case reflect.Float32, reflect.Float64: 105 | v, err := ToFloat(value) 106 | if err != nil { 107 | return err 108 | } 109 | if r.compareFloat(rv.Float(), v) { 110 | return nil 111 | } 112 | 113 | case reflect.Struct: 114 | t, ok := r.threshold.(time.Time) 115 | if !ok { 116 | return fmt.Errorf("type not supported: %v", rv.Type()) 117 | } 118 | v, ok := value.(time.Time) 119 | if !ok { 120 | return fmt.Errorf("cannot convert %v to time.Time", reflect.TypeOf(value)) 121 | } 122 | if v.IsZero() || r.compareTime(t, v) { 123 | return nil 124 | } 125 | 126 | default: 127 | return fmt.Errorf("type not supported: %v", rv.Type()) 128 | } 129 | 130 | return r.err.SetParams(map[string]interface{}{"threshold": r.threshold}) 131 | } 132 | 133 | // Error sets the error message for the rule. 134 | func (r ThresholdRule) Error(message string) ThresholdRule { 135 | r.err = r.err.SetMessage(message) 136 | return r 137 | } 138 | 139 | // ErrorObject sets the error struct for the rule. 140 | func (r ThresholdRule) ErrorObject(err Error) ThresholdRule { 141 | r.err = err 142 | return r 143 | } 144 | 145 | func (r ThresholdRule) compareInt(threshold, value int64) bool { 146 | switch r.operator { 147 | case greaterThan: 148 | return value > threshold 149 | case greaterEqualThan: 150 | return value >= threshold 151 | case lessThan: 152 | return value < threshold 153 | default: 154 | return value <= threshold 155 | } 156 | } 157 | 158 | func (r ThresholdRule) compareUint(threshold, value uint64) bool { 159 | switch r.operator { 160 | case greaterThan: 161 | return value > threshold 162 | case greaterEqualThan: 163 | return value >= threshold 164 | case lessThan: 165 | return value < threshold 166 | default: 167 | return value <= threshold 168 | } 169 | } 170 | 171 | func (r ThresholdRule) compareFloat(threshold, value float64) bool { 172 | switch r.operator { 173 | case greaterThan: 174 | return value > threshold 175 | case greaterEqualThan: 176 | return value >= threshold 177 | case lessThan: 178 | return value < threshold 179 | default: 180 | return value <= threshold 181 | } 182 | } 183 | 184 | func (r ThresholdRule) compareTime(threshold, value time.Time) bool { 185 | switch r.operator { 186 | case greaterThan: 187 | return value.After(threshold) 188 | case greaterEqualThan: 189 | return value.After(threshold) || value.Equal(threshold) 190 | case lessThan: 191 | return value.Before(threshold) 192 | default: 193 | return value.Before(threshold) || value.Equal(threshold) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /minmax_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestMin(t *testing.T) { 15 | date0 := time.Time{} 16 | date20000101 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 17 | date20001201 := time.Date(2000, 12, 1, 0, 0, 0, 0, time.UTC) 18 | date20000601 := time.Date(2000, 6, 1, 0, 0, 0, 0, time.UTC) 19 | 20 | tests := []struct { 21 | tag string 22 | threshold interface{} 23 | exclusive bool 24 | value interface{} 25 | err string 26 | }{ 27 | // int cases 28 | {"t1.1", 1, false, 1, ""}, 29 | {"t1.2", 1, false, 2, ""}, 30 | {"t1.3", 1, false, -1, "must be no less than 1"}, 31 | {"t1.4", 1, false, 0, ""}, 32 | {"t1.5", 1, true, 1, "must be greater than 1"}, 33 | {"t1.6", 1, false, "1", "cannot convert string to int64"}, 34 | {"t1.7", "1", false, 1, "type not supported: string"}, 35 | // uint cases 36 | {"t2.1", uint(2), false, uint(2), ""}, 37 | {"t2.2", uint(2), false, uint(3), ""}, 38 | {"t2.3", uint(2), false, uint(1), "must be no less than 2"}, 39 | {"t2.4", uint(2), false, uint(0), ""}, 40 | {"t2.5", uint(2), true, uint(2), "must be greater than 2"}, 41 | {"t2.6", uint(2), false, "1", "cannot convert string to uint64"}, 42 | // float cases 43 | {"t3.1", float64(2), false, float64(2), ""}, 44 | {"t3.2", float64(2), false, float64(3), ""}, 45 | {"t3.3", float64(2), false, float64(1), "must be no less than 2"}, 46 | {"t3.4", float64(2), false, float64(0), ""}, 47 | {"t3.5", float64(2), true, float64(2), "must be greater than 2"}, 48 | {"t3.6", float64(2), false, "1", "cannot convert string to float64"}, 49 | // Time cases 50 | {"t4.1", date20000601, false, date20000601, ""}, 51 | {"t4.2", date20000601, false, date20001201, ""}, 52 | {"t4.3", date20000601, false, date20000101, "must be no less than 2000-06-01 00:00:00 +0000 UTC"}, 53 | {"t4.4", date20000601, false, date0, ""}, 54 | {"t4.5", date20000601, true, date20000601, "must be greater than 2000-06-01 00:00:00 +0000 UTC"}, 55 | {"t4.6", date20000601, true, 1, "cannot convert int to time.Time"}, 56 | {"t4.7", struct{}{}, false, 1, "type not supported: struct {}"}, 57 | {"t4.8", date0, false, date20000601, ""}, 58 | } 59 | 60 | for _, test := range tests { 61 | r := Min(test.threshold) 62 | if test.exclusive { 63 | r = r.Exclusive() 64 | } 65 | err := r.Validate(test.value) 66 | assertError(t, test.err, err, test.tag) 67 | } 68 | } 69 | 70 | func TestMinError(t *testing.T) { 71 | r := Min(10) 72 | assert.Equal(t, "must be no less than 10", r.Validate(9).Error()) 73 | 74 | r = r.Error("123") 75 | assert.Equal(t, "123", r.err.Message()) 76 | } 77 | 78 | func TestMax(t *testing.T) { 79 | date0 := time.Time{} 80 | date20000101 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 81 | date20001201 := time.Date(2000, 12, 1, 0, 0, 0, 0, time.UTC) 82 | date20000601 := time.Date(2000, 6, 1, 0, 0, 0, 0, time.UTC) 83 | 84 | tests := []struct { 85 | tag string 86 | threshold interface{} 87 | exclusive bool 88 | value interface{} 89 | err string 90 | }{ 91 | // int cases 92 | {"t1.1", 2, false, 2, ""}, 93 | {"t1.2", 2, false, 1, ""}, 94 | {"t1.3", 2, false, 3, "must be no greater than 2"}, 95 | {"t1.4", 2, false, 0, ""}, 96 | {"t1.5", 2, true, 2, "must be less than 2"}, 97 | {"t1.6", 2, false, "1", "cannot convert string to int64"}, 98 | {"t1.7", "1", false, 1, "type not supported: string"}, 99 | // uint cases 100 | {"t2.1", uint(2), false, uint(2), ""}, 101 | {"t2.2", uint(2), false, uint(1), ""}, 102 | {"t2.3", uint(2), false, uint(3), "must be no greater than 2"}, 103 | {"t2.4", uint(2), false, uint(0), ""}, 104 | {"t2.5", uint(2), true, uint(2), "must be less than 2"}, 105 | {"t2.6", uint(2), false, "1", "cannot convert string to uint64"}, 106 | // float cases 107 | {"t3.1", float64(2), false, float64(2), ""}, 108 | {"t3.2", float64(2), false, float64(1), ""}, 109 | {"t3.3", float64(2), false, float64(3), "must be no greater than 2"}, 110 | {"t3.4", float64(2), false, float64(0), ""}, 111 | {"t3.5", float64(2), true, float64(2), "must be less than 2"}, 112 | {"t3.6", float64(2), false, "1", "cannot convert string to float64"}, 113 | // Time cases 114 | {"t4.1", date20000601, false, date20000601, ""}, 115 | {"t4.2", date20000601, false, date20000101, ""}, 116 | {"t4.3", date20000601, false, date20001201, "must be no greater than 2000-06-01 00:00:00 +0000 UTC"}, 117 | {"t4.4", date20000601, false, date0, ""}, 118 | {"t4.5", date20000601, true, date20000601, "must be less than 2000-06-01 00:00:00 +0000 UTC"}, 119 | {"t4.6", date20000601, true, 1, "cannot convert int to time.Time"}, 120 | } 121 | 122 | for _, test := range tests { 123 | r := Max(test.threshold) 124 | if test.exclusive { 125 | r = r.Exclusive() 126 | } 127 | err := r.Validate(test.value) 128 | assertError(t, test.err, err, test.tag) 129 | } 130 | } 131 | 132 | func TestMaxError(t *testing.T) { 133 | r := Max(10) 134 | assert.Equal(t, "must be no greater than 10", r.Validate(11).Error()) 135 | 136 | r = r.Error("123") 137 | assert.Equal(t, "123", r.err.Message()) 138 | } 139 | 140 | func TestThresholdRule_ErrorObject(t *testing.T) { 141 | r := Max(10) 142 | err := NewError("code", "abc") 143 | r = r.ErrorObject(err) 144 | 145 | assert.Equal(t, err, r.err) 146 | assert.Equal(t, err.Code(), r.err.Code()) 147 | assert.Equal(t, err.Message(), r.err.Message()) 148 | } 149 | -------------------------------------------------------------------------------- /multipleof.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ErrMultipleOfInvalid is the error that returns when a value is not multiple of a base. 9 | var ErrMultipleOfInvalid = NewError("validation_multiple_of_invalid", "must be multiple of {{.base}}") 10 | 11 | // MultipleOf returns a validation rule that checks if a value is a multiple of the "base" value. 12 | // Note that "base" should be of integer type. 13 | func MultipleOf(base interface{}) MultipleOfRule { 14 | return MultipleOfRule{ 15 | base: base, 16 | err: ErrMultipleOfInvalid, 17 | } 18 | } 19 | 20 | // MultipleOfRule is a validation rule that checks if a value is a multiple of the "base" value. 21 | type MultipleOfRule struct { 22 | base interface{} 23 | err Error 24 | } 25 | 26 | // Error sets the error message for the rule. 27 | func (r MultipleOfRule) Error(message string) MultipleOfRule { 28 | r.err = r.err.SetMessage(message) 29 | return r 30 | } 31 | 32 | // ErrorObject sets the error struct for the rule. 33 | func (r MultipleOfRule) ErrorObject(err Error) MultipleOfRule { 34 | r.err = err 35 | return r 36 | } 37 | 38 | // Validate checks if the value is a multiple of the "base" value. 39 | func (r MultipleOfRule) Validate(value interface{}) error { 40 | rv := reflect.ValueOf(r.base) 41 | switch rv.Kind() { 42 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 43 | v, err := ToInt(value) 44 | if err != nil { 45 | return err 46 | } 47 | if v%rv.Int() == 0 { 48 | return nil 49 | } 50 | 51 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 52 | v, err := ToUint(value) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if v%rv.Uint() == 0 { 58 | return nil 59 | } 60 | default: 61 | return fmt.Errorf("type not supported: %v", rv.Type()) 62 | } 63 | 64 | return r.err.SetParams(map[string]interface{}{"base": r.base}) 65 | } 66 | -------------------------------------------------------------------------------- /multipleof_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue, Google LLC. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestMultipleOf(t *testing.T) { 14 | r := MultipleOf(10) 15 | assert.Equal(t, "must be multiple of 10", r.Validate(11).Error()) 16 | assert.Equal(t, nil, r.Validate(20)) 17 | assert.Equal(t, "cannot convert float32 to int64", r.Validate(float32(20)).Error()) 18 | 19 | r2 := MultipleOf("some string ....") 20 | assert.Equal(t, "type not supported: string", r2.Validate(10).Error()) 21 | 22 | r3 := MultipleOf(uint(10)) 23 | assert.Equal(t, "must be multiple of 10", r3.Validate(uint(11)).Error()) 24 | assert.Equal(t, nil, r3.Validate(uint(20))) 25 | assert.Equal(t, "cannot convert float32 to uint64", r3.Validate(float32(20)).Error()) 26 | 27 | } 28 | 29 | func Test_MultipleOf_Error(t *testing.T) { 30 | r := MultipleOf(10) 31 | assert.Equal(t, "must be multiple of 10", r.Validate(3).Error()) 32 | 33 | r = r.Error("some error string ...") 34 | assert.Equal(t, "some error string ...", r.err.Message()) 35 | } 36 | 37 | func TestMultipleOfRule_ErrorObject(t *testing.T) { 38 | r := MultipleOf(10) 39 | err := NewError("code", "abc") 40 | r = r.ErrorObject(err) 41 | 42 | assert.Equal(t, err, r.err) 43 | assert.Equal(t, err.Code(), r.err.Code()) 44 | assert.Equal(t, err.Message(), r.err.Message()) 45 | } 46 | -------------------------------------------------------------------------------- /not_in.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Qiang Xue, Google LLC. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | // ErrNotInInvalid is the error that returns when a value is in a list. 8 | var ErrNotInInvalid = NewError("validation_not_in_invalid", "must not be in list") 9 | 10 | // NotIn returns a validation rule that checks if a value is absent from the given list of values. 11 | // Note that the value being checked and the possible range of values must be of the same type. 12 | // An empty value is considered valid. Use the Required rule to make sure a value is not empty. 13 | func NotIn(values ...interface{}) NotInRule { 14 | return NotInRule{ 15 | elements: values, 16 | err: ErrNotInInvalid, 17 | } 18 | } 19 | 20 | // NotInRule is a validation rule that checks if a value is absent from the given list of values. 21 | type NotInRule struct { 22 | elements []interface{} 23 | err Error 24 | } 25 | 26 | // Validate checks if the given value is valid or not. 27 | func (r NotInRule) Validate(value interface{}) error { 28 | value, isNil := Indirect(value) 29 | if isNil || IsEmpty(value) { 30 | return nil 31 | } 32 | 33 | for _, e := range r.elements { 34 | if e == value { 35 | return r.err 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | // Error sets the error message for the rule. 42 | func (r NotInRule) Error(message string) NotInRule { 43 | r.err = r.err.SetMessage(message) 44 | return r 45 | } 46 | 47 | // ErrorObject sets the error struct for the rule. 48 | func (r NotInRule) ErrorObject(err Error) NotInRule { 49 | r.err = err 50 | return r 51 | } 52 | -------------------------------------------------------------------------------- /not_in_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue, Google LLC. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestNotIn(t *testing.T) { 14 | v := 1 15 | var v2 *int 16 | var tests = []struct { 17 | tag string 18 | values []interface{} 19 | value interface{} 20 | err string 21 | }{ 22 | {"t0", []interface{}{1, 2}, 0, ""}, 23 | {"t1", []interface{}{1, 2}, 1, "must not be in list"}, 24 | {"t2", []interface{}{1, 2}, 2, "must not be in list"}, 25 | {"t3", []interface{}{1, 2}, 3, ""}, 26 | {"t4", []interface{}{}, 3, ""}, 27 | {"t5", []interface{}{1, 2}, "1", ""}, 28 | {"t6", []interface{}{1, 2}, &v, "must not be in list"}, 29 | {"t7", []interface{}{1, 2}, v2, ""}, 30 | } 31 | 32 | for _, test := range tests { 33 | r := NotIn(test.values...) 34 | err := r.Validate(test.value) 35 | assertError(t, test.err, err, test.tag) 36 | } 37 | } 38 | 39 | func Test_NotInRule_Error(t *testing.T) { 40 | r := NotIn(1, 2, 3) 41 | assert.Equal(t, "must not be in list", r.Validate(1).Error()) 42 | r = r.Error("123") 43 | assert.Equal(t, "123", r.err.Message()) 44 | } 45 | 46 | func TestNotInRule_ErrorObject(t *testing.T) { 47 | r := NotIn(1, 2, 3) 48 | 49 | err := NewError("code", "abc") 50 | r = r.ErrorObject(err) 51 | 52 | assert.Equal(t, err, r.err) 53 | assert.Equal(t, err.Code(), r.err.Code()) 54 | assert.Equal(t, err.Message(), r.err.Message()) 55 | } 56 | -------------------------------------------------------------------------------- /not_nil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | // ErrNotNilRequired is the error that returns when a value is Nil. 8 | var ErrNotNilRequired = NewError("validation_not_nil_required", "is required") 9 | 10 | // NotNil is a validation rule that checks if a value is not nil. 11 | // NotNil only handles types including interface, pointer, slice, and map. 12 | // All other types are considered valid. 13 | var NotNil = notNilRule{} 14 | 15 | type notNilRule struct { 16 | err Error 17 | } 18 | 19 | // Validate checks if the given value is valid or not. 20 | func (r notNilRule) Validate(value interface{}) error { 21 | _, isNil := Indirect(value) 22 | if isNil { 23 | if r.err != nil { 24 | return r.err 25 | } 26 | return ErrNotNilRequired 27 | } 28 | return nil 29 | } 30 | 31 | // Error sets the error message for the rule. 32 | func (r notNilRule) Error(message string) notNilRule { 33 | if r.err == nil { 34 | r.err = ErrNotNilRequired 35 | } 36 | r.err = r.err.SetMessage(message) 37 | return r 38 | } 39 | 40 | // ErrorObject sets the error struct for the rule. 41 | func (r notNilRule) ErrorObject(err Error) notNilRule { 42 | r.err = err 43 | return r 44 | } 45 | -------------------------------------------------------------------------------- /not_nil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | type MyInterface interface { 14 | Hello() 15 | } 16 | 17 | func TestNotNil(t *testing.T) { 18 | var v1 []int 19 | var v2 map[string]int 20 | var v3 *int 21 | var v4 interface{} 22 | var v5 MyInterface 23 | tests := []struct { 24 | tag string 25 | value interface{} 26 | err string 27 | }{ 28 | {"t1", v1, "is required"}, 29 | {"t2", v2, "is required"}, 30 | {"t3", v3, "is required"}, 31 | {"t4", v4, "is required"}, 32 | {"t5", v5, "is required"}, 33 | {"t6", "", ""}, 34 | {"t7", 0, ""}, 35 | } 36 | 37 | for _, test := range tests { 38 | r := NotNil 39 | err := r.Validate(test.value) 40 | assertError(t, test.err, err, test.tag) 41 | } 42 | } 43 | 44 | func Test_notNilRule_Error(t *testing.T) { 45 | r := NotNil 46 | assert.Equal(t, "is required", r.Validate(nil).Error()) 47 | r2 := r.Error("123") 48 | assert.Equal(t, "is required", r.Validate(nil).Error()) 49 | assert.Equal(t, "123", r2.err.Message()) 50 | } 51 | 52 | func TestNotNilRule_ErrorObject(t *testing.T) { 53 | r := NotNil 54 | 55 | err := NewError("code", "abc") 56 | r = r.ErrorObject(err) 57 | 58 | assert.Equal(t, err, r.err) 59 | assert.Equal(t, err.Code(), r.err.Code()) 60 | assert.Equal(t, err.Message(), r.err.Message()) 61 | assert.NotEqual(t, err, NotNil.err) 62 | } 63 | -------------------------------------------------------------------------------- /required.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | var ( 8 | // ErrRequired is the error that returns when a value is required. 9 | ErrRequired = NewError("validation_required", "cannot be blank") 10 | // ErrNilOrNotEmpty is the error that returns when a value is not nil and is empty. 11 | ErrNilOrNotEmpty = NewError("validation_nil_or_not_empty_required", "cannot be blank") 12 | ) 13 | 14 | // Required is a validation rule that checks if a value is not empty. 15 | // A value is considered not empty if 16 | // - integer, float: not zero 17 | // - bool: true 18 | // - string, array, slice, map: len() > 0 19 | // - interface, pointer: not nil and the referenced value is not empty 20 | // - any other types 21 | var Required = RequiredRule{skipNil: false, condition: true} 22 | 23 | // NilOrNotEmpty checks if a value is a nil pointer or a value that is not empty. 24 | // NilOrNotEmpty differs from Required in that it treats a nil pointer as valid. 25 | var NilOrNotEmpty = RequiredRule{skipNil: true, condition: true} 26 | 27 | // RequiredRule is a rule that checks if a value is not empty. 28 | type RequiredRule struct { 29 | condition bool 30 | skipNil bool 31 | err Error 32 | } 33 | 34 | // Validate checks if the given value is valid or not. 35 | func (r RequiredRule) Validate(value interface{}) error { 36 | if r.condition { 37 | value, isNil := Indirect(value) 38 | if r.skipNil && !isNil && IsEmpty(value) || !r.skipNil && (isNil || IsEmpty(value)) { 39 | if r.err != nil { 40 | return r.err 41 | } 42 | if r.skipNil { 43 | return ErrNilOrNotEmpty 44 | } 45 | return ErrRequired 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | // When sets the condition that determines if the validation should be performed. 52 | func (r RequiredRule) When(condition bool) RequiredRule { 53 | r.condition = condition 54 | return r 55 | } 56 | 57 | // Error sets the error message for the rule. 58 | func (r RequiredRule) Error(message string) RequiredRule { 59 | if r.err == nil { 60 | if r.skipNil { 61 | r.err = ErrNilOrNotEmpty 62 | } else { 63 | r.err = ErrRequired 64 | } 65 | } 66 | r.err = r.err.SetMessage(message) 67 | return r 68 | } 69 | 70 | // ErrorObject sets the error struct for the rule. 71 | func (r RequiredRule) ErrorObject(err Error) RequiredRule { 72 | r.err = err 73 | return r 74 | } 75 | -------------------------------------------------------------------------------- /required_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRequired(t *testing.T) { 15 | s1 := "123" 16 | s2 := "" 17 | var time1 time.Time 18 | tests := []struct { 19 | tag string 20 | value interface{} 21 | err string 22 | }{ 23 | {"t1", 123, ""}, 24 | {"t2", "", "cannot be blank"}, 25 | {"t3", &s1, ""}, 26 | {"t4", &s2, "cannot be blank"}, 27 | {"t5", nil, "cannot be blank"}, 28 | {"t6", time1, "cannot be blank"}, 29 | } 30 | 31 | for _, test := range tests { 32 | r := Required 33 | err := r.Validate(test.value) 34 | assertError(t, test.err, err, test.tag) 35 | } 36 | } 37 | 38 | func TestRequiredRule_When(t *testing.T) { 39 | r := Required.When(false) 40 | err := Validate(nil, r) 41 | assert.Nil(t, err) 42 | 43 | r = Required.When(true) 44 | err = Validate(nil, r) 45 | assert.Equal(t, ErrRequired, err) 46 | } 47 | 48 | func TestNilOrNotEmpty(t *testing.T) { 49 | s1 := "123" 50 | s2 := "" 51 | tests := []struct { 52 | tag string 53 | value interface{} 54 | err string 55 | }{ 56 | {"t1", 123, ""}, 57 | {"t2", "", "cannot be blank"}, 58 | {"t3", &s1, ""}, 59 | {"t4", &s2, "cannot be blank"}, 60 | {"t5", nil, ""}, 61 | } 62 | 63 | for _, test := range tests { 64 | r := NilOrNotEmpty 65 | err := r.Validate(test.value) 66 | assertError(t, test.err, err, test.tag) 67 | } 68 | } 69 | 70 | func Test_requiredRule_Error(t *testing.T) { 71 | r := Required 72 | assert.Equal(t, "cannot be blank", r.Validate(nil).Error()) 73 | assert.False(t, r.skipNil) 74 | r2 := r.Error("123") 75 | assert.Equal(t, "cannot be blank", r.Validate(nil).Error()) 76 | assert.False(t, r.skipNil) 77 | assert.Equal(t, "123", r2.err.Message()) 78 | assert.False(t, r2.skipNil) 79 | 80 | r = NilOrNotEmpty 81 | assert.Equal(t, "cannot be blank", r.Validate("").Error()) 82 | assert.True(t, r.skipNil) 83 | r2 = r.Error("123") 84 | assert.Equal(t, "cannot be blank", r.Validate("").Error()) 85 | assert.True(t, r.skipNil) 86 | assert.Equal(t, "123", r2.err.Message()) 87 | assert.True(t, r2.skipNil) 88 | } 89 | 90 | func TestRequiredRule_Error(t *testing.T) { 91 | r := Required 92 | 93 | err := NewError("code", "abc") 94 | r = r.ErrorObject(err) 95 | 96 | assert.Equal(t, err, r.err) 97 | assert.Equal(t, err.Code(), r.err.Code()) 98 | assert.Equal(t, err.Message(), r.err.Message()) 99 | assert.NotEqual(t, err, Required.err) 100 | } 101 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | type stringValidator func(string) bool 8 | 9 | // StringRule is a rule that checks a string variable using a specified stringValidator. 10 | type StringRule struct { 11 | validate stringValidator 12 | err Error 13 | } 14 | 15 | // NewStringRule creates a new validation rule using a function that takes a string value and returns a bool. 16 | // The rule returned will use the function to check if a given string or byte slice is valid or not. 17 | // An empty value is considered to be valid. Please use the Required rule to make sure a value is not empty. 18 | func NewStringRule(validator stringValidator, message string) StringRule { 19 | return StringRule{ 20 | validate: validator, 21 | err: NewError("", message), 22 | } 23 | } 24 | 25 | // NewStringRuleWithError creates a new validation rule using a function that takes a string value and returns a bool. 26 | // The rule returned will use the function to check if a given string or byte slice is valid or not. 27 | // An empty value is considered to be valid. Please use the Required rule to make sure a value is not empty. 28 | func NewStringRuleWithError(validator stringValidator, err Error) StringRule { 29 | return StringRule{ 30 | validate: validator, 31 | err: err, 32 | } 33 | } 34 | 35 | // Error sets the error message for the rule. 36 | func (r StringRule) Error(message string) StringRule { 37 | r.err = r.err.SetMessage(message) 38 | return r 39 | } 40 | 41 | // ErrorObject sets the error struct for the rule. 42 | func (r StringRule) ErrorObject(err Error) StringRule { 43 | r.err = err 44 | return r 45 | } 46 | 47 | // Validate checks if the given value is valid or not. 48 | func (r StringRule) Validate(value interface{}) error { 49 | value, isNil := Indirect(value) 50 | if isNil || IsEmpty(value) { 51 | return nil 52 | } 53 | 54 | str, err := EnsureString(value) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | if r.validate(str) { 60 | return nil 61 | } 62 | 63 | return r.err 64 | } 65 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "database/sql" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func validateMe(s string) bool { 16 | return s == "me" 17 | } 18 | 19 | func TestNewStringRule(t *testing.T) { 20 | v := NewStringRule(validateMe, "abc") 21 | 22 | assert.NotNil(t, v.validate) 23 | assert.Equal(t, "", v.err.Code()) 24 | assert.Equal(t, "abc", v.err.Message()) 25 | } 26 | 27 | func TestNewStringRuleWithError(t *testing.T) { 28 | err := NewError("C", "abc") 29 | v := NewStringRuleWithError(validateMe, err) 30 | 31 | assert.NotNil(t, v.validate) 32 | assert.Equal(t, err, v.err) 33 | assert.Equal(t, "C", v.err.Code()) 34 | assert.Equal(t, "abc", v.err.Message()) 35 | } 36 | 37 | func TestStringRule_Error(t *testing.T) { 38 | err := NewError("code", "abc") 39 | v := NewStringRuleWithError(validateMe, err).Error("abc") 40 | assert.Equal(t, "code", v.err.Code()) 41 | assert.Equal(t, "abc", v.err.Message()) 42 | 43 | v2 := v.Error("correct") 44 | assert.Equal(t, "code", v.err.Code()) 45 | assert.Equal(t, "correct", v2.err.Message()) 46 | assert.Equal(t, "abc", v.err.Message()) 47 | } 48 | 49 | func TestStringValidator_Validate(t *testing.T) { 50 | v := NewStringRule(validateMe, "wrong_rule").Error("wrong") 51 | 52 | value := "me" 53 | 54 | err := v.Validate(value) 55 | assert.Nil(t, err) 56 | 57 | err = v.Validate(&value) 58 | assert.Nil(t, err) 59 | 60 | value = "" 61 | 62 | err = v.Validate(value) 63 | assert.Nil(t, err) 64 | 65 | err = v.Validate(&value) 66 | assert.Nil(t, err) 67 | 68 | nullValue := sql.NullString{String: "me", Valid: true} 69 | err = v.Validate(nullValue) 70 | assert.Nil(t, err) 71 | 72 | nullValue = sql.NullString{String: "", Valid: true} 73 | err = v.Validate(nullValue) 74 | assert.Nil(t, err) 75 | 76 | var s *string 77 | err = v.Validate(s) 78 | assert.Nil(t, err) 79 | 80 | err = v.Validate("not me") 81 | if assert.NotNil(t, err) { 82 | assert.Equal(t, "wrong", err.Error()) 83 | } 84 | 85 | err = v.Validate(100) 86 | if assert.NotNil(t, err) { 87 | assert.NotEqual(t, "wrong", err.Error()) 88 | } 89 | 90 | v2 := v.Error("Wrong!") 91 | err = v2.Validate("not me") 92 | if assert.NotNil(t, err) { 93 | assert.Equal(t, "Wrong!", err.Error()) 94 | } 95 | } 96 | 97 | func TestGetErrorFieldName(t *testing.T) { 98 | type A struct { 99 | T0 string 100 | T1 string `json:"t1"` 101 | T2 string `json:"t2,omitempty"` 102 | T3 string `json:",omitempty"` 103 | T4 string `json:"t4,x1,omitempty"` 104 | } 105 | tests := []struct { 106 | tag string 107 | field string 108 | name string 109 | }{ 110 | {"t1", "T0", "T0"}, 111 | {"t2", "T1", "t1"}, 112 | {"t3", "T2", "t2"}, 113 | {"t4", "T3", "T3"}, 114 | {"t5", "T4", "t4"}, 115 | } 116 | a := reflect.TypeOf(A{}) 117 | for _, test := range tests { 118 | field, _ := a.FieldByName(test.field) 119 | assert.Equal(t, test.name, getErrorFieldName(&field), test.tag) 120 | } 121 | } 122 | 123 | func TestStringRule_ErrorObject(t *testing.T) { 124 | r := NewStringRule(validateMe, "wrong_rule") 125 | 126 | err := NewError("code", "abc") 127 | r = r.ErrorObject(err) 128 | 129 | assert.Equal(t, err, r.err) 130 | assert.Equal(t, "code", r.err.Code()) 131 | assert.Equal(t, "abc", r.err.Message()) 132 | } 133 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | "reflect" 12 | "strings" 13 | ) 14 | 15 | var ( 16 | // ErrStructPointer is the error that a struct being validated is not specified as a pointer. 17 | ErrStructPointer = errors.New("only a pointer to a struct can be validated") 18 | ) 19 | 20 | type ( 21 | // ErrFieldPointer is the error that a field is not specified as a pointer. 22 | ErrFieldPointer int 23 | 24 | // ErrFieldNotFound is the error that a field cannot be found in the struct. 25 | ErrFieldNotFound int 26 | 27 | // FieldRules represents a rule set associated with a struct field. 28 | FieldRules struct { 29 | fieldPtr interface{} 30 | rules []Rule 31 | } 32 | ) 33 | 34 | // Error returns the error string of ErrFieldPointer. 35 | func (e ErrFieldPointer) Error() string { 36 | return fmt.Sprintf("field #%v must be specified as a pointer", int(e)) 37 | } 38 | 39 | // Error returns the error string of ErrFieldNotFound. 40 | func (e ErrFieldNotFound) Error() string { 41 | return fmt.Sprintf("field #%v cannot be found in the struct", int(e)) 42 | } 43 | 44 | // ValidateStruct validates a struct by checking the specified struct fields against the corresponding validation rules. 45 | // Note that the struct being validated must be specified as a pointer to it. If the pointer is nil, it is considered valid. 46 | // Use Field() to specify struct fields that need to be validated. Each Field() call specifies a single field which 47 | // should be specified as a pointer to the field. A field can be associated with multiple rules. 48 | // For example, 49 | // 50 | // value := struct { 51 | // Name string 52 | // Value string 53 | // }{"name", "demo"} 54 | // err := validation.ValidateStruct(&value, 55 | // validation.Field(&a.Name, validation.Required), 56 | // validation.Field(&a.Value, validation.Required, validation.Length(5, 10)), 57 | // ) 58 | // fmt.Println(err) 59 | // // Value: the length must be between 5 and 10. 60 | // 61 | // An error will be returned if validation fails. 62 | func ValidateStruct(structPtr interface{}, fields ...*FieldRules) error { 63 | return ValidateStructWithContext(nil, structPtr, fields...) 64 | } 65 | 66 | // ValidateStructWithContext validates a struct with the given context. 67 | // The only difference between ValidateStructWithContext and ValidateStruct is that the former will 68 | // validate struct fields with the provided context. 69 | // Please refer to ValidateStruct for the detailed instructions on how to use this function. 70 | func ValidateStructWithContext(ctx context.Context, structPtr interface{}, fields ...*FieldRules) error { 71 | value := reflect.ValueOf(structPtr) 72 | if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct { 73 | // must be a pointer to a struct 74 | return NewInternalError(ErrStructPointer) 75 | } 76 | if value.IsNil() { 77 | // treat a nil struct pointer as valid 78 | return nil 79 | } 80 | value = value.Elem() 81 | 82 | errs := Errors{} 83 | 84 | for i, fr := range fields { 85 | fv := reflect.ValueOf(fr.fieldPtr) 86 | if fv.Kind() != reflect.Ptr { 87 | return NewInternalError(ErrFieldPointer(i)) 88 | } 89 | ft := findStructField(value, fv) 90 | if ft == nil { 91 | return NewInternalError(ErrFieldNotFound(i)) 92 | } 93 | var err error 94 | if ctx == nil { 95 | err = Validate(fv.Elem().Interface(), fr.rules...) 96 | } else { 97 | err = ValidateWithContext(ctx, fv.Elem().Interface(), fr.rules...) 98 | } 99 | if err != nil { 100 | if ie, ok := err.(InternalError); ok && ie.InternalError() != nil { 101 | return err 102 | } 103 | if ft.Anonymous { 104 | // merge errors from anonymous struct field 105 | if es, ok := err.(Errors); ok { 106 | for name, value := range es { 107 | errs[name] = value 108 | } 109 | continue 110 | } 111 | } 112 | errs[getErrorFieldName(ft)] = err 113 | } 114 | } 115 | 116 | if len(errs) > 0 { 117 | return errs 118 | } 119 | return nil 120 | } 121 | 122 | // Field specifies a struct field and the corresponding validation rules. 123 | // The struct field must be specified as a pointer to it. 124 | func Field(fieldPtr interface{}, rules ...Rule) *FieldRules { 125 | return &FieldRules{ 126 | fieldPtr: fieldPtr, 127 | rules: rules, 128 | } 129 | } 130 | 131 | // findStructField looks for a field in the given struct. 132 | // The field being looked for should be a pointer to the actual struct field. 133 | // If found, the field info will be returned. Otherwise, nil will be returned. 134 | func findStructField(structValue reflect.Value, fieldValue reflect.Value) *reflect.StructField { 135 | ptr := fieldValue.Pointer() 136 | for i := structValue.NumField() - 1; i >= 0; i-- { 137 | sf := structValue.Type().Field(i) 138 | if ptr == structValue.Field(i).UnsafeAddr() { 139 | // do additional type comparison because it's possible that the address of 140 | // an embedded struct is the same as the first field of the embedded struct 141 | if sf.Type == fieldValue.Elem().Type() { 142 | return &sf 143 | } 144 | } 145 | if sf.Anonymous { 146 | // delve into anonymous struct to look for the field 147 | fi := structValue.Field(i) 148 | if sf.Type.Kind() == reflect.Ptr { 149 | fi = fi.Elem() 150 | } 151 | if fi.Kind() == reflect.Struct { 152 | if f := findStructField(fi, fieldValue); f != nil { 153 | return f 154 | } 155 | } 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | // getErrorFieldName returns the name that should be used to represent the validation error of a struct field. 162 | func getErrorFieldName(f *reflect.StructField) string { 163 | if tag := f.Tag.Get(ErrorTag); tag != "" && tag != "-" { 164 | if cps := strings.SplitN(tag, ",", 2); cps[0] != "" { 165 | return cps[0] 166 | } 167 | } 168 | return f.Name 169 | } 170 | -------------------------------------------------------------------------------- /struct_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "context" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | type Struct1 struct { 16 | Field1 int 17 | Field2 *int 18 | Field3 []int 19 | Field4 [4]int 20 | field5 int 21 | Struct2 22 | S1 *Struct2 23 | S2 Struct2 24 | JSONField int `json:"some_json_field"` 25 | JSONIgnoredField int `json:"-"` 26 | } 27 | 28 | type Struct2 struct { 29 | Field21 string 30 | Field22 string 31 | } 32 | 33 | type Struct3 struct { 34 | *Struct2 35 | S1 string 36 | } 37 | 38 | func TestFindStructField(t *testing.T) { 39 | var s1 Struct1 40 | v1 := reflect.ValueOf(&s1).Elem() 41 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field1))) 42 | assert.Nil(t, findStructField(v1, reflect.ValueOf(s1.Field2))) 43 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field2))) 44 | assert.Nil(t, findStructField(v1, reflect.ValueOf(s1.Field3))) 45 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field3))) 46 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field4))) 47 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.field5))) 48 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Struct2))) 49 | assert.Nil(t, findStructField(v1, reflect.ValueOf(s1.S1))) 50 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.S1))) 51 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field21))) 52 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field22))) 53 | assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Struct2.Field22))) 54 | s2 := reflect.ValueOf(&s1.Struct2).Elem() 55 | assert.NotNil(t, findStructField(s2, reflect.ValueOf(&s1.Field21))) 56 | assert.NotNil(t, findStructField(s2, reflect.ValueOf(&s1.Struct2.Field21))) 57 | assert.NotNil(t, findStructField(s2, reflect.ValueOf(&s1.Struct2.Field22))) 58 | s3 := Struct3{ 59 | Struct2: &Struct2{}, 60 | } 61 | v3 := reflect.ValueOf(&s3).Elem() 62 | assert.NotNil(t, findStructField(v3, reflect.ValueOf(&s3.Struct2))) 63 | assert.NotNil(t, findStructField(v3, reflect.ValueOf(&s3.Field21))) 64 | } 65 | 66 | func TestValidateStruct(t *testing.T) { 67 | var m0 *Model1 68 | m1 := Model1{A: "abc", B: "xyz", c: "abc", G: "xyz", H: []string{"abc", "abc"}, I: map[string]string{"foo": "abc"}} 69 | m2 := Model1{E: String123("xyz")} 70 | m3 := Model2{} 71 | m4 := Model2{M3: Model3{A: "abc"}, Model3: Model3{A: "abc"}} 72 | m5 := Model2{Model3: Model3{A: "internal"}} 73 | tests := []struct { 74 | tag string 75 | model interface{} 76 | rules []*FieldRules 77 | err string 78 | }{ 79 | // empty rules 80 | {"t1.1", &m1, []*FieldRules{}, ""}, 81 | {"t1.2", &m1, []*FieldRules{Field(&m1.A), Field(&m1.B)}, ""}, 82 | // normal rules 83 | {"t2.1", &m1, []*FieldRules{Field(&m1.A, &validateAbc{}), Field(&m1.B, &validateXyz{})}, ""}, 84 | {"t2.2", &m1, []*FieldRules{Field(&m1.A, &validateXyz{}), Field(&m1.B, &validateAbc{})}, "A: error xyz; B: error abc."}, 85 | {"t2.3", &m1, []*FieldRules{Field(&m1.A, &validateXyz{}), Field(&m1.c, &validateXyz{})}, "A: error xyz; c: error xyz."}, 86 | {"t2.4", &m1, []*FieldRules{Field(&m1.D, Length(0, 5))}, ""}, 87 | {"t2.5", &m1, []*FieldRules{Field(&m1.F, Length(0, 5))}, ""}, 88 | {"t2.6", &m1, []*FieldRules{Field(&m1.H, Each(&validateAbc{})), Field(&m1.I, Each(&validateAbc{}))}, ""}, 89 | {"t2.7", &m1, []*FieldRules{Field(&m1.H, Each(&validateXyz{})), Field(&m1.I, Each(&validateXyz{}))}, "H: (0: error xyz; 1: error xyz.); I: (foo: error xyz.)."}, 90 | // non-struct pointer 91 | {"t3.1", m1, []*FieldRules{}, ErrStructPointer.Error()}, 92 | {"t3.2", nil, []*FieldRules{}, ErrStructPointer.Error()}, 93 | {"t3.3", m0, []*FieldRules{}, ""}, 94 | {"t3.4", &m0, []*FieldRules{}, ErrStructPointer.Error()}, 95 | // invalid field spec 96 | {"t4.1", &m1, []*FieldRules{Field(m1)}, ErrFieldPointer(0).Error()}, 97 | {"t4.2", &m1, []*FieldRules{Field(&m1)}, ErrFieldNotFound(0).Error()}, 98 | // struct tag 99 | {"t5.1", &m1, []*FieldRules{Field(&m1.G, &validateAbc{})}, "g: error abc."}, 100 | // validatable field 101 | {"t6.1", &m2, []*FieldRules{Field(&m2.E)}, "E: error 123."}, 102 | {"t6.2", &m2, []*FieldRules{Field(&m2.E, Skip)}, ""}, 103 | {"t6.3", &m2, []*FieldRules{Field(&m2.E, Skip.When(true))}, ""}, 104 | {"t6.4", &m2, []*FieldRules{Field(&m2.E, Skip.When(false))}, "E: error 123."}, 105 | // Required, NotNil 106 | {"t7.1", &m2, []*FieldRules{Field(&m2.F, Required)}, "F: cannot be blank."}, 107 | {"t7.2", &m2, []*FieldRules{Field(&m2.F, NotNil)}, "F: is required."}, 108 | {"t7.3", &m2, []*FieldRules{Field(&m2.F, Skip, Required)}, ""}, 109 | {"t7.4", &m2, []*FieldRules{Field(&m2.F, Skip, NotNil)}, ""}, 110 | {"t7.5", &m2, []*FieldRules{Field(&m2.F, Skip.When(true), Required)}, ""}, 111 | {"t7.6", &m2, []*FieldRules{Field(&m2.F, Skip.When(true), NotNil)}, ""}, 112 | {"t7.7", &m2, []*FieldRules{Field(&m2.F, Skip.When(false), Required)}, "F: cannot be blank."}, 113 | {"t7.8", &m2, []*FieldRules{Field(&m2.F, Skip.When(false), NotNil)}, "F: is required."}, 114 | // embedded structs 115 | {"t8.1", &m3, []*FieldRules{Field(&m3.M3, Skip)}, ""}, 116 | {"t8.2", &m3, []*FieldRules{Field(&m3.M3)}, "M3: (A: error abc.)."}, 117 | {"t8.3", &m3, []*FieldRules{Field(&m3.Model3, Skip)}, ""}, 118 | {"t8.4", &m3, []*FieldRules{Field(&m3.Model3)}, "A: error abc."}, 119 | {"t8.5", &m4, []*FieldRules{Field(&m4.M3)}, ""}, 120 | {"t8.6", &m4, []*FieldRules{Field(&m4.Model3)}, ""}, 121 | {"t8.7", &m3, []*FieldRules{Field(&m3.A, Required), Field(&m3.B, Required)}, "A: cannot be blank; B: cannot be blank."}, 122 | {"t8.8", &m3, []*FieldRules{Field(&m4.A, Required)}, "field #0 cannot be found in the struct"}, 123 | // internal error 124 | {"t9.1", &m5, []*FieldRules{Field(&m5.A, &validateAbc{}), Field(&m5.B, Required), Field(&m5.A, &validateInternalError{})}, "error internal"}, 125 | } 126 | for _, test := range tests { 127 | err1 := ValidateStruct(test.model, test.rules...) 128 | err2 := ValidateStructWithContext(context.Background(), test.model, test.rules...) 129 | assertError(t, test.err, err1, test.tag) 130 | assertError(t, test.err, err2, test.tag) 131 | } 132 | 133 | // embedded struct 134 | err := Validate(&m3) 135 | assert.EqualError(t, err, "A: error abc.") 136 | 137 | a := struct { 138 | Name string 139 | Value string 140 | }{"name", "demo"} 141 | err = ValidateStruct(&a, 142 | Field(&a.Name, Required), 143 | Field(&a.Value, Required, Length(5, 10)), 144 | ) 145 | assert.EqualError(t, err, "Value: the length must be between 5 and 10.") 146 | } 147 | 148 | func TestValidateStructWithContext(t *testing.T) { 149 | m1 := Model1{A: "abc", B: "xyz", c: "abc", G: "xyz"} 150 | m2 := Model2{Model3: Model3{A: "internal"}} 151 | m3 := Model5{} 152 | tests := []struct { 153 | tag string 154 | model interface{} 155 | rules []*FieldRules 156 | err string 157 | }{ 158 | // normal rules 159 | {"t1.1", &m1, []*FieldRules{Field(&m1.A, &validateContextAbc{}), Field(&m1.B, &validateContextXyz{})}, ""}, 160 | {"t1.2", &m1, []*FieldRules{Field(&m1.A, &validateContextXyz{}), Field(&m1.B, &validateContextAbc{})}, "A: error xyz; B: error abc."}, 161 | {"t1.3", &m1, []*FieldRules{Field(&m1.A, &validateContextXyz{}), Field(&m1.c, &validateContextXyz{})}, "A: error xyz; c: error xyz."}, 162 | {"t1.4", &m1, []*FieldRules{Field(&m1.G, &validateContextAbc{})}, "g: error abc."}, 163 | // skip rule 164 | {"t2.1", &m1, []*FieldRules{Field(&m1.G, Skip, &validateContextAbc{})}, ""}, 165 | {"t2.2", &m1, []*FieldRules{Field(&m1.G, &validateContextAbc{}, Skip)}, "g: error abc."}, 166 | // internal error 167 | {"t3.1", &m2, []*FieldRules{Field(&m2.A, &validateContextAbc{}), Field(&m2.B, Required), Field(&m2.A, &validateInternalError{})}, "error internal"}, 168 | } 169 | for _, test := range tests { 170 | err := ValidateStructWithContext(context.Background(), test.model, test.rules...) 171 | assertError(t, test.err, err, test.tag) 172 | } 173 | 174 | //embedded struct 175 | err := ValidateWithContext(context.Background(), &m3) 176 | if assert.NotNil(t, err) { 177 | assert.Equal(t, "A: error abc.", err.Error()) 178 | } 179 | 180 | a := struct { 181 | Name string 182 | Value string 183 | }{"name", "demo"} 184 | err = ValidateStructWithContext(context.Background(), &a, 185 | Field(&a.Name, Required), 186 | Field(&a.Value, Required, Length(5, 10)), 187 | ) 188 | if assert.NotNil(t, err) { 189 | assert.Equal(t, "Value: the length must be between 5 and 10.", err.Error()) 190 | } 191 | } 192 | 193 | func Test_getErrorFieldName(t *testing.T) { 194 | var s1 Struct1 195 | v1 := reflect.ValueOf(&s1).Elem() 196 | 197 | sf1 := findStructField(v1, reflect.ValueOf(&s1.Field1)) 198 | assert.NotNil(t, sf1) 199 | assert.Equal(t, "Field1", getErrorFieldName(sf1)) 200 | 201 | jsonField := findStructField(v1, reflect.ValueOf(&s1.JSONField)) 202 | assert.NotNil(t, jsonField) 203 | assert.Equal(t, "some_json_field", getErrorFieldName(jsonField)) 204 | 205 | jsonIgnoredField := findStructField(v1, reflect.ValueOf(&s1.JSONIgnoredField)) 206 | assert.NotNil(t, jsonIgnoredField) 207 | assert.Equal(t, "JSONIgnoredField", getErrorFieldName(jsonIgnoredField)) 208 | } 209 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "database/sql/driver" 9 | "errors" 10 | "fmt" 11 | "reflect" 12 | "time" 13 | ) 14 | 15 | var ( 16 | bytesType = reflect.TypeOf([]byte(nil)) 17 | valuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem() 18 | ) 19 | 20 | // EnsureString ensures the given value is a string. 21 | // If the value is a byte slice, it will be typecast into a string. 22 | // An error is returned otherwise. 23 | func EnsureString(value interface{}) (string, error) { 24 | v := reflect.ValueOf(value) 25 | if v.Kind() == reflect.String { 26 | return v.String(), nil 27 | } 28 | if v.Type() == bytesType { 29 | return string(v.Interface().([]byte)), nil 30 | } 31 | return "", errors.New("must be either a string or byte slice") 32 | } 33 | 34 | // StringOrBytes typecasts a value into a string or byte slice. 35 | // Boolean flags are returned to indicate if the typecasting succeeds or not. 36 | func StringOrBytes(value interface{}) (isString bool, str string, isBytes bool, bs []byte) { 37 | v := reflect.ValueOf(value) 38 | if v.Kind() == reflect.String { 39 | str = v.String() 40 | isString = true 41 | } else if v.Kind() == reflect.Slice && v.Type() == bytesType { 42 | bs = v.Interface().([]byte) 43 | isBytes = true 44 | } 45 | return 46 | } 47 | 48 | // LengthOfValue returns the length of a value that is a string, slice, map, or array. 49 | // An error is returned for all other types. 50 | func LengthOfValue(value interface{}) (int, error) { 51 | v := reflect.ValueOf(value) 52 | switch v.Kind() { 53 | case reflect.String, reflect.Slice, reflect.Map, reflect.Array: 54 | return v.Len(), nil 55 | } 56 | return 0, fmt.Errorf("cannot get the length of %v", v.Kind()) 57 | } 58 | 59 | // ToInt converts the given value to an int64. 60 | // An error is returned for all incompatible types. 61 | func ToInt(value interface{}) (int64, error) { 62 | v := reflect.ValueOf(value) 63 | switch v.Kind() { 64 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 65 | return v.Int(), nil 66 | } 67 | return 0, fmt.Errorf("cannot convert %v to int64", v.Kind()) 68 | } 69 | 70 | // ToUint converts the given value to an uint64. 71 | // An error is returned for all incompatible types. 72 | func ToUint(value interface{}) (uint64, error) { 73 | v := reflect.ValueOf(value) 74 | switch v.Kind() { 75 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 76 | return v.Uint(), nil 77 | } 78 | return 0, fmt.Errorf("cannot convert %v to uint64", v.Kind()) 79 | } 80 | 81 | // ToFloat converts the given value to a float64. 82 | // An error is returned for all incompatible types. 83 | func ToFloat(value interface{}) (float64, error) { 84 | v := reflect.ValueOf(value) 85 | switch v.Kind() { 86 | case reflect.Float32, reflect.Float64: 87 | return v.Float(), nil 88 | } 89 | return 0, fmt.Errorf("cannot convert %v to float64", v.Kind()) 90 | } 91 | 92 | // IsEmpty checks if a value is empty or not. 93 | // A value is considered empty if 94 | // - integer, float: zero 95 | // - bool: false 96 | // - string, array: len() == 0 97 | // - slice, map: nil or len() == 0 98 | // - interface, pointer: nil or the referenced value is empty 99 | func IsEmpty(value interface{}) bool { 100 | v := reflect.ValueOf(value) 101 | switch v.Kind() { 102 | case reflect.String, reflect.Array, reflect.Map, reflect.Slice: 103 | return v.Len() == 0 104 | case reflect.Bool: 105 | return !v.Bool() 106 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 107 | return v.Int() == 0 108 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 109 | return v.Uint() == 0 110 | case reflect.Float32, reflect.Float64: 111 | return v.Float() == 0 112 | case reflect.Invalid: 113 | return true 114 | case reflect.Interface, reflect.Ptr: 115 | if v.IsNil() { 116 | return true 117 | } 118 | return IsEmpty(v.Elem().Interface()) 119 | case reflect.Struct: 120 | v, ok := value.(time.Time) 121 | if ok && v.IsZero() { 122 | return true 123 | } 124 | } 125 | 126 | return false 127 | } 128 | 129 | // Indirect returns the value that the given interface or pointer references to. 130 | // If the value implements driver.Valuer, it will deal with the value returned by 131 | // the Value() method instead. A boolean value is also returned to indicate if 132 | // the value is nil or not (only applicable to interface, pointer, map, and slice). 133 | // If the value is neither an interface nor a pointer, it will be returned back. 134 | func Indirect(value interface{}) (interface{}, bool) { 135 | rv := reflect.ValueOf(value) 136 | kind := rv.Kind() 137 | switch kind { 138 | case reflect.Invalid: 139 | return nil, true 140 | case reflect.Ptr, reflect.Interface: 141 | if rv.IsNil() { 142 | return nil, true 143 | } 144 | return Indirect(rv.Elem().Interface()) 145 | case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: 146 | if rv.IsNil() { 147 | return nil, true 148 | } 149 | } 150 | 151 | if rv.Type().Implements(valuerType) { 152 | return indirectValuer(value.(driver.Valuer)) 153 | } 154 | 155 | return value, false 156 | } 157 | 158 | func indirectValuer(valuer driver.Valuer) (interface{}, bool) { 159 | if value, err := valuer.Value(); value != nil && err == nil { 160 | return Indirect(value) 161 | } 162 | return nil, true 163 | } 164 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "database/sql" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestEnsureString(t *testing.T) { 16 | str := "abc" 17 | bytes := []byte("abc") 18 | 19 | tests := []struct { 20 | tag string 21 | value interface{} 22 | expected string 23 | hasError bool 24 | }{ 25 | {"t1", "abc", "abc", false}, 26 | {"t2", &str, "", true}, 27 | {"t3", bytes, "abc", false}, 28 | {"t4", &bytes, "", true}, 29 | {"t5", 100, "", true}, 30 | } 31 | for _, test := range tests { 32 | s, err := EnsureString(test.value) 33 | if test.hasError { 34 | assert.NotNil(t, err, test.tag) 35 | } else { 36 | assert.Nil(t, err, test.tag) 37 | assert.Equal(t, test.expected, s, test.tag) 38 | } 39 | } 40 | } 41 | 42 | type MyString string 43 | 44 | func TestStringOrBytes(t *testing.T) { 45 | str := "abc" 46 | bytes := []byte("abc") 47 | var str2 string 48 | var bytes2 []byte 49 | var str3 MyString = "abc" 50 | var str4 *string 51 | 52 | tests := []struct { 53 | tag string 54 | value interface{} 55 | str string 56 | bs []byte 57 | isString bool 58 | isBytes bool 59 | }{ 60 | {"t1", str, "abc", nil, true, false}, 61 | {"t2", &str, "", nil, false, false}, 62 | {"t3", bytes, "", []byte("abc"), false, true}, 63 | {"t4", &bytes, "", nil, false, false}, 64 | {"t5", 100, "", nil, false, false}, 65 | {"t6", str2, "", nil, true, false}, 66 | {"t7", &str2, "", nil, false, false}, 67 | {"t8", bytes2, "", nil, false, true}, 68 | {"t9", &bytes2, "", nil, false, false}, 69 | {"t10", str3, "abc", nil, true, false}, 70 | {"t11", &str3, "", nil, false, false}, 71 | {"t12", str4, "", nil, false, false}, 72 | } 73 | for _, test := range tests { 74 | isString, str, isBytes, bs := StringOrBytes(test.value) 75 | assert.Equal(t, test.str, str, test.tag) 76 | assert.Equal(t, test.bs, bs, test.tag) 77 | assert.Equal(t, test.isString, isString, test.tag) 78 | assert.Equal(t, test.isBytes, isBytes, test.tag) 79 | } 80 | } 81 | 82 | func TestLengthOfValue(t *testing.T) { 83 | var a [3]int 84 | 85 | tests := []struct { 86 | tag string 87 | value interface{} 88 | length int 89 | err string 90 | }{ 91 | {"t1", "abc", 3, ""}, 92 | {"t2", []int{1, 2}, 2, ""}, 93 | {"t3", map[string]int{"A": 1, "B": 2}, 2, ""}, 94 | {"t4", a, 3, ""}, 95 | {"t5", &a, 0, "cannot get the length of ptr"}, 96 | {"t6", 123, 0, "cannot get the length of int"}, 97 | } 98 | 99 | for _, test := range tests { 100 | l, err := LengthOfValue(test.value) 101 | assert.Equal(t, test.length, l, test.tag) 102 | assertError(t, test.err, err, test.tag) 103 | } 104 | } 105 | 106 | func TestToInt(t *testing.T) { 107 | var a int 108 | 109 | tests := []struct { 110 | tag string 111 | value interface{} 112 | result int64 113 | err string 114 | }{ 115 | {"t1", 1, 1, ""}, 116 | {"t2", int8(1), 1, ""}, 117 | {"t3", int16(1), 1, ""}, 118 | {"t4", int32(1), 1, ""}, 119 | {"t5", int64(1), 1, ""}, 120 | {"t6", &a, 0, "cannot convert ptr to int64"}, 121 | {"t7", uint(1), 0, "cannot convert uint to int64"}, 122 | {"t8", float64(1), 0, "cannot convert float64 to int64"}, 123 | {"t9", "abc", 0, "cannot convert string to int64"}, 124 | {"t10", []int{1, 2}, 0, "cannot convert slice to int64"}, 125 | {"t11", map[string]int{"A": 1}, 0, "cannot convert map to int64"}, 126 | } 127 | 128 | for _, test := range tests { 129 | l, err := ToInt(test.value) 130 | assert.Equal(t, test.result, l, test.tag) 131 | assertError(t, test.err, err, test.tag) 132 | } 133 | } 134 | 135 | func TestToUint(t *testing.T) { 136 | var a int 137 | var b uint 138 | 139 | tests := []struct { 140 | tag string 141 | value interface{} 142 | result uint64 143 | err string 144 | }{ 145 | {"t1", uint(1), 1, ""}, 146 | {"t2", uint8(1), 1, ""}, 147 | {"t3", uint16(1), 1, ""}, 148 | {"t4", uint32(1), 1, ""}, 149 | {"t5", uint64(1), 1, ""}, 150 | {"t6", 1, 0, "cannot convert int to uint64"}, 151 | {"t7", &a, 0, "cannot convert ptr to uint64"}, 152 | {"t8", &b, 0, "cannot convert ptr to uint64"}, 153 | {"t9", float64(1), 0, "cannot convert float64 to uint64"}, 154 | {"t10", "abc", 0, "cannot convert string to uint64"}, 155 | {"t11", []int{1, 2}, 0, "cannot convert slice to uint64"}, 156 | {"t12", map[string]int{"A": 1}, 0, "cannot convert map to uint64"}, 157 | } 158 | 159 | for _, test := range tests { 160 | l, err := ToUint(test.value) 161 | assert.Equal(t, test.result, l, test.tag) 162 | assertError(t, test.err, err, test.tag) 163 | } 164 | } 165 | 166 | func TestToFloat(t *testing.T) { 167 | var a int 168 | var b uint 169 | 170 | tests := []struct { 171 | tag string 172 | value interface{} 173 | result float64 174 | err string 175 | }{ 176 | {"t1", float32(1), 1, ""}, 177 | {"t2", float64(1), 1, ""}, 178 | {"t3", 1, 0, "cannot convert int to float64"}, 179 | {"t4", uint(1), 0, "cannot convert uint to float64"}, 180 | {"t5", &a, 0, "cannot convert ptr to float64"}, 181 | {"t6", &b, 0, "cannot convert ptr to float64"}, 182 | {"t7", "abc", 0, "cannot convert string to float64"}, 183 | {"t8", []int{1, 2}, 0, "cannot convert slice to float64"}, 184 | {"t9", map[string]int{"A": 1}, 0, "cannot convert map to float64"}, 185 | } 186 | 187 | for _, test := range tests { 188 | l, err := ToFloat(test.value) 189 | assert.Equal(t, test.result, l, test.tag) 190 | assertError(t, test.err, err, test.tag) 191 | } 192 | } 193 | 194 | func TestIsEmpty(t *testing.T) { 195 | var s1 string 196 | var s2 = "a" 197 | var s3 *string 198 | s4 := struct{}{} 199 | time1 := time.Now() 200 | var time2 time.Time 201 | tests := []struct { 202 | tag string 203 | value interface{} 204 | empty bool 205 | }{ 206 | // nil 207 | {"t0", nil, true}, 208 | // string 209 | {"t1.1", "", true}, 210 | {"t1.2", "1", false}, 211 | {"t1.3", MyString(""), true}, 212 | {"t1.4", MyString("1"), false}, 213 | // slice 214 | {"t2.1", []byte(""), true}, 215 | {"t2.2", []byte("1"), false}, 216 | // map 217 | {"t3.1", map[string]int{}, true}, 218 | {"t3.2", map[string]int{"a": 1}, false}, 219 | // bool 220 | {"t4.1", false, true}, 221 | {"t4.2", true, false}, 222 | // int 223 | {"t5.1", 0, true}, 224 | {"t5.2", int8(0), true}, 225 | {"t5.3", int16(0), true}, 226 | {"t5.4", int32(0), true}, 227 | {"t5.5", int64(0), true}, 228 | {"t5.6", 1, false}, 229 | {"t5.7", int8(1), false}, 230 | {"t5.8", int16(1), false}, 231 | {"t5.9", int32(1), false}, 232 | {"t5.10", int64(1), false}, 233 | // uint 234 | {"t6.1", uint(0), true}, 235 | {"t6.2", uint8(0), true}, 236 | {"t6.3", uint16(0), true}, 237 | {"t6.4", uint32(0), true}, 238 | {"t6.5", uint64(0), true}, 239 | {"t6.6", uint(1), false}, 240 | {"t6.7", uint8(1), false}, 241 | {"t6.8", uint16(1), false}, 242 | {"t6.9", uint32(1), false}, 243 | {"t6.10", uint64(1), false}, 244 | // float 245 | {"t7.1", float32(0), true}, 246 | {"t7.2", float64(0), true}, 247 | {"t7.3", float32(1), false}, 248 | {"t7.4", float64(1), false}, 249 | // interface, ptr 250 | {"t8.1", &s1, true}, 251 | {"t8.2", &s2, false}, 252 | {"t8.3", s3, true}, 253 | // struct 254 | {"t9.1", s4, false}, 255 | {"t9.2", &s4, false}, 256 | // time.Time 257 | {"t10.1", time1, false}, 258 | {"t10.2", &time1, false}, 259 | {"t10.3", time2, true}, 260 | {"t10.4", &time2, true}, 261 | } 262 | 263 | for _, test := range tests { 264 | empty := IsEmpty(test.value) 265 | assert.Equal(t, test.empty, empty, test.tag) 266 | } 267 | } 268 | 269 | func TestIndirect(t *testing.T) { 270 | var a = 100 271 | var b *int 272 | var c *sql.NullInt64 273 | 274 | tests := []struct { 275 | tag string 276 | value interface{} 277 | result interface{} 278 | isNil bool 279 | }{ 280 | {"t1", 100, 100, false}, 281 | {"t2", &a, 100, false}, 282 | {"t3", b, nil, true}, 283 | {"t4", nil, nil, true}, 284 | {"t5", sql.NullInt64{Int64: 0, Valid: false}, nil, true}, 285 | {"t6", sql.NullInt64{Int64: 1, Valid: false}, nil, true}, 286 | {"t7", &sql.NullInt64{Int64: 0, Valid: false}, nil, true}, 287 | {"t8", &sql.NullInt64{Int64: 1, Valid: false}, nil, true}, 288 | {"t9", sql.NullInt64{Int64: 0, Valid: true}, int64(0), false}, 289 | {"t10", sql.NullInt64{Int64: 1, Valid: true}, int64(1), false}, 290 | {"t11", &sql.NullInt64{Int64: 0, Valid: true}, int64(0), false}, 291 | {"t12", &sql.NullInt64{Int64: 1, Valid: true}, int64(1), false}, 292 | {"t13", c, nil, true}, 293 | } 294 | 295 | for _, test := range tests { 296 | result, isNil := Indirect(test.value) 297 | assert.Equal(t, test.result, result, test.tag) 298 | assert.Equal(t, test.isNil, isNil, test.tag) 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /validation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package validation provides configurable and extensible rules for validating data of various types. 6 | package validation 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "reflect" 12 | "strconv" 13 | ) 14 | 15 | type ( 16 | // Validatable is the interface indicating the type implementing it supports data validation. 17 | Validatable interface { 18 | // Validate validates the data and returns an error if validation fails. 19 | Validate() error 20 | } 21 | 22 | // ValidatableWithContext is the interface indicating the type implementing it supports context-aware data validation. 23 | ValidatableWithContext interface { 24 | // ValidateWithContext validates the data with the given context and returns an error if validation fails. 25 | ValidateWithContext(ctx context.Context) error 26 | } 27 | 28 | // Rule represents a validation rule. 29 | Rule interface { 30 | // Validate validates a value and returns a value if validation fails. 31 | Validate(value interface{}) error 32 | } 33 | 34 | // RuleWithContext represents a context-aware validation rule. 35 | RuleWithContext interface { 36 | // ValidateWithContext validates a value and returns a value if validation fails. 37 | ValidateWithContext(ctx context.Context, value interface{}) error 38 | } 39 | 40 | // RuleFunc represents a validator function. 41 | // You may wrap it as a Rule by calling By(). 42 | RuleFunc func(value interface{}) error 43 | 44 | // RuleWithContextFunc represents a validator function that is context-aware. 45 | // You may wrap it as a Rule by calling WithContext(). 46 | RuleWithContextFunc func(ctx context.Context, value interface{}) error 47 | ) 48 | 49 | var ( 50 | // ErrorTag is the struct tag name used to customize the error field name for a struct field. 51 | ErrorTag = "json" 52 | 53 | // Skip is a special validation rule that indicates all rules following it should be skipped. 54 | Skip = skipRule{skip: true} 55 | 56 | validatableType = reflect.TypeOf((*Validatable)(nil)).Elem() 57 | validatableWithContextType = reflect.TypeOf((*ValidatableWithContext)(nil)).Elem() 58 | ) 59 | 60 | // Validate validates the given value and returns the validation error, if any. 61 | // 62 | // Validate performs validation using the following steps: 63 | // 1. For each rule, call its `Validate()` to validate the value. Return if any error is found. 64 | // 2. If the value being validated implements `Validatable`, call the value's `Validate()`. 65 | // Return with the validation result. 66 | // 3. If the value being validated is a map/slice/array, and the element type implements `Validatable`, 67 | // for each element call the element value's `Validate()`. Return with the validation result. 68 | func Validate(value interface{}, rules ...Rule) error { 69 | for _, rule := range rules { 70 | if s, ok := rule.(skipRule); ok && s.skip { 71 | return nil 72 | } 73 | if err := rule.Validate(value); err != nil { 74 | return err 75 | } 76 | } 77 | 78 | rv := reflect.ValueOf(value) 79 | if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() { 80 | return nil 81 | } 82 | 83 | if v, ok := value.(Validatable); ok { 84 | return v.Validate() 85 | } 86 | 87 | switch rv.Kind() { 88 | case reflect.Map: 89 | if rv.Type().Elem().Implements(validatableType) { 90 | return validateMap(rv) 91 | } 92 | case reflect.Slice, reflect.Array: 93 | if rv.Type().Elem().Implements(validatableType) { 94 | return validateSlice(rv) 95 | } 96 | case reflect.Ptr, reflect.Interface: 97 | return Validate(rv.Elem().Interface()) 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // ValidateWithContext validates the given value with the given context and returns the validation error, if any. 104 | // 105 | // ValidateWithContext performs validation using the following steps: 106 | // 1. For each rule, call its `ValidateWithContext()` to validate the value if the rule implements `RuleWithContext`. 107 | // Otherwise call `Validate()` of the rule. Return if any error is found. 108 | // 2. If the value being validated implements `ValidatableWithContext`, call the value's `ValidateWithContext()` 109 | // and return with the validation result. 110 | // 3. If the value being validated implements `Validatable`, call the value's `Validate()` 111 | // and return with the validation result. 112 | // 4. If the value being validated is a map/slice/array, and the element type implements `ValidatableWithContext`, 113 | // for each element call the element value's `ValidateWithContext()`. Return with the validation result. 114 | // 5. If the value being validated is a map/slice/array, and the element type implements `Validatable`, 115 | // for each element call the element value's `Validate()`. Return with the validation result. 116 | func ValidateWithContext(ctx context.Context, value interface{}, rules ...Rule) error { 117 | for _, rule := range rules { 118 | if s, ok := rule.(skipRule); ok && s.skip { 119 | return nil 120 | } 121 | if rc, ok := rule.(RuleWithContext); ok { 122 | if err := rc.ValidateWithContext(ctx, value); err != nil { 123 | return err 124 | } 125 | } else if err := rule.Validate(value); err != nil { 126 | return err 127 | } 128 | } 129 | 130 | rv := reflect.ValueOf(value) 131 | if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() { 132 | return nil 133 | } 134 | 135 | if v, ok := value.(ValidatableWithContext); ok { 136 | return v.ValidateWithContext(ctx) 137 | } 138 | 139 | if v, ok := value.(Validatable); ok { 140 | return v.Validate() 141 | } 142 | 143 | switch rv.Kind() { 144 | case reflect.Map: 145 | if rv.Type().Elem().Implements(validatableWithContextType) { 146 | return validateMapWithContext(ctx, rv) 147 | } 148 | if rv.Type().Elem().Implements(validatableType) { 149 | return validateMap(rv) 150 | } 151 | case reflect.Slice, reflect.Array: 152 | if rv.Type().Elem().Implements(validatableWithContextType) { 153 | return validateSliceWithContext(ctx, rv) 154 | } 155 | if rv.Type().Elem().Implements(validatableType) { 156 | return validateSlice(rv) 157 | } 158 | case reflect.Ptr, reflect.Interface: 159 | return ValidateWithContext(ctx, rv.Elem().Interface()) 160 | } 161 | 162 | return nil 163 | } 164 | 165 | // validateMap validates a map of validatable elements 166 | func validateMap(rv reflect.Value) error { 167 | errs := Errors{} 168 | for _, key := range rv.MapKeys() { 169 | if mv := rv.MapIndex(key).Interface(); mv != nil { 170 | if err := mv.(Validatable).Validate(); err != nil { 171 | errs[fmt.Sprintf("%v", key.Interface())] = err 172 | } 173 | } 174 | } 175 | if len(errs) > 0 { 176 | return errs 177 | } 178 | return nil 179 | } 180 | 181 | // validateMapWithContext validates a map of validatable elements with the given context. 182 | func validateMapWithContext(ctx context.Context, rv reflect.Value) error { 183 | errs := Errors{} 184 | for _, key := range rv.MapKeys() { 185 | if mv := rv.MapIndex(key).Interface(); mv != nil { 186 | if err := mv.(ValidatableWithContext).ValidateWithContext(ctx); err != nil { 187 | errs[fmt.Sprintf("%v", key.Interface())] = err 188 | } 189 | } 190 | } 191 | if len(errs) > 0 { 192 | return errs 193 | } 194 | return nil 195 | } 196 | 197 | // validateSlice validates a slice/array of validatable elements 198 | func validateSlice(rv reflect.Value) error { 199 | errs := Errors{} 200 | l := rv.Len() 201 | for i := 0; i < l; i++ { 202 | if ev := rv.Index(i).Interface(); ev != nil { 203 | if err := ev.(Validatable).Validate(); err != nil { 204 | errs[strconv.Itoa(i)] = err 205 | } 206 | } 207 | } 208 | if len(errs) > 0 { 209 | return errs 210 | } 211 | return nil 212 | } 213 | 214 | // validateSliceWithContext validates a slice/array of validatable elements with the given context. 215 | func validateSliceWithContext(ctx context.Context, rv reflect.Value) error { 216 | errs := Errors{} 217 | l := rv.Len() 218 | for i := 0; i < l; i++ { 219 | if ev := rv.Index(i).Interface(); ev != nil { 220 | if err := ev.(ValidatableWithContext).ValidateWithContext(ctx); err != nil { 221 | errs[strconv.Itoa(i)] = err 222 | } 223 | } 224 | } 225 | if len(errs) > 0 { 226 | return errs 227 | } 228 | return nil 229 | } 230 | 231 | type skipRule struct { 232 | skip bool 233 | } 234 | 235 | func (r skipRule) Validate(interface{}) error { 236 | return nil 237 | } 238 | 239 | // When determines if all rules following it should be skipped. 240 | func (r skipRule) When(condition bool) skipRule { 241 | r.skip = condition 242 | return r 243 | } 244 | 245 | type inlineRule struct { 246 | f RuleFunc 247 | fc RuleWithContextFunc 248 | } 249 | 250 | func (r *inlineRule) Validate(value interface{}) error { 251 | if r.f == nil { 252 | return r.fc(context.Background(), value) 253 | } 254 | return r.f(value) 255 | } 256 | 257 | func (r *inlineRule) ValidateWithContext(ctx context.Context, value interface{}) error { 258 | if r.fc == nil { 259 | return r.f(value) 260 | } 261 | return r.fc(ctx, value) 262 | } 263 | 264 | // By wraps a RuleFunc into a Rule. 265 | func By(f RuleFunc) Rule { 266 | return &inlineRule{f: f} 267 | } 268 | 269 | // WithContext wraps a RuleWithContextFunc into a context-aware Rule. 270 | func WithContext(f RuleWithContextFunc) Rule { 271 | return &inlineRule{fc: f} 272 | } 273 | -------------------------------------------------------------------------------- /validation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestValidate(t *testing.T) { 17 | slice := []String123{String123("abc"), String123("123"), String123("xyz")} 18 | ctxSlice := []Model4{{A: "abc"}, {A: "def"}} 19 | mp := map[string]String123{"c": String123("abc"), "b": String123("123"), "a": String123("xyz")} 20 | mpCtx := map[string]StringValidateContext{"c": StringValidateContext("abc"), "b": StringValidateContext("123"), "a": StringValidateContext("xyz")} 21 | var ( 22 | ptr *string 23 | noCtx StringValidate = "abc" 24 | withCtx StringValidateContext = "xyz" 25 | ) 26 | tests := []struct { 27 | tag string 28 | value interface{} 29 | err string 30 | errWithContext string 31 | }{ 32 | {"t1", 123, "", ""}, 33 | {"t2", String123("123"), "", ""}, 34 | {"t3", String123("abc"), "error 123", "error 123"}, 35 | {"t4", []String123{}, "", ""}, 36 | {"t4.1", []StringValidateContext{}, "", ""}, 37 | {"t4.2", map[string]StringValidateContext{}, "", ""}, 38 | {"t5", slice, "0: error 123; 2: error 123.", "0: error 123; 2: error 123."}, 39 | {"t6", &slice, "0: error 123; 2: error 123.", "0: error 123; 2: error 123."}, 40 | {"t7", ctxSlice, "", "1: (A: error abc.)."}, 41 | {"t8", mp, "a: error 123; c: error 123.", "a: error 123; c: error 123."}, 42 | {"t8.1", mpCtx, "a: must be abc; b: must be abc.", "a: must be abc with context; b: must be abc with context."}, 43 | {"t9", &mp, "a: error 123; c: error 123.", "a: error 123; c: error 123."}, 44 | {"t10", map[string]String123{}, "", ""}, 45 | {"t11", ptr, "", ""}, 46 | {"t12", noCtx, "called validate", "called validate"}, 47 | {"t13", withCtx, "must be abc", "must be abc with context"}, 48 | } 49 | for _, test := range tests { 50 | err := Validate(test.value) 51 | assertError(t, test.err, err, test.tag) 52 | // rules that are not context-aware should still be applied in context-aware validation 53 | err = ValidateWithContext(context.Background(), test.value) 54 | assertError(t, test.errWithContext, err, test.tag) 55 | } 56 | 57 | // with rules 58 | err := Validate("123", &validateAbc{}, &validateXyz{}) 59 | assert.EqualError(t, err, "error abc") 60 | err = Validate("abc", &validateAbc{}, &validateXyz{}) 61 | assert.EqualError(t, err, "error xyz") 62 | err = Validate("abcxyz", &validateAbc{}, &validateXyz{}) 63 | assert.NoError(t, err) 64 | 65 | err = Validate("123", &validateAbc{}, Skip, &validateXyz{}) 66 | assert.EqualError(t, err, "error abc") 67 | err = Validate("abc", &validateAbc{}, Skip, &validateXyz{}) 68 | assert.NoError(t, err) 69 | 70 | err = Validate("123", &validateAbc{}, Skip.When(true), &validateXyz{}) 71 | assert.EqualError(t, err, "error abc") 72 | err = Validate("abc", &validateAbc{}, Skip.When(true), &validateXyz{}) 73 | assert.NoError(t, err) 74 | 75 | err = Validate("123", &validateAbc{}, Skip.When(false), &validateXyz{}) 76 | assert.EqualError(t, err, "error abc") 77 | err = Validate("abc", &validateAbc{}, Skip.When(false), &validateXyz{}) 78 | assert.EqualError(t, err, "error xyz") 79 | } 80 | 81 | func stringEqual(str string) RuleFunc { 82 | return func(value interface{}) error { 83 | s, _ := value.(string) 84 | if s != str { 85 | return errors.New("unexpected string") 86 | } 87 | return nil 88 | } 89 | } 90 | 91 | func TestBy(t *testing.T) { 92 | abcRule := By(func(value interface{}) error { 93 | s, _ := value.(string) 94 | if s != "abc" { 95 | return errors.New("must be abc") 96 | } 97 | return nil 98 | }) 99 | assert.Nil(t, Validate("abc", abcRule)) 100 | err := Validate("xyz", abcRule) 101 | if assert.NotNil(t, err) { 102 | assert.Equal(t, "must be abc", err.Error()) 103 | } 104 | 105 | xyzRule := By(stringEqual("xyz")) 106 | assert.Nil(t, Validate("xyz", xyzRule)) 107 | assert.NotNil(t, Validate("abc", xyzRule)) 108 | assert.Nil(t, ValidateWithContext(context.Background(), "xyz", xyzRule)) 109 | assert.NotNil(t, ValidateWithContext(context.Background(), "abc", xyzRule)) 110 | } 111 | 112 | type key int 113 | 114 | func TestByWithContext(t *testing.T) { 115 | k := key(1) 116 | abcRule := WithContext(func(ctx context.Context, value interface{}) error { 117 | if ctx.Value(k) != value.(string) { 118 | return errors.New("must be abc") 119 | } 120 | return nil 121 | }) 122 | ctx := context.WithValue(context.Background(), k, "abc") 123 | assert.Nil(t, ValidateWithContext(ctx, "abc", abcRule)) 124 | err := ValidateWithContext(ctx, "xyz", abcRule) 125 | if assert.NotNil(t, err) { 126 | assert.Equal(t, "must be abc", err.Error()) 127 | } 128 | 129 | assert.NotNil(t, Validate("abc", abcRule)) 130 | } 131 | 132 | func Test_skipRule_Validate(t *testing.T) { 133 | assert.Nil(t, Skip.Validate(100)) 134 | } 135 | 136 | func assertError(t *testing.T, expected string, err error, tag string) { 137 | if expected == "" { 138 | assert.NoError(t, err, tag) 139 | } else { 140 | assert.EqualError(t, err, expected, tag) 141 | } 142 | } 143 | 144 | type validateAbc struct{} 145 | 146 | func (v *validateAbc) Validate(obj interface{}) error { 147 | if !strings.Contains(obj.(string), "abc") { 148 | return errors.New("error abc") 149 | } 150 | return nil 151 | } 152 | 153 | type validateContextAbc struct{} 154 | 155 | func (v *validateContextAbc) Validate(obj interface{}) error { 156 | return v.ValidateWithContext(context.Background(), obj) 157 | } 158 | 159 | func (v *validateContextAbc) ValidateWithContext(_ context.Context, obj interface{}) error { 160 | if !strings.Contains(obj.(string), "abc") { 161 | return errors.New("error abc") 162 | } 163 | return nil 164 | } 165 | 166 | type validateXyz struct{} 167 | 168 | func (v *validateXyz) Validate(obj interface{}) error { 169 | if !strings.Contains(obj.(string), "xyz") { 170 | return errors.New("error xyz") 171 | } 172 | return nil 173 | } 174 | 175 | type validateContextXyz struct{} 176 | 177 | func (v *validateContextXyz) Validate(obj interface{}) error { 178 | return v.ValidateWithContext(context.Background(), obj) 179 | } 180 | 181 | func (v *validateContextXyz) ValidateWithContext(_ context.Context, obj interface{}) error { 182 | if !strings.Contains(obj.(string), "xyz") { 183 | return errors.New("error xyz") 184 | } 185 | return nil 186 | } 187 | 188 | type validateInternalError struct{} 189 | 190 | func (v *validateInternalError) Validate(obj interface{}) error { 191 | if strings.Contains(obj.(string), "internal") { 192 | return NewInternalError(errors.New("error internal")) 193 | } 194 | return nil 195 | } 196 | 197 | type Model1 struct { 198 | A string 199 | B string 200 | c string 201 | D *string 202 | E String123 203 | F *String123 204 | G string `json:"g"` 205 | H []string 206 | I map[string]string 207 | } 208 | 209 | type String123 string 210 | 211 | func (s String123) Validate() error { 212 | if !strings.Contains(string(s), "123") { 213 | return errors.New("error 123") 214 | } 215 | return nil 216 | } 217 | 218 | type Model2 struct { 219 | Model3 220 | M3 Model3 221 | B string 222 | } 223 | 224 | type Model3 struct { 225 | A string 226 | } 227 | 228 | func (m Model3) Validate() error { 229 | return ValidateStruct(&m, 230 | Field(&m.A, &validateAbc{}), 231 | ) 232 | } 233 | 234 | type Model4 struct { 235 | A string 236 | } 237 | 238 | func (m Model4) ValidateWithContext(ctx context.Context) error { 239 | return ValidateStructWithContext(ctx, &m, 240 | Field(&m.A, &validateContextAbc{}), 241 | ) 242 | } 243 | 244 | type Model5 struct { 245 | Model4 246 | M4 Model4 247 | B string 248 | } 249 | 250 | type StringValidate string 251 | 252 | func (s StringValidate) Validate() error { 253 | return errors.New("called validate") 254 | } 255 | 256 | type StringValidateContext string 257 | 258 | func (s StringValidateContext) Validate() error { 259 | if string(s) != "abc" { 260 | return errors.New("must be abc") 261 | } 262 | return nil 263 | } 264 | 265 | func (s StringValidateContext) ValidateWithContext(context.Context) error { 266 | if string(s) != "abc" { 267 | return errors.New("must be abc with context") 268 | } 269 | return nil 270 | } 271 | -------------------------------------------------------------------------------- /when.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import "context" 4 | 5 | // When returns a validation rule that executes the given list of rules when the condition is true. 6 | func When(condition bool, rules ...Rule) WhenRule { 7 | return WhenRule{ 8 | condition: condition, 9 | rules: rules, 10 | elseRules: []Rule{}, 11 | } 12 | } 13 | 14 | // WhenRule is a validation rule that executes the given list of rules when the condition is true. 15 | type WhenRule struct { 16 | condition bool 17 | rules []Rule 18 | elseRules []Rule 19 | } 20 | 21 | // Validate checks if the condition is true and if so, it validates the value using the specified rules. 22 | func (r WhenRule) Validate(value interface{}) error { 23 | return r.ValidateWithContext(nil, value) 24 | } 25 | 26 | // ValidateWithContext checks if the condition is true and if so, it validates the value using the specified rules. 27 | func (r WhenRule) ValidateWithContext(ctx context.Context, value interface{}) error { 28 | if r.condition { 29 | if ctx == nil { 30 | return Validate(value, r.rules...) 31 | } 32 | return ValidateWithContext(ctx, value, r.rules...) 33 | } 34 | 35 | if ctx == nil { 36 | return Validate(value, r.elseRules...) 37 | } 38 | return ValidateWithContext(ctx, value, r.elseRules...) 39 | } 40 | 41 | // Else returns a validation rule that executes the given list of rules when the condition is false. 42 | func (r WhenRule) Else(rules ...Rule) WhenRule { 43 | r.elseRules = rules 44 | return r 45 | } 46 | -------------------------------------------------------------------------------- /when_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Qiang Xue. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package validation 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func abcValidation(val string) bool { 15 | return val == "abc" 16 | } 17 | 18 | func TestWhen(t *testing.T) { 19 | abcRule := NewStringRule(abcValidation, "wrong_abc") 20 | validateMeRule := NewStringRule(validateMe, "wrong_me") 21 | 22 | tests := []struct { 23 | tag string 24 | condition bool 25 | value interface{} 26 | rules []Rule 27 | elseRules []Rule 28 | err string 29 | }{ 30 | // True condition 31 | {"t1.1", true, nil, []Rule{}, []Rule{}, ""}, 32 | {"t1.2", true, "", []Rule{}, []Rule{}, ""}, 33 | {"t1.3", true, "", []Rule{abcRule}, []Rule{}, ""}, 34 | {"t1.4", true, 12, []Rule{Required}, []Rule{}, ""}, 35 | {"t1.5", true, nil, []Rule{Required}, []Rule{}, "cannot be blank"}, 36 | {"t1.6", true, "123", []Rule{abcRule}, []Rule{}, "wrong_abc"}, 37 | {"t1.7", true, "abc", []Rule{abcRule}, []Rule{}, ""}, 38 | {"t1.8", true, "abc", []Rule{abcRule, abcRule}, []Rule{}, ""}, 39 | {"t1.9", true, "abc", []Rule{abcRule, validateMeRule}, []Rule{}, "wrong_me"}, 40 | {"t1.10", true, "me", []Rule{abcRule, validateMeRule}, []Rule{}, "wrong_abc"}, 41 | {"t1.11", true, "me", []Rule{}, []Rule{abcRule}, ""}, 42 | 43 | // False condition 44 | {"t2.1", false, "", []Rule{}, []Rule{}, ""}, 45 | {"t2.2", false, "", []Rule{abcRule}, []Rule{}, ""}, 46 | {"t2.3", false, "abc", []Rule{abcRule}, []Rule{}, ""}, 47 | {"t2.4", false, "abc", []Rule{abcRule, abcRule}, []Rule{}, ""}, 48 | {"t2.5", false, "abc", []Rule{abcRule, validateMeRule}, []Rule{}, ""}, 49 | {"t2.6", false, "me", []Rule{abcRule, validateMeRule}, []Rule{}, ""}, 50 | {"t2.7", false, "", []Rule{abcRule, validateMeRule}, []Rule{}, ""}, 51 | {"t2.8", false, "me", []Rule{}, []Rule{abcRule, validateMeRule}, "wrong_abc"}, 52 | } 53 | 54 | for _, test := range tests { 55 | err := Validate(test.value, When(test.condition, test.rules...).Else(test.elseRules...)) 56 | assertError(t, test.err, err, test.tag) 57 | } 58 | } 59 | 60 | type ctxKey int 61 | 62 | const ( 63 | contains ctxKey = iota 64 | ) 65 | 66 | func TestWhenWithContext(t *testing.T) { 67 | rule := WithContext(func(ctx context.Context, value interface{}) error { 68 | if !strings.Contains(value.(string), ctx.Value(contains).(string)) { 69 | return errors.New("unexpected value") 70 | } 71 | return nil 72 | }) 73 | ctx1 := context.WithValue(context.Background(), contains, "abc") 74 | ctx2 := context.WithValue(context.Background(), contains, "xyz") 75 | 76 | tests := []struct { 77 | tag string 78 | condition bool 79 | value interface{} 80 | ctx context.Context 81 | err string 82 | }{ 83 | // True condition 84 | {"t1.1", true, "abc", ctx1, ""}, 85 | {"t1.2", true, "abc", ctx2, "unexpected value"}, 86 | {"t1.3", true, "xyz", ctx1, "unexpected value"}, 87 | {"t1.4", true, "xyz", ctx2, ""}, 88 | 89 | // False condition 90 | {"t2.1", false, "abc", ctx1, ""}, 91 | {"t2.2", false, "abc", ctx2, "unexpected value"}, 92 | {"t2.3", false, "xyz", ctx1, "unexpected value"}, 93 | {"t2.4", false, "xyz", ctx2, ""}, 94 | } 95 | 96 | for _, test := range tests { 97 | err := ValidateWithContext(test.ctx, test.value, When(test.condition, rule).Else(rule)) 98 | assertError(t, test.err, err, test.tag) 99 | } 100 | } 101 | --------------------------------------------------------------------------------