pax_global_header00006660000000000000000000000064137432752430014524gustar00rootroot0000000000000052 comment=c0d3bb3ac5f43ebb9fc22eaeb7d8c07836b7973b ozzo-validation-4.3.0/000077500000000000000000000000001374327524300146615ustar00rootroot00000000000000ozzo-validation-4.3.0/.gitignore000066400000000000000000000004541374327524300166540ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Idea Directory .idea # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof .DS_Store ozzo-validation-4.3.0/.travis.yml000066400000000000000000000005631374327524300167760ustar00rootroot00000000000000dist: bionic language: go go: - 1.13.x install: - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls - go get golang.org/x/lint/golint script: - test -z "`gofmt -l -d .`" - test -z "`golint ./...`" - go test -v -covermode=count -coverprofile=coverage.out - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci ozzo-validation-4.3.0/LICENSE000066400000000000000000000020641374327524300156700ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016, Qiang Xue Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ozzo-validation-4.3.0/README.md000066400000000000000000000716771374327524300161620ustar00rootroot00000000000000# ozzo-validation [![GoDoc](https://godoc.org/github.com/go-ozzo/ozzo-validation?status.png)](http://godoc.org/github.com/go-ozzo/ozzo-validation) [![Build Status](https://travis-ci.org/go-ozzo/ozzo-validation.svg?branch=master)](https://travis-ci.org/go-ozzo/ozzo-validation) [![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) [![Go Report](https://goreportcard.com/badge/github.com/go-ozzo/ozzo-validation)](https://goreportcard.com/report/github.com/go-ozzo/ozzo-validation) ## Description ozzo-validation is a Go package that provides configurable and extensible data validation capabilities. It has the following features: * use normal programming constructs rather than error-prone struct tags to specify how data should be validated. * can validate data of different types, e.g., structs, strings, byte slices, slices, maps, arrays. * can validate custom data types as long as they implement the `Validatable` interface. * can validate data types that implement the `sql.Valuer` interface (e.g. `sql.NullString`). * customizable and well-formatted validation errors. * error code and message translation support. * provide a rich set of validation rules right out of box. * extremely easy to create and use custom validation rules. 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. ## Requirements Go 1.13 or above. ## Getting Started The ozzo-validation package mainly includes a set of validation rules and two validation methods. You use validation rules to describe how a value should be considered valid, and you call either `validation.Validate()` or `validation.ValidateStruct()` to validate the value. ### Installation Run the following command to install the package: ``` go get github.com/go-ozzo/ozzo-validation ``` ### Validating a Simple Value For a simple value, such as a string or an integer, you may use `validation.Validate()` to validate it. For example, ```go package main import ( "fmt" "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" ) func main() { data := "example" err := validation.Validate(data, validation.Required, // not empty validation.Length(5, 100), // length between 5 and 100 is.URL, // is a valid URL ) fmt.Println(err) // Output: // must be a valid URL } ``` The method `validation.Validate()` will run through the rules in the order that they are listed. If a rule fails the validation, the method will return the corresponding error and skip the rest of the rules. The method will return nil if the value passes all validation rules. ### Validating a Struct For a struct value, you usually want to check if its fields are valid. For example, in a RESTful application, you may unmarshal the request payload into a struct and then validate the struct fields. If one or multiple fields are invalid, you may want to get an error describing which fields are invalid. You can use `validation.ValidateStruct()` to achieve this purpose. A single struct can have rules for multiple fields, and a field can be associated with multiple rules. For example, ```go type Address struct { Street string City string State string Zip string } func (a Address) Validate() error { return validation.ValidateStruct(&a, // Street cannot be empty, and the length must between 5 and 50 validation.Field(&a.Street, validation.Required, validation.Length(5, 50)), // City cannot be empty, and the length must between 5 and 50 validation.Field(&a.City, validation.Required, validation.Length(5, 50)), // State cannot be empty, and must be a string consisting of two letters in upper case validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), // State cannot be empty, and must be a string consisting of five digits validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), ) } a := Address{ Street: "123", City: "Unknown", State: "Virginia", Zip: "12345", } err := a.Validate() fmt.Println(err) // Output: // Street: the length must be between 5 and 50; State: must be in a valid format. ``` Note that when calling `validation.ValidateStruct` to validate a struct, you should pass to the method a pointer to the struct instead of the struct itself. Similarly, when calling `validation.Field` to specify the rules for a struct field, you should use a pointer to the struct field. When the struct validation is performed, the fields are validated in the order they are specified in `ValidateStruct`. And when each field is validated, its rules are also evaluated in the order they are associated with the field. If a rule fails, an error is recorded for that field, and the validation will continue with the next field. ### Validating a Map Sometimes you might need to work with dynamic data stored in maps rather than a typed model. You can use `validation.Map()` in this situation. A single map can have rules for multiple keys, and a key can be associated with multiple rules. For example, ```go c := map[string]interface{}{ "Name": "Qiang Xue", "Email": "q", "Address": map[string]interface{}{ "Street": "123", "City": "Unknown", "State": "Virginia", "Zip": "12345", }, } err := validation.Validate(c, validation.Map( // Name cannot be empty, and the length must be between 5 and 20. validation.Key("Name", validation.Required, validation.Length(5, 20)), // Email cannot be empty and should be in a valid email format. validation.Key("Email", validation.Required, is.Email), // Validate Address using its own validation rules validation.Key("Address", validation.Map( // Street cannot be empty, and the length must between 5 and 50 validation.Key("Street", validation.Required, validation.Length(5, 50)), // City cannot be empty, and the length must between 5 and 50 validation.Key("City", validation.Required, validation.Length(5, 50)), // State cannot be empty, and must be a string consisting of two letters in upper case validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), // State cannot be empty, and must be a string consisting of five digits validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), )), ), ) fmt.Println(err) // Output: // Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address. ``` When the map validation is performed, the keys are validated in the order they are specified in `Map`. And when each key is validated, its rules are also evaluated in the order they are associated with the key. If a rule fails, an error is recorded for that key, and the validation will continue with the next key. ### Validation Errors The `validation.ValidateStruct` method returns validation errors found in struct fields in terms of `validation.Errors` which is a map of fields and their corresponding errors. Nil is returned if validation passes. By default, `validation.Errors` uses the struct tags named `json` to determine what names should be used to represent the invalid fields. The type also implements the `json.Marshaler` interface so that it can be marshaled into a proper JSON object. For example, ```go type Address struct { Street string `json:"street"` City string `json:"city"` State string `json:"state"` Zip string `json:"zip"` } // ...perform validation here... err := a.Validate() b, _ := json.Marshal(err) fmt.Println(string(b)) // Output: // {"street":"the length must be between 5 and 50","state":"must be in a valid format"} ``` You may modify `validation.ErrorTag` to use a different struct tag name. If you do not like the magic that `ValidateStruct` determines error keys based on struct field names or corresponding tag values, you may use the following alternative approach: ```go c := Customer{ Name: "Qiang Xue", Email: "q", Address: Address{ State: "Virginia", }, } err := validation.Errors{ "name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)), "email": validation.Validate(c.Name, validation.Required, is.Email), "zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), }.Filter() fmt.Println(err) // Output: // email: must be a valid email address; zip: cannot be blank. ``` In the above example, we build a `validation.Errors` by a list of names and the corresponding validation results. At the end we call `Errors.Filter()` to remove from `Errors` all nils which correspond to those successful validation results. The method will return nil if `Errors` is empty. The above approach is very flexible as it allows you to freely build up your validation error structure. You can use it to validate both struct and non-struct values. Compared to using `ValidateStruct` to validate a struct, it has the drawback that you have to redundantly specify the error keys while `ValidateStruct` can automatically find them out. ### Internal Errors Internal errors are different from validation errors in that internal errors are caused by malfunctioning code (e.g. a validator making a remote call to validate some data when the remote service is down) rather than the data being validated. When an internal error happens during data validation, you may allow the user to resubmit the same data to perform validation again, hoping the program resumes functioning. On the other hand, if data validation fails due to data error, the user should generally not resubmit the same data again. To differentiate internal errors from validation errors, when an internal error occurs in a validator, wrap it into `validation.InternalError` by calling `validation.NewInternalError()`. The user of the validator can then check if a returned error is an internal error or not. For example, ```go if err := a.Validate(); err != nil { if e, ok := err.(validation.InternalError); ok { // an internal error happened fmt.Println(e.InternalError()) } } ``` ## Validatable Types A type is validatable if it implements the `validation.Validatable` interface. When `validation.Validate` is used to validate a validatable value, if it does not find any error with the given validation rules, it will further call the value's `Validate()` method. Similarly, when `validation.ValidateStruct` is validating a struct field whose type is validatable, it will call the field's `Validate` method after it passes the listed rules. > Note: When implementing `validation.Validatable`, do not call `validation.Validate()` to validate the value in its > original type because this will cause infinite loops. For example, if you define a new type `MyString` as `string` > and implement `validation.Validatable` for `MyString`, within the `Validate()` function you should cast the value > to `string` first before calling `validation.Validate()` to validate it. In the following example, the `Address` field of `Customer` is validatable because `Address` implements `validation.Validatable`. Therefore, when validating a `Customer` struct with `validation.ValidateStruct`, validation will "dive" into the `Address` field. ```go type Customer struct { Name string Gender string Email string Address Address } func (c Customer) Validate() error { return validation.ValidateStruct(&c, // Name cannot be empty, and the length must be between 5 and 20. validation.Field(&c.Name, validation.Required, validation.Length(5, 20)), // Gender is optional, and should be either "Female" or "Male". validation.Field(&c.Gender, validation.In("Female", "Male")), // Email cannot be empty and should be in a valid email format. validation.Field(&c.Email, validation.Required, is.Email), // Validate Address using its own validation rules validation.Field(&c.Address), ) } c := Customer{ Name: "Qiang Xue", Email: "q", Address: Address{ Street: "123 Main Street", City: "Unknown", State: "Virginia", Zip: "12345", }, } err := c.Validate() fmt.Println(err) // Output: // Address: (State: must be in a valid format.); Email: must be a valid email address. ``` Sometimes, you may want to skip the invocation of a type's `Validate` method. To do so, simply associate a `validation.Skip` rule with the value being validated. ### Maps/Slices/Arrays of Validatables When validating an iterable (map, slice, or array), whose element type implements the `validation.Validatable` interface, the `validation.Validate` method will call the `Validate` method of every non-nil element. The validation errors of the elements will be returned as `validation.Errors` which maps the keys of the invalid elements to their corresponding validation errors. For example, ```go addresses := []Address{ Address{State: "MD", Zip: "12345"}, Address{Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"}, Address{City: "Unknown", State: "NC", Zip: "123"}, } err := validation.Validate(addresses) fmt.Println(err) // Output: // 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.). ``` When using `validation.ValidateStruct` to validate a struct, the above validation procedure also applies to those struct fields which are map/slices/arrays of validatables. #### Each The `Each` validation rule allows you to apply a set of rules to each element of an array, slice, or map. ```go type Customer struct { Name string Emails []string } func (c Customer) Validate() error { return validation.ValidateStruct(&c, // Name cannot be empty, and the length must be between 5 and 20. validation.Field(&c.Name, validation.Required, validation.Length(5, 20)), // Emails are optional, but if given must be valid. validation.Field(&c.Emails, validation.Each(is.Email)), ) } c := Customer{ Name: "Qiang Xue", Emails: []Email{ "valid@example.com", "invalid", }, } err := c.Validate() fmt.Println(err) // Output: // Emails: (1: must be a valid email address.). ``` ### Pointers When a value being validated is a pointer, most validation rules will validate the actual value pointed to by the pointer. If the pointer is nil, these rules will skip the validation. An exception is the `validation.Required` and `validation.NotNil` rules. When a pointer is nil, they will report a validation error. ### Types Implementing `sql.Valuer` If a data type implements the `sql.Valuer` interface (e.g. `sql.NullString`), the built-in validation rules will handle it properly. In particular, when a rule is validating such data, it will call the `Value()` method and validate the returned value instead. ### Required vs. Not Nil When validating input values, there are two different scenarios about checking if input values are provided or not. In the first scenario, an input value is considered missing if it is not entered or it is entered as a zero value (e.g. an empty string, a zero integer). You can use the `validation.Required` rule in this case. In the second scenario, an input value is considered missing only if it is not entered. A pointer field is usually 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. You can use the `validation.NotNil` rule to ensure a value is entered (even if it is a zero value). ### Embedded Structs The `validation.ValidateStruct` method will properly validate a struct that contains embedded structs. In particular, the fields of an embedded struct are treated as if they belong directly to the containing struct. For example, ```go type Employee struct { Name string } type Manager struct { Employee Level int } m := Manager{} err := validation.ValidateStruct(&m, validation.Field(&m.Name, validation.Required), validation.Field(&m.Level, validation.Required), ) fmt.Println(err) // Output: // Level: cannot be blank; Name: cannot be blank. ``` In the above code, we use `&m.Name` to specify the validation of the `Name` field of the embedded struct `Employee`. And the validation error uses `Name` as the key for the error associated with the `Name` field as if `Name` a field directly belonging to `Manager`. If `Employee` implements the `validation.Validatable` interface, we can also use the following code to validate `Manager`, which generates the same validation result: ```go func (e Employee) Validate() error { return validation.ValidateStruct(&e, validation.Field(&e.Name, validation.Required), ) } err := validation.ValidateStruct(&m, validation.Field(&m.Employee), validation.Field(&m.Level, validation.Required), ) fmt.Println(err) // Output: // Level: cannot be blank; Name: cannot be blank. ``` ### Conditional Validation Sometimes, we may want to validate a value only when certain condition is met. For example, we want to ensure the `unit` struct field is not empty only when the `quantity` field is not empty; or we may want to ensure either `email` or `phone` is provided. The so-called conditional validation can be achieved with the help of `validation.When`. The following code implements the aforementioned examples: ```go result := validation.ValidateStruct(&a, validation.Field(&a.Unit, validation.When(a.Quantity != "", validation.Required).Else(validation.Nil)), validation.Field(&a.Phone, validation.When(a.Email == "", validation.Required.Error('Either phone or Email is required.')), validation.Field(&a.Email, validation.When(a.Phone == "", validation.Required.Error('Either phone or Email is required.')), ) ``` 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). The above code can also be simplified using the shortcut `validation.Required.When`: ```go result := validation.ValidateStruct(&a, validation.Field(&a.Unit, validation.Required.When(a.Quantity != ""), validation.Nil.When(a.Quantity == "")), validation.Field(&a.Phone, validation.Required.When(a.Email == "").Error('Either phone or Email is required.')), validation.Field(&a.Email, validation.Required.When(a.Phone == "").Error('Either phone or Email is required.')), ) ``` ### Customizing Error Messages All built-in validation rules allow you to customize their error messages. To do so, simply call the `Error()` method of the rules. For example, ```go data := "2123" err := validation.Validate(data, validation.Required.Error("is required"), validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("must be a string with five digits"), ) fmt.Println(err) // Output: // must be a string with five digits ``` You can also customize the pre-defined error(s) of a built-in rule such that the customization applies to *every* instance of the rule. For example, the `Required` rule uses the pre-defined error `ErrRequired`. You can customize it during the application initialization: ```go validation.ErrRequired = validation.ErrRequired.SetMessage("the value is required") ``` ### Error Code and Message Translation The errors returned by the validation rules implement the `Error` interface which contains the `Code()` method to provide the error code information. While the message of a validation error is often customized, the code is immutable. You can use error code to programmatically check a validation error or look for the translation of the corresponding message. If you are developing your own validation rules, you can use `validation.NewError()` to create a validation error which implements the aforementioned `Error` interface. ## Creating Custom Rules Creating a custom rule is as simple as implementing the `validation.Rule` interface. The interface contains a single method as shown below, which should validate the value and return the validation error, if any: ```go // Validate validates a value and returns an error if validation fails. Validate(value interface{}) error ``` If you already have a function with the same signature as shown above, you can call `validation.By()` to turn it into a validation rule. For example, ```go func checkAbc(value interface{}) error { s, _ := value.(string) if s != "abc" { return errors.New("must be abc") } return nil } err := validation.Validate("xyz", validation.By(checkAbc)) fmt.Println(err) // Output: must be abc ``` If your validation function takes additional parameters, you can use the following closure trick: ```go func stringEquals(str string) validation.RuleFunc { return func(value interface{}) error { s, _ := value.(string) if s != str { return errors.New("unexpected string") } return nil } } err := validation.Validate("xyz", validation.By(stringEquals("abc"))) fmt.Println(err) // Output: unexpected string ``` ### Rule Groups When a combination of several rules are used in multiple places, you may use the following trick to create a rule group so that your code is more maintainable. ```go var NameRule = []validation.Rule{ validation.Required, validation.Length(5, 20), } type User struct { FirstName string LastName string } func (u User) Validate() error { return validation.ValidateStruct(&u, validation.Field(&u.FirstName, NameRule...), validation.Field(&u.LastName, NameRule...), ) } ``` In the above example, we create a rule group `NameRule` which consists of two validation rules. We then use this rule group to validate both `FirstName` and `LastName`. ## Context-aware Validation While most validation rules are self-contained, some rules may depend dynamically on a context. A rule may implement the `validation.RuleWithContext` interface to support the so-called context-aware validation. To validate an arbitrary value with a context, call `validation.ValidateWithContext()`. The `context.Conext` parameter will be passed along to those rules that implement `validation.RuleWithContext`. To validate the fields of a struct with a context, call `validation.ValidateStructWithContext()`. You can define a context-aware rule from scratch by implementing both `validation.Rule` and `validation.RuleWithContext`. You can also use `validation.WithContext()` to turn a function into a context-aware rule. For example, ```go rule := validation.WithContext(func(ctx context.Context, value interface{}) error { if ctx.Value("secret") == value.(string) { return nil } return errors.New("value incorrect") }) value := "xyz" ctx := context.WithValue(context.Background(), "secret", "example") err := validation.ValidateWithContext(ctx, value, rule) fmt.Println(err) // Output: value incorrect ``` When performing context-aware validation, if a rule does not implement `validation.RuleWithContext`, its `validation.Rule` will be used instead. ## Built-in Validation Rules The following rules are provided in the `validation` package: * `In(...interface{})`: checks if a value can be found in the given list of values. * `NotIn(...interface{})`: checks if a value is NOT among the given list of values. * `Length(min, max int)`: checks if the length of a value is within the specified range. This rule should only be used for validating strings, slices, maps, and arrays. * `RuneLength(min, max int)`: checks if the length of a string is within the specified range. This rule is similar as `Length` except that when the value being validated is a string, it checks its rune length instead of byte length. * `Min(min interface{})` and `Max(max interface{})`: checks if a value is within the specified range. These two rules should only be used for validating int, uint, float and time.Time types. * `Match(*regexp.Regexp)`: checks if a value matches the specified regular expression. This rule should only be used for strings and byte slices. * `Date(layout string)`: checks if a string value is a date whose format is specified by the layout. By calling `Min()` and/or `Max()`, you can check additionally if the date is within the specified range. * `Required`: checks if a value is not empty (neither nil nor zero). * `NotNil`: checks if a pointer value is not nil. Non-pointer values are considered valid. * `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. * `Nil`: checks if a value is a nil pointer. * `Empty`: checks if a value is empty. nil pointers are considered valid. * `Skip`: this is a special rule used to indicate that all rules following it should be skipped (including the nested ones). * `MultipleOf`: checks if the value is a multiple of the specified range. * `Each(rules ...Rule)`: checks the elements within an iterable (map/slice/array) with other rules. * `When(condition, rules ...Rule)`: validates with the specified rules only when the condition is true. * `Else(rules ...Rule)`: must be used with `When(condition, rules ...Rule)`, validates with the specified rules only when the condition is false. The `is` sub-package provides a list of commonly used string validation rules that can be used to check if the format of a value satisfies certain requirements. Note that these rules only handle strings and byte slices and if a string or byte slice is empty, it is considered valid. You may use a `Required` rule to ensure a value is not empty. Below is the whole list of the rules provided by the `is` package: * `Email`: validates if a string is an email or not. It also checks if the MX record exists for the email domain. * `EmailFormat`: validates if a string is an email or not. It does NOT check the existence of the MX record. * `URL`: validates if a string is a valid URL * `RequestURL`: validates if a string is a valid request URL * `RequestURI`: validates if a string is a valid request URI * `Alpha`: validates if a string contains English letters only (a-zA-Z) * `Digit`: validates if a string contains digits only (0-9) * `Alphanumeric`: validates if a string contains English letters and digits only (a-zA-Z0-9) * `UTFLetter`: validates if a string contains unicode letters only * `UTFDigit`: validates if a string contains unicode decimal digits only * `UTFLetterNumeric`: validates if a string contains unicode letters and numbers only * `UTFNumeric`: validates if a string contains unicode number characters (category N) only * `LowerCase`: validates if a string contains lower case unicode letters only * `UpperCase`: validates if a string contains upper case unicode letters only * `Hexadecimal`: validates if a string is a valid hexadecimal number * `HexColor`: validates if a string is a valid hexadecimal color code * `RGBColor`: validates if a string is a valid RGB color in the form of rgb(R, G, B) * `Int`: validates if a string is a valid integer number * `Float`: validates if a string is a floating point number * `UUIDv3`: validates if a string is a valid version 3 UUID * `UUIDv4`: validates if a string is a valid version 4 UUID * `UUIDv5`: validates if a string is a valid version 5 UUID * `UUID`: validates if a string is a valid UUID * `CreditCard`: validates if a string is a valid credit card number * `ISBN10`: validates if a string is an ISBN version 10 * `ISBN13`: validates if a string is an ISBN version 13 * `ISBN`: validates if a string is an ISBN (either version 10 or 13) * `JSON`: validates if a string is in valid JSON format * `ASCII`: validates if a string contains ASCII characters only * `PrintableASCII`: validates if a string contains printable ASCII characters only * `Multibyte`: validates if a string contains multibyte characters * `FullWidth`: validates if a string contains full-width characters * `HalfWidth`: validates if a string contains half-width characters * `VariableWidth`: validates if a string contains both full-width and half-width characters * `Base64`: validates if a string is encoded in Base64 * `DataURI`: validates if a string is a valid base64-encoded data URI * `E164`: validates if a string is a valid E164 phone number (+19251232233) * `CountryCode2`: validates if a string is a valid ISO3166 Alpha 2 country code * `CountryCode3`: validates if a string is a valid ISO3166 Alpha 3 country code * `DialString`: validates if a string is a valid dial string that can be passed to Dial() * `MAC`: validates if a string is a MAC address * `IP`: validates if a string is a valid IP address (either version 4 or 6) * `IPv4`: validates if a string is a valid version 4 IP address * `IPv6`: validates if a string is a valid version 6 IP address * `Subdomain`: validates if a string is valid subdomain * `Domain`: validates if a string is valid domain * `DNSName`: validates if a string is valid DNS name * `Host`: validates if a string is a valid IP (both v4 and v6) or a valid DNS name * `Port`: validates if a string is a valid port number * `MongoID`: validates if a string is a valid Mongo ID * `Latitude`: validates if a string is a valid latitude * `Longitude`: validates if a string is a valid longitude * `SSN`: validates if a string is a social security number (SSN) * `Semver`: validates if a string is a valid semantic version ## Credits The `is` sub-package wraps the excellent validators provided by the [govalidator](https://github.com/asaskevich/govalidator) package. ozzo-validation-4.3.0/UPGRADE.md000066400000000000000000000045561374327524300163040ustar00rootroot00000000000000# Upgrade Instructions ## Upgrade from 3.x to 4.x If you are customizing the error messages of the following built-in validation rules, you may need to change the parameter placeholders from `%v` to the Go template variable placeholders. * `Length`: use `{{.min}}` and `{{.max}}` in the error message to refer to the minimum and maximum lengths. * `Min`: use `{{.threshold}}` in the error message to refer to the lower bound. * `Max`: use `{{.threshold}}` in the error message to refer to the upper bound * `MultipleOf`: use `{{.base}}` in the error message to refer to the base of the multiples. For example, ```go // 3.x lengthRule := validation.Length(2,10).Error("the length must be between %v and %v") // 4.x lengthRule := validation.Length(2,10).Error("the length must be between {{.min}} and {{.max}}") ``` ## Upgrade from 2.x to 3.x * Instead of using `StructRules` to define struct validation rules, use `ValidateStruct()` to declare and perform struct validation. The following code snippet shows how to modify your code: ```go // 2.x usage err := validation.StructRules{}. Add("Street", validation.Required, validation.Length(5, 50)). Add("City", validation.Required, validation.Length(5, 50)). Add("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))). Add("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))). Validate(a) // 3.x usage err := validation.ValidateStruct(&a, validation.Field(&a.Street, validation.Required, validation.Length(5, 50)), validation.Field(&a.City, validation.Required, validation.Length(5, 50)), validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), ) ``` * Instead of using `Rules` to declare a rule list and use it to validate a value, call `Validate()` with the rules directly. ```go data := "example" // 2.x usage rules := validation.Rules{ validation.Required, validation.Length(5, 100), is.URL, } err := rules.Validate(data) // 3.x usage err := validation.Validate(data, validation.Required, validation.Length(5, 100), is.URL, ) ``` * The default struct tags used for determining error keys is changed from `validation` to `json`. You may modify `validation.ErrorTag` to change it back.ozzo-validation-4.3.0/absent.go000066400000000000000000000032021374327524300164610ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation var ( // ErrNil is the error that returns when a value is not nil. ErrNil = NewError("validation_nil", "must be blank") // ErrEmpty is the error that returns when a not nil value is not empty. ErrEmpty = NewError("validation_empty", "must be blank") ) // Nil is a validation rule that checks if a value is nil. // It is the opposite of NotNil rule var Nil = absentRule{condition: true, skipNil: false} // Empty checks if a not nil value is empty. var Empty = absentRule{condition: true, skipNil: true} type absentRule struct { condition bool err Error skipNil bool } // Validate checks if the given value is valid or not. func (r absentRule) Validate(value interface{}) error { if r.condition { value, isNil := Indirect(value) if !r.skipNil && !isNil || r.skipNil && !isNil && !IsEmpty(value) { if r.err != nil { return r.err } if r.skipNil { return ErrEmpty } return ErrNil } } return nil } // When sets the condition that determines if the validation should be performed. func (r absentRule) When(condition bool) absentRule { r.condition = condition return r } // Error sets the error message for the rule. func (r absentRule) Error(message string) absentRule { if r.err == nil { if r.skipNil { r.err = ErrEmpty } else { r.err = ErrNil } } r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r absentRule) ErrorObject(err Error) absentRule { r.err = err return r } ozzo-validation-4.3.0/absent_test.go000066400000000000000000000042601374327524300175250ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestNil(t *testing.T) { s1 := "123" s2 := "" var time1 time.Time tests := []struct { tag string value interface{} err string }{ {"t1", 123, "must be blank"}, {"t2", "", "must be blank"}, {"t3", &s1, "must be blank"}, {"t4", &s2, "must be blank"}, {"t5", nil, ""}, {"t6", time1, "must be blank"}, } for _, test := range tests { r := Nil err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestEmpty(t *testing.T) { s1 := "123" s2 := "" time1 := time.Now() var time2 time.Time tests := []struct { tag string value interface{} err string }{ {"t1", 123, "must be blank"}, {"t2", "", ""}, {"t3", &s1, "must be blank"}, {"t4", &s2, ""}, {"t5", nil, ""}, {"t6", time1, "must be blank"}, {"t7", time2, ""}, } for _, test := range tests { r := Empty err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestAbsentRule_When(t *testing.T) { r := Nil.When(false) err := Validate(42, r) assert.Nil(t, err) r = Nil.When(true) err = Validate(42, r) assert.Equal(t, ErrNil, err) } func Test_absentRule_Error(t *testing.T) { r := Nil assert.Equal(t, "must be blank", r.Validate("42").Error()) assert.False(t, r.skipNil) r2 := r.Error("123") assert.Equal(t, "must be blank", r.Validate("42").Error()) assert.False(t, r.skipNil) assert.Equal(t, "123", r2.err.Message()) assert.False(t, r2.skipNil) r = Empty assert.Equal(t, "must be blank", r.Validate("42").Error()) assert.True(t, r.skipNil) r2 = r.Error("123") assert.Equal(t, "must be blank", r.Validate("42").Error()) assert.True(t, r.skipNil) assert.Equal(t, "123", r2.err.Message()) assert.True(t, r2.skipNil) } func TestAbsentRule_Error(t *testing.T) { r := Nil err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) assert.NotEqual(t, err, Nil.err) } ozzo-validation-4.3.0/date.go000066400000000000000000000057501374327524300161340ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "time" ) var ( // ErrDateInvalid is the error that returns in case of an invalid date. ErrDateInvalid = NewError("validation_date_invalid", "must be a valid date") // ErrDateOutOfRange is the error that returns in case of an invalid date. ErrDateOutOfRange = NewError("validation_date_out_of_range", "the date is out of range") ) // DateRule is a validation rule that validates date/time string values. type DateRule struct { layout string min, max time.Time err, rangeErr Error } // Date returns a validation rule that checks if a string value is in a format that can be parsed into a date. // The format of the date should be specified as the layout parameter which accepts the same value as that for time.Parse. // For example, // validation.Date(time.ANSIC) // validation.Date("02 Jan 06 15:04 MST") // validation.Date("2006-01-02") // // By calling Min() and/or Max(), you can let the Date rule to check if a parsed date value is within // the specified date range. // // An empty value is considered valid. Use the Required rule to make sure a value is not empty. func Date(layout string) DateRule { return DateRule{ layout: layout, err: ErrDateInvalid, rangeErr: ErrDateOutOfRange, } } // Error sets the error message that is used when the value being validated is not a valid date. func (r DateRule) Error(message string) DateRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct that is used when the value being validated is not a valid date.. func (r DateRule) ErrorObject(err Error) DateRule { r.err = err return r } // RangeError sets the error message that is used when the value being validated is out of the specified Min/Max date range. func (r DateRule) RangeError(message string) DateRule { r.rangeErr = r.rangeErr.SetMessage(message) return r } // RangeErrorObject sets the error struct that is used when the value being validated is out of the specified Min/Max date range. func (r DateRule) RangeErrorObject(err Error) DateRule { r.rangeErr = err return r } // Min sets the minimum date range. A zero value means skipping the minimum range validation. func (r DateRule) Min(min time.Time) DateRule { r.min = min return r } // Max sets the maximum date range. A zero value means skipping the maximum range validation. func (r DateRule) Max(max time.Time) DateRule { r.max = max return r } // Validate checks if the given value is a valid date. func (r DateRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } str, err := EnsureString(value) if err != nil { return err } date, err := time.Parse(r.layout, str) if err != nil { return r.err } if !r.min.IsZero() && r.min.After(date) || !r.max.IsZero() && date.After(r.max) { return r.rangeErr } return nil } ozzo-validation-4.3.0/date_test.go000066400000000000000000000054601374327524300171710ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestDate(t *testing.T) { tests := []struct { tag string layout string value interface{} err string }{ {"t1", time.ANSIC, "", ""}, {"t2", time.ANSIC, "Wed Feb 4 21:00:57 2009", ""}, {"t3", time.ANSIC, "Wed Feb 29 21:00:57 2009", "must be a valid date"}, {"t4", "2006-01-02", "2009-11-12", ""}, {"t5", "2006-01-02", "2009-11-12 21:00:57", "must be a valid date"}, {"t6", "2006-01-02", "2009-1-12", "must be a valid date"}, {"t7", "2006-01-02", "2009-01-12", ""}, {"t8", "2006-01-02", "2009-01-32", "must be a valid date"}, {"t9", "2006-01-02", 1, "must be either a string or byte slice"}, } for _, test := range tests { r := Date(test.layout) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestDateRule_Error(t *testing.T) { r := Date(time.RFC3339) assert.Equal(t, "must be a valid date", r.Validate("0001-01-02T15:04:05Z07:00").Error()) r2 := r.Min(time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)) assert.Equal(t, "the date is out of range", r2.Validate("1999-01-02T15:04:05Z").Error()) r = r.Error("123") r = r.RangeError("456") assert.Equal(t, "123", r.err.Message()) assert.Equal(t, "456", r.rangeErr.Message()) } func TestDateRule_ErrorObject(t *testing.T) { r := Date(time.RFC3339) assert.Equal(t, "must be a valid date", r.Validate("0001-01-02T15:04:05Z07:00").Error()) r = r.ErrorObject(NewError("code", "abc")) assert.Equal(t, "code", r.err.Code()) assert.Equal(t, "abc", r.Validate("0001-01-02T15:04:05Z07:00").Error()) r2 := r.Min(time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)) assert.Equal(t, "the date is out of range", r2.Validate("1999-01-02T15:04:05Z").Error()) r = r.ErrorObject(NewError("C", "def")) r = r.RangeErrorObject(NewError("D", "123")) assert.Equal(t, "C", r.err.Code()) assert.Equal(t, "def", r.err.Message()) assert.Equal(t, "D", r.rangeErr.Code()) assert.Equal(t, "123", r.rangeErr.Message()) } func TestDateRule_MinMax(t *testing.T) { r := Date(time.ANSIC) assert.True(t, r.min.IsZero()) assert.True(t, r.max.IsZero()) r = r.Min(time.Now()) assert.False(t, r.min.IsZero()) assert.True(t, r.max.IsZero()) r = r.Max(time.Now()) assert.False(t, r.max.IsZero()) 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)) assert.Nil(t, r2.Validate("2010-01-02")) err := r2.Validate("1999-01-02") if assert.NotNil(t, err) { assert.Equal(t, "the date is out of range", err.Error()) } err = r2.Validate("2021-01-02") if assert.NotNil(t, err) { assert.Equal(t, "the date is out of range", err.Error()) } } ozzo-validation-4.3.0/each.go000066400000000000000000000045151374327524300161150ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "context" "errors" "reflect" "strconv" ) // Each returns a validation rule that loops through an iterable (map, slice or array) // and validates each value inside with the provided rules. // An empty iterable is considered valid. Use the Required rule to make sure the iterable is not empty. func Each(rules ...Rule) EachRule { return EachRule{ rules: rules, } } // EachRule is a validation rule that validates elements in a map/slice/array using the specified list of rules. type EachRule struct { rules []Rule } // Validate loops through the given iterable and calls the Ozzo Validate() method for each value. func (r EachRule) Validate(value interface{}) error { return r.ValidateWithContext(nil, value) } // ValidateWithContext loops through the given iterable and calls the Ozzo ValidateWithContext() method for each value. func (r EachRule) ValidateWithContext(ctx context.Context, value interface{}) error { errs := Errors{} v := reflect.ValueOf(value) switch v.Kind() { case reflect.Map: for _, k := range v.MapKeys() { val := r.getInterface(v.MapIndex(k)) var err error if ctx == nil { err = Validate(val, r.rules...) } else { err = ValidateWithContext(ctx, val, r.rules...) } if err != nil { errs[r.getString(k)] = err } } case reflect.Slice, reflect.Array: for i := 0; i < v.Len(); i++ { val := r.getInterface(v.Index(i)) var err error if ctx == nil { err = Validate(val, r.rules...) } else { err = ValidateWithContext(ctx, val, r.rules...) } if err != nil { errs[strconv.Itoa(i)] = err } } default: return errors.New("must be an iterable (map, slice or array)") } if len(errs) > 0 { return errs } return nil } func (r EachRule) getInterface(value reflect.Value) interface{} { switch value.Kind() { case reflect.Ptr, reflect.Interface: if value.IsNil() { return nil } return value.Elem().Interface() default: return value.Interface() } } func (r EachRule) getString(value reflect.Value) string { switch value.Kind() { case reflect.Ptr, reflect.Interface: if value.IsNil() { return "" } return value.Elem().String() default: return value.String() } } ozzo-validation-4.3.0/each_test.go000066400000000000000000000047451374327524300171610ustar00rootroot00000000000000package validation import ( "context" "errors" "strings" "testing" ) func TestEach(t *testing.T) { var a *int var f = func(v string) string { return v } var c0 chan int c1 := make(chan int) tests := []struct { tag string value interface{} err string }{ {"t1", nil, "must be an iterable (map, slice or array)"}, {"t2", map[string]string{}, ""}, {"t3", map[string]string{"key1": "value1", "key2": "value2"}, ""}, {"t4", map[string]string{"key1": "", "key2": "value2", "key3": ""}, "key1: cannot be blank; key3: cannot be blank."}, {"t5", map[string]map[string]string{"key1": {"key1.1": "value1"}, "key2": {"key2.1": "value1"}}, ""}, {"t6", map[string]map[string]string{"": nil}, ": cannot be blank."}, {"t7", map[interface{}]interface{}{}, ""}, {"t8", map[interface{}]interface{}{"key1": struct{ foo string }{"foo"}}, ""}, {"t9", map[interface{}]interface{}{nil: "", "": "", "key1": nil}, ": cannot be blank; key1: cannot be blank."}, {"t10", []string{"value1", "value2", "value3"}, ""}, {"t11", []string{"", "value2", ""}, "0: cannot be blank; 2: cannot be blank."}, {"t12", []interface{}{struct{ foo string }{"foo"}}, ""}, {"t13", []interface{}{nil, a}, "0: cannot be blank; 1: cannot be blank."}, {"t14", []interface{}{c0, c1, f}, "0: cannot be blank."}, } for _, test := range tests { r := Each(Required) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestEachWithContext(t *testing.T) { rule := Each(WithContext(func(ctx context.Context, value interface{}) error { if !strings.Contains(value.(string), ctx.Value("contains").(string)) { return errors.New("unexpected value") } return nil })) ctx1 := context.WithValue(context.Background(), "contains", "abc") ctx2 := context.WithValue(context.Background(), "contains", "xyz") tests := []struct { tag string value interface{} ctx context.Context err string }{ {"t1.1", map[string]string{"key": "abc"}, ctx1, ""}, {"t1.2", map[string]string{"key": "abc"}, ctx2, "key: unexpected value."}, {"t1.3", map[string]string{"key": "xyz"}, ctx1, "key: unexpected value."}, {"t1.4", map[string]string{"key": "xyz"}, ctx2, ""}, {"t1.5", []string{"abc"}, ctx1, ""}, {"t1.6", []string{"abc"}, ctx2, "0: unexpected value."}, {"t1.7", []string{"xyz"}, ctx1, "0: unexpected value."}, {"t1.8", []string{"xyz"}, ctx2, ""}, } for _, test := range tests { err := ValidateWithContext(test.ctx, test.value, rule) assertError(t, test.err, err, test.tag) } } ozzo-validation-4.3.0/error.go000066400000000000000000000074731374327524300163540ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "bytes" "encoding/json" "fmt" "sort" "strings" "text/template" ) type ( // Error interface represents an validation error Error interface { Error() string Code() string Message() string SetMessage(string) Error Params() map[string]interface{} SetParams(map[string]interface{}) Error } // ErrorObject is the default validation error // that implements the Error interface. ErrorObject struct { code string message string params map[string]interface{} } // Errors represents the validation errors that are indexed by struct field names, map or slice keys. // values are Error or Errors (for map, slice and array error value is Errors). Errors map[string]error // InternalError represents an error that should NOT be treated as a validation error. InternalError interface { error InternalError() error } internalError struct { error } ) // NewInternalError wraps a given error into an InternalError. func NewInternalError(err error) InternalError { return internalError{error: err} } // InternalError returns the actual error that it wraps around. func (e internalError) InternalError() error { return e.error } // SetCode set the error's translation code. func (e ErrorObject) SetCode(code string) Error { e.code = code return e } // Code get the error's translation code. func (e ErrorObject) Code() string { return e.code } // SetParams set the error's params. func (e ErrorObject) SetParams(params map[string]interface{}) Error { e.params = params return e } // AddParam add parameter to the error's parameters. func (e ErrorObject) AddParam(name string, value interface{}) Error { if e.params == nil { e.params = make(map[string]interface{}) } e.params[name] = value return e } // Params returns the error's params. func (e ErrorObject) Params() map[string]interface{} { return e.params } // SetMessage set the error's message. func (e ErrorObject) SetMessage(message string) Error { e.message = message return e } // Message return the error's message. func (e ErrorObject) Message() string { return e.message } // Error returns the error message. func (e ErrorObject) Error() string { if len(e.params) == 0 { return e.message } res := bytes.Buffer{} _ = template.Must(template.New("err").Parse(e.message)).Execute(&res, e.params) return res.String() } // Error returns the error string of Errors. func (es Errors) Error() string { if len(es) == 0 { return "" } keys := make([]string, len(es)) i := 0 for key := range es { keys[i] = key i++ } sort.Strings(keys) var s strings.Builder for i, key := range keys { if i > 0 { s.WriteString("; ") } if errs, ok := es[key].(Errors); ok { _, _ = fmt.Fprintf(&s, "%v: (%v)", key, errs) } else { _, _ = fmt.Fprintf(&s, "%v: %v", key, es[key].Error()) } } s.WriteString(".") return s.String() } // MarshalJSON converts the Errors into a valid JSON. func (es Errors) MarshalJSON() ([]byte, error) { errs := map[string]interface{}{} for key, err := range es { if ms, ok := err.(json.Marshaler); ok { errs[key] = ms } else { errs[key] = err.Error() } } return json.Marshal(errs) } // Filter removes all nils from Errors and returns back the updated Errors as an error. // If the length of Errors becomes 0, it will return nil. func (es Errors) Filter() error { for key, value := range es { if value == nil { delete(es, key) } } if len(es) == 0 { return nil } return es } // NewError create new validation error. func NewError(code, message string) Error { return ErrorObject{ code: code, message: message, } } // Assert that our ErrorObject implements the Error interface. var _ Error = ErrorObject{} ozzo-validation-4.3.0/error_test.go000066400000000000000000000077611374327524300174130ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func TestNewInternalError(t *testing.T) { err := NewInternalError(errors.New("abc")) if assert.NotNil(t, err.InternalError()) { assert.Equal(t, "abc", err.InternalError().Error()) } } func TestErrors_Error(t *testing.T) { errs := Errors{ "B": errors.New("B1"), "C": errors.New("C1"), "A": errors.New("A1"), } assert.Equal(t, "A: A1; B: B1; C: C1.", errs.Error()) errs = Errors{ "B": errors.New("B1"), } assert.Equal(t, "B: B1.", errs.Error()) errs = Errors{} assert.Equal(t, "", errs.Error()) } func TestErrors_MarshalMessage(t *testing.T) { errs := Errors{ "A": errors.New("A1"), "B": Errors{ "2": errors.New("B1"), }, } errsJSON, err := errs.MarshalJSON() assert.Nil(t, err) assert.Equal(t, "{\"A\":\"A1\",\"B\":{\"2\":\"B1\"}}", string(errsJSON)) } func TestErrors_Filter(t *testing.T) { errs := Errors{ "B": errors.New("B1"), "C": nil, "A": errors.New("A1"), } err := errs.Filter() assert.Equal(t, 2, len(errs)) if assert.NotNil(t, err) { assert.Equal(t, "A: A1; B: B1.", err.Error()) } errs = Errors{} assert.Nil(t, errs.Filter()) errs = Errors{ "B": nil, "C": nil, } assert.Nil(t, errs.Filter()) } func TestErrorObject_SetCode(t *testing.T) { err := NewError("A", "msg").(ErrorObject) assert.Equal(t, err.code, "A") assert.Equal(t, err.Code(), "A") err = err.SetCode("B").(ErrorObject) assert.Equal(t, "B", err.code) } func TestErrorObject_Code(t *testing.T) { err := NewError("A", "msg").(ErrorObject) assert.Equal(t, err.Code(), "A") } func TestErrorObject_SetMessage(t *testing.T) { err := NewError("code", "A").(ErrorObject) assert.Equal(t, err.message, "A") assert.Equal(t, err.Message(), "A") err = err.SetMessage("abc").(ErrorObject) assert.Equal(t, err.message, "abc") assert.Equal(t, err.Message(), "abc") } func TestErrorObject_Message(t *testing.T) { err := NewError("code", "A").(ErrorObject) assert.Equal(t, err.message, "A") assert.Equal(t, err.Message(), "A") } func TestErrorObject_Params(t *testing.T) { p := map[string]interface{}{"A": "val1", "AA": "val2"} err := NewError("code", "A").(ErrorObject) err = err.SetParams(p).(ErrorObject) err = err.SetMessage("B").(ErrorObject) assert.Equal(t, err.params, p) assert.Equal(t, err.Params(), p) } func TestErrorObject_AddParam2(t *testing.T) { p := map[string]interface{}{"key": "val"} err := NewError("code", "A").(ErrorObject) err = err.AddParam("key", "val").(ErrorObject) assert.Equal(t, err.params, p) assert.Equal(t, err.Params(), p) } func TestErrorObject_AddParam(t *testing.T) { p := map[string]interface{}{"A": "val1", "B": "val2"} err := NewError("code", "A").(ErrorObject) err = err.SetParams(p).(ErrorObject) err = err.AddParam("C", "val3").(ErrorObject) p["C"] = "val3" assert.Equal(t, err.params, p) assert.Equal(t, err.Params(), p) } func TestError_Code(t *testing.T) { err := NewError("A", "msg") assert.Equal(t, err.Code(), "A") } func TestError_SetMessage(t *testing.T) { err := NewError("code", "A") assert.Equal(t, err.Message(), "A") err = err.SetMessage("abc") assert.Equal(t, err.Message(), "abc") } func TestError_Message(t *testing.T) { err := NewError("code", "A") assert.Equal(t, err.Message(), "A") } func TestError_Params(t *testing.T) { p := map[string]interface{}{"A": "val1", "AA": "val2"} err := NewError("code", "A") err = err.SetParams(p) err = err.SetMessage("B") assert.Equal(t, err.Params(), p) } func TestValidationError(t *testing.T) { params := map[string]interface{}{ "A": "B", } err := NewError("code", "msg") err = err.SetParams(params) assert.Equal(t, err.Code(), "code") assert.Equal(t, err.Message(), "msg") assert.Equal(t, err.Params(), params) params = map[string]interface{}{"min": 1} err = err.SetParams(params) assert.Equal(t, err.Params(), params) } ozzo-validation-4.3.0/example_test.go000066400000000000000000000127041374327524300177060ustar00rootroot00000000000000package validation_test import ( "context" "errors" "fmt" "regexp" "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" ) type Address struct { Street string City string State string Zip string } type Customer struct { Name string Gender string Email string Address Address } func (a Address) Validate() error { return validation.ValidateStruct(&a, // Street cannot be empty, and the length must between 5 and 50 validation.Field(&a.Street, validation.Required, validation.Length(5, 50)), // City cannot be empty, and the length must between 5 and 50 validation.Field(&a.City, validation.Required, validation.Length(5, 50)), // State cannot be empty, and must be a string consisting of two letters in upper case validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), // State cannot be empty, and must be a string consisting of five digits validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), ) } func (c Customer) Validate() error { return validation.ValidateStruct(&c, // Name cannot be empty, and the length must be between 5 and 20. validation.Field(&c.Name, validation.Required, validation.Length(5, 20)), // Gender is optional, and should be either "Female" or "Male". validation.Field(&c.Gender, validation.In("Female", "Male")), // Email cannot be empty and should be in a valid email format. validation.Field(&c.Email, validation.Required, is.Email), // Validate Address using its own validation rules validation.Field(&c.Address), ) } func Example() { c := Customer{ Name: "Qiang Xue", Email: "q", Address: Address{ Street: "123 Main Street", City: "Unknown", State: "Virginia", Zip: "12345", }, } err := c.Validate() fmt.Println(err) // Output: // Address: (State: must be in a valid format.); Email: must be a valid email address. } func Example_second() { data := "example" err := validation.Validate(data, validation.Required, // not empty validation.Length(5, 100), // length between 5 and 100 is.URL, // is a valid URL ) fmt.Println(err) // Output: // must be a valid URL } func Example_third() { addresses := []Address{ {State: "MD", Zip: "12345"}, {Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"}, {City: "Unknown", State: "NC", Zip: "123"}, } err := validation.Validate(addresses) fmt.Println(err) // Output: // 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.). } func Example_four() { c := Customer{ Name: "Qiang Xue", Email: "q", Address: Address{ State: "Virginia", }, } err := validation.Errors{ "name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)), "email": validation.Validate(c.Name, validation.Required, is.Email), "zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), }.Filter() fmt.Println(err) // Output: // email: must be a valid email address; zip: cannot be blank. } func Example_five() { type Employee struct { Name string } type Manager struct { Employee Level int } m := Manager{} err := validation.ValidateStruct(&m, validation.Field(&m.Name, validation.Required), validation.Field(&m.Level, validation.Required), ) fmt.Println(err) // Output: // Level: cannot be blank; Name: cannot be blank. } type contextKey int func Example_six() { key := contextKey(1) rule := validation.WithContext(func(ctx context.Context, value interface{}) error { s, _ := value.(string) if ctx.Value(key) == s { return nil } return errors.New("unexpected value") }) ctx := context.WithValue(context.Background(), key, "good sample") err1 := validation.ValidateWithContext(ctx, "bad sample", rule) fmt.Println(err1) err2 := validation.ValidateWithContext(ctx, "good sample", rule) fmt.Println(err2) // Output: // unexpected value // } func Example_seven() { c := map[string]interface{}{ "Name": "Qiang Xue", "Email": "q", "Address": map[string]interface{}{ "Street": "123", "City": "Unknown", "State": "Virginia", "Zip": "12345", }, } err := validation.Validate(c, validation.Map( // Name cannot be empty, and the length must be between 5 and 20. validation.Key("Name", validation.Required, validation.Length(5, 20)), // Email cannot be empty and should be in a valid email format. validation.Key("Email", validation.Required, is.Email), // Validate Address using its own validation rules validation.Key("Address", validation.Map( // Street cannot be empty, and the length must between 5 and 50 validation.Key("Street", validation.Required, validation.Length(5, 50)), // City cannot be empty, and the length must between 5 and 50 validation.Key("City", validation.Required, validation.Length(5, 50)), // State cannot be empty, and must be a string consisting of two letters in upper case validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))), // State cannot be empty, and must be a string consisting of five digits validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))), )), ), ) fmt.Println(err) // Output: // Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address. } ozzo-validation-4.3.0/go.mod000066400000000000000000000002551374327524300157710ustar00rootroot00000000000000module github.com/go-ozzo/ozzo-validation/v4 go 1.13 require ( github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 github.com/stretchr/testify v1.4.0 ) ozzo-validation-4.3.0/go.sum000066400000000000000000000024111374327524300160120ustar00rootroot00000000000000github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ozzo-validation-4.3.0/in.go000066400000000000000000000030001374327524300156070ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "reflect" ) // ErrInInvalid is the error that returns in case of an invalid value for "in" rule. var ErrInInvalid = NewError("validation_in_invalid", "must be a valid value") // In returns a validation rule that checks if a value can be found in the given list of values. // reflect.DeepEqual() will be used to determine if two values are equal. // For more details please refer to https://golang.org/pkg/reflect/#DeepEqual // An empty value is considered valid. Use the Required rule to make sure a value is not empty. func In(values ...interface{}) InRule { return InRule{ elements: values, err: ErrInInvalid, } } // InRule is a validation rule that validates if a value can be found in the given list of values. type InRule struct { elements []interface{} err Error } // Validate checks if the given value is valid or not. func (r InRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } for _, e := range r.elements { if reflect.DeepEqual(e, value) { return nil } } return r.err } // Error sets the error message for the rule. func (r InRule) Error(message string) InRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r InRule) ErrorObject(err Error) InRule { r.err = err return r } ozzo-validation-4.3.0/in_test.go000066400000000000000000000025641374327524300166640ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "github.com/stretchr/testify/assert" ) func TestIn(t *testing.T) { var v = 1 var v2 *int tests := []struct { tag string values []interface{} value interface{} err string }{ {"t0", []interface{}{1, 2}, 0, ""}, {"t1", []interface{}{1, 2}, 1, ""}, {"t2", []interface{}{1, 2}, 2, ""}, {"t3", []interface{}{1, 2}, 3, "must be a valid value"}, {"t4", []interface{}{}, 3, "must be a valid value"}, {"t5", []interface{}{1, 2}, "1", "must be a valid value"}, {"t6", []interface{}{1, 2}, &v, ""}, {"t7", []interface{}{1, 2}, v2, ""}, {"t8", []interface{}{[]byte{1}, 1, 2}, []byte{1}, ""}, } for _, test := range tests { r := In(test.values...) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func Test_InRule_Error(t *testing.T) { r := In(1, 2, 3) val := 4 assert.Equal(t, "must be a valid value", r.Validate(&val).Error()) r = r.Error("123") assert.Equal(t, "123", r.err.Message()) } func TestInRule_ErrorObject(t *testing.T) { r := In(1, 2, 3) err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) } ozzo-validation-4.3.0/is/000077500000000000000000000000001374327524300152745ustar00rootroot00000000000000ozzo-validation-4.3.0/is/rules.go000066400000000000000000000451711374327524300167650ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. // Package is provides a list of commonly used string validation rules. package is import ( "regexp" "unicode" "github.com/asaskevich/govalidator" validation "github.com/go-ozzo/ozzo-validation/v4" ) var ( // ErrEmail is the error that returns in case of an invalid email. ErrEmail = validation.NewError("validation_is_email", "must be a valid email address") // ErrURL is the error that returns in case of an invalid URL. ErrURL = validation.NewError("validation_is_url", "must be a valid URL") // ErrRequestURL is the error that returns in case of an invalid request URL. ErrRequestURL = validation.NewError("validation_is_request_url", "must be a valid request URL") // ErrRequestURI is the error that returns in case of an invalid request URI. ErrRequestURI = validation.NewError("validation_request_is_request_uri", "must be a valid request URI") // ErrAlpha is the error that returns in case of an invalid alpha value. ErrAlpha = validation.NewError("validation_is_alpha", "must contain English letters only") // ErrDigit is the error that returns in case of an invalid digit value. ErrDigit = validation.NewError("validation_is_digit", "must contain digits only") // ErrAlphanumeric is the error that returns in case of an invalid alphanumeric value. ErrAlphanumeric = validation.NewError("validation_is_alphanumeric", "must contain English letters and digits only") // ErrUTFLetter is the error that returns in case of an invalid utf letter value. ErrUTFLetter = validation.NewError("validation_is_utf_letter", "must contain unicode letter characters only") // ErrUTFDigit is the error that returns in case of an invalid utf digit value. ErrUTFDigit = validation.NewError("validation_is_utf_digit", "must contain unicode decimal digits only") // ErrUTFLetterNumeric is the error that returns in case of an invalid utf numeric or letter value. ErrUTFLetterNumeric = validation.NewError("validation_is utf_letter_numeric", "must contain unicode letters and numbers only") // ErrUTFNumeric is the error that returns in case of an invalid utf numeric value. ErrUTFNumeric = validation.NewError("validation_is_utf_numeric", "must contain unicode number characters only") // ErrLowerCase is the error that returns in case of an invalid lower case value. ErrLowerCase = validation.NewError("validation_is_lower_case", "must be in lower case") // ErrUpperCase is the error that returns in case of an invalid upper case value. ErrUpperCase = validation.NewError("validation_is_upper_case", "must be in upper case") // ErrHexadecimal is the error that returns in case of an invalid hexadecimal number. ErrHexadecimal = validation.NewError("validation_is_hexadecimal", "must be a valid hexadecimal number") // ErrHexColor is the error that returns in case of an invalid hexadecimal color code. ErrHexColor = validation.NewError("validation_is_hex_color", "must be a valid hexadecimal color code") // ErrRGBColor is the error that returns in case of an invalid RGB color code. ErrRGBColor = validation.NewError("validation_is_rgb_color", "must be a valid RGB color code") // ErrInt is the error that returns in case of an invalid integer value. ErrInt = validation.NewError("validation_is_int", "must be an integer number") // ErrFloat is the error that returns in case of an invalid float value. ErrFloat = validation.NewError("validation_is_float", "must be a floating point number") // ErrUUIDv3 is the error that returns in case of an invalid UUIDv3 value. ErrUUIDv3 = validation.NewError("validation_is_uuid_v3", "must be a valid UUID v3") // ErrUUIDv4 is the error that returns in case of an invalid UUIDv4 value. ErrUUIDv4 = validation.NewError("validation_is_uuid_v4", "must be a valid UUID v4") // ErrUUIDv5 is the error that returns in case of an invalid UUIDv5 value. ErrUUIDv5 = validation.NewError("validation_is_uuid_v5", "must be a valid UUID v5") // ErrUUID is the error that returns in case of an invalid UUID value. ErrUUID = validation.NewError("validation_is_uuid", "must be a valid UUID") // ErrCreditCard is the error that returns in case of an invalid credit card number. ErrCreditCard = validation.NewError("validation_is_credit_card", "must be a valid credit card number") // ErrISBN10 is the error that returns in case of an invalid ISBN-10 value. ErrISBN10 = validation.NewError("validation_is_isbn_10", "must be a valid ISBN-10") // ErrISBN13 is the error that returns in case of an invalid ISBN-13 value. ErrISBN13 = validation.NewError("validation_is_isbn_13", "must be a valid ISBN-13") // ErrISBN is the error that returns in case of an invalid ISBN value. ErrISBN = validation.NewError("validation_is_isbn", "must be a valid ISBN") // ErrJSON is the error that returns in case of an invalid JSON. ErrJSON = validation.NewError("validation_is_json", "must be in valid JSON format") // ErrASCII is the error that returns in case of an invalid ASCII. ErrASCII = validation.NewError("validation_is_ascii", "must contain ASCII characters only") // ErrPrintableASCII is the error that returns in case of an invalid printable ASCII value. ErrPrintableASCII = validation.NewError("validation_is_printable_ascii", "must contain printable ASCII characters only") // ErrMultibyte is the error that returns in case of an invalid multibyte value. ErrMultibyte = validation.NewError("validation_is_multibyte", "must contain multibyte characters") // ErrFullWidth is the error that returns in case of an invalid full-width value. ErrFullWidth = validation.NewError("validation_is_full_width", "must contain full-width characters") // ErrHalfWidth is the error that returns in case of an invalid half-width value. ErrHalfWidth = validation.NewError("validation_is_half_width", "must contain half-width characters") // ErrVariableWidth is the error that returns in case of an invalid variable width value. ErrVariableWidth = validation.NewError("validation_is_variable_width", "must contain both full-width and half-width characters") // ErrBase64 is the error that returns in case of an invalid base54 value. ErrBase64 = validation.NewError("validation_is_base64", "must be encoded in Base64") // ErrDataURI is the error that returns in case of an invalid data URI. ErrDataURI = validation.NewError("validation_is_data_uri", "must be a Base64-encoded data URI") // ErrE164 is the error that returns in case of an invalid e165. ErrE164 = validation.NewError("validation_is_e164_number", "must be a valid E164 number") // ErrCountryCode2 is the error that returns in case of an invalid two-letter country code. ErrCountryCode2 = validation.NewError("validation_is_country_code_2_letter", "must be a valid two-letter country code") // ErrCountryCode3 is the error that returns in case of an invalid three-letter country code. ErrCountryCode3 = validation.NewError("validation_is_country_code_3_letter", "must be a valid three-letter country code") // ErrCurrencyCode is the error that returns in case of an invalid currency code. ErrCurrencyCode = validation.NewError("validation_is_currency_code", "must be valid ISO 4217 currency code") // ErrDialString is the error that returns in case of an invalid string. ErrDialString = validation.NewError("validation_is_dial_string", "must be a valid dial string") // ErrMac is the error that returns in case of an invalid mac address. ErrMac = validation.NewError("validation_is_mac_address", "must be a valid MAC address") // ErrIP is the error that returns in case of an invalid IP. ErrIP = validation.NewError("validation_is_ip", "must be a valid IP address") // ErrIPv4 is the error that returns in case of an invalid IPv4. ErrIPv4 = validation.NewError("validation_is_ipv4", "must be a valid IPv4 address") // ErrIPv6 is the error that returns in case of an invalid IPv6. ErrIPv6 = validation.NewError("validation_is_ipv6", "must be a valid IPv6 address") // ErrSubdomain is the error that returns in case of an invalid subdomain. ErrSubdomain = validation.NewError("validation_is_sub_domain", "must be a valid subdomain") // ErrDomain is the error that returns in case of an invalid domain. ErrDomain = validation.NewError("validation_is_domain", "must be a valid domain") // ErrDNSName is the error that returns in case of an invalid DNS name. ErrDNSName = validation.NewError("validation_is_dns_name", "must be a valid DNS name") // ErrHost is the error that returns in case of an invalid host. ErrHost = validation.NewError("validation_is_host", "must be a valid IP address or DNS name") // ErrPort is the error that returns in case of an invalid port. ErrPort = validation.NewError("validation_is_port", "must be a valid port number") // ErrMongoID is the error that returns in case of an invalid MongoID. ErrMongoID = validation.NewError("validation_is_mongo_id", "must be a valid hex-encoded MongoDB ObjectId") // ErrLatitude is the error that returns in case of an invalid latitude. ErrLatitude = validation.NewError("validation_is_latitude", "must be a valid latitude") // ErrLongitude is the error that returns in case of an invalid longitude. ErrLongitude = validation.NewError("validation_is_longitude", "must be a valid longitude") // ErrSSN is the error that returns in case of an invalid SSN. ErrSSN = validation.NewError("validation_is_ssn", "must be a valid social security number") // ErrSemver is the error that returns in case of an invalid semver. ErrSemver = validation.NewError("validation_is_semver", "must be a valid semantic version") ) var ( // Email validates if a string is an email or not. It also checks if the MX record exists for the email domain. Email = validation.NewStringRuleWithError(govalidator.IsExistingEmail, ErrEmail) // EmailFormat validates if a string is an email or not. Note that it does NOT check if the MX record exists or not. EmailFormat = validation.NewStringRuleWithError(govalidator.IsEmail, ErrEmail) // URL validates if a string is a valid URL URL = validation.NewStringRuleWithError(govalidator.IsURL, ErrURL) // RequestURL validates if a string is a valid request URL RequestURL = validation.NewStringRuleWithError(govalidator.IsRequestURL, ErrRequestURL) // RequestURI validates if a string is a valid request URI RequestURI = validation.NewStringRuleWithError(govalidator.IsRequestURI, ErrRequestURI) // Alpha validates if a string contains English letters only (a-zA-Z) Alpha = validation.NewStringRuleWithError(govalidator.IsAlpha, ErrAlpha) // Digit validates if a string contains digits only (0-9) Digit = validation.NewStringRuleWithError(isDigit, ErrDigit) // Alphanumeric validates if a string contains English letters and digits only (a-zA-Z0-9) Alphanumeric = validation.NewStringRuleWithError(govalidator.IsAlphanumeric, ErrAlphanumeric) // UTFLetter validates if a string contains unicode letters only UTFLetter = validation.NewStringRuleWithError(govalidator.IsUTFLetter, ErrUTFLetter) // UTFDigit validates if a string contains unicode decimal digits only UTFDigit = validation.NewStringRuleWithError(govalidator.IsUTFDigit, ErrUTFDigit) // UTFLetterNumeric validates if a string contains unicode letters and numbers only UTFLetterNumeric = validation.NewStringRuleWithError(govalidator.IsUTFLetterNumeric, ErrUTFLetterNumeric) // UTFNumeric validates if a string contains unicode number characters (category N) only UTFNumeric = validation.NewStringRuleWithError(isUTFNumeric, ErrUTFNumeric) // LowerCase validates if a string contains lower case unicode letters only LowerCase = validation.NewStringRuleWithError(govalidator.IsLowerCase, ErrLowerCase) // UpperCase validates if a string contains upper case unicode letters only UpperCase = validation.NewStringRuleWithError(govalidator.IsUpperCase, ErrUpperCase) // Hexadecimal validates if a string is a valid hexadecimal number Hexadecimal = validation.NewStringRuleWithError(govalidator.IsHexadecimal, ErrHexadecimal) // HexColor validates if a string is a valid hexadecimal color code HexColor = validation.NewStringRuleWithError(govalidator.IsHexcolor, ErrHexColor) // RGBColor validates if a string is a valid RGB color in the form of rgb(R, G, B) RGBColor = validation.NewStringRuleWithError(govalidator.IsRGBcolor, ErrRGBColor) // Int validates if a string is a valid integer number Int = validation.NewStringRuleWithError(govalidator.IsInt, ErrInt) // Float validates if a string is a floating point number Float = validation.NewStringRuleWithError(govalidator.IsFloat, ErrFloat) // UUIDv3 validates if a string is a valid version 3 UUID UUIDv3 = validation.NewStringRuleWithError(govalidator.IsUUIDv3, ErrUUIDv3) // UUIDv4 validates if a string is a valid version 4 UUID UUIDv4 = validation.NewStringRuleWithError(govalidator.IsUUIDv4, ErrUUIDv4) // UUIDv5 validates if a string is a valid version 5 UUID UUIDv5 = validation.NewStringRuleWithError(govalidator.IsUUIDv5, ErrUUIDv5) // UUID validates if a string is a valid UUID UUID = validation.NewStringRuleWithError(govalidator.IsUUID, ErrUUID) // CreditCard validates if a string is a valid credit card number CreditCard = validation.NewStringRuleWithError(govalidator.IsCreditCard, ErrCreditCard) // ISBN10 validates if a string is an ISBN version 10 ISBN10 = validation.NewStringRuleWithError(govalidator.IsISBN10, ErrISBN10) // ISBN13 validates if a string is an ISBN version 13 ISBN13 = validation.NewStringRuleWithError(govalidator.IsISBN13, ErrISBN13) // ISBN validates if a string is an ISBN (either version 10 or 13) ISBN = validation.NewStringRuleWithError(isISBN, ErrISBN) // JSON validates if a string is in valid JSON format JSON = validation.NewStringRuleWithError(govalidator.IsJSON, ErrJSON) // ASCII validates if a string contains ASCII characters only ASCII = validation.NewStringRuleWithError(govalidator.IsASCII, ErrASCII) // PrintableASCII validates if a string contains printable ASCII characters only PrintableASCII = validation.NewStringRuleWithError(govalidator.IsPrintableASCII, ErrPrintableASCII) // Multibyte validates if a string contains multibyte characters Multibyte = validation.NewStringRuleWithError(govalidator.IsMultibyte, ErrMultibyte) // FullWidth validates if a string contains full-width characters FullWidth = validation.NewStringRuleWithError(govalidator.IsFullWidth, ErrFullWidth) // HalfWidth validates if a string contains half-width characters HalfWidth = validation.NewStringRuleWithError(govalidator.IsHalfWidth, ErrHalfWidth) // VariableWidth validates if a string contains both full-width and half-width characters VariableWidth = validation.NewStringRuleWithError(govalidator.IsVariableWidth, ErrVariableWidth) // Base64 validates if a string is encoded in Base64 Base64 = validation.NewStringRuleWithError(govalidator.IsBase64, ErrBase64) // DataURI validates if a string is a valid base64-encoded data URI DataURI = validation.NewStringRuleWithError(govalidator.IsDataURI, ErrDataURI) // E164 validates if a string is a valid ISO3166 Alpha 2 country code E164 = validation.NewStringRuleWithError(isE164Number, ErrE164) // CountryCode2 validates if a string is a valid ISO3166 Alpha 2 country code CountryCode2 = validation.NewStringRuleWithError(govalidator.IsISO3166Alpha2, ErrCountryCode2) // CountryCode3 validates if a string is a valid ISO3166 Alpha 3 country code CountryCode3 = validation.NewStringRuleWithError(govalidator.IsISO3166Alpha3, ErrCountryCode3) // CurrencyCode validates if a string is a valid IsISO4217 currency code. CurrencyCode = validation.NewStringRuleWithError(govalidator.IsISO4217, ErrCurrencyCode) // DialString validates if a string is a valid dial string that can be passed to Dial() DialString = validation.NewStringRuleWithError(govalidator.IsDialString, ErrDialString) // MAC validates if a string is a MAC address MAC = validation.NewStringRuleWithError(govalidator.IsMAC, ErrMac) // IP validates if a string is a valid IP address (either version 4 or 6) IP = validation.NewStringRuleWithError(govalidator.IsIP, ErrIP) // IPv4 validates if a string is a valid version 4 IP address IPv4 = validation.NewStringRuleWithError(govalidator.IsIPv4, ErrIPv4) // IPv6 validates if a string is a valid version 6 IP address IPv6 = validation.NewStringRuleWithError(govalidator.IsIPv6, ErrIPv6) // Subdomain validates if a string is valid subdomain Subdomain = validation.NewStringRuleWithError(isSubdomain, ErrSubdomain) // Domain validates if a string is valid domain Domain = validation.NewStringRuleWithError(isDomain, ErrDomain) // DNSName validates if a string is valid DNS name DNSName = validation.NewStringRuleWithError(govalidator.IsDNSName, ErrDNSName) // Host validates if a string is a valid IP (both v4 and v6) or a valid DNS name Host = validation.NewStringRuleWithError(govalidator.IsHost, ErrHost) // Port validates if a string is a valid port number Port = validation.NewStringRuleWithError(govalidator.IsPort, ErrPort) // MongoID validates if a string is a valid Mongo ID MongoID = validation.NewStringRuleWithError(govalidator.IsMongoID, ErrMongoID) // Latitude validates if a string is a valid latitude Latitude = validation.NewStringRuleWithError(govalidator.IsLatitude, ErrLatitude) // Longitude validates if a string is a valid longitude Longitude = validation.NewStringRuleWithError(govalidator.IsLongitude, ErrLongitude) // SSN validates if a string is a social security number (SSN) SSN = validation.NewStringRuleWithError(govalidator.IsSSN, ErrSSN) // Semver validates if a string is a valid semantic version Semver = validation.NewStringRuleWithError(govalidator.IsSemver, ErrSemver) ) var ( reDigit = regexp.MustCompile("^[0-9]+$") // Subdomain regex source: https://stackoverflow.com/a/7933253 reSubdomain = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$`) // E164 regex source: https://stackoverflow.com/a/23299989 reE164 = regexp.MustCompile(`^\+?[1-9]\d{1,14}$`) // Domain regex source: https://stackoverflow.com/a/7933253 // Slightly modified: Removed 255 max length validation since Go regex does not // support lookarounds. More info: https://stackoverflow.com/a/38935027 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})$`) ) func isISBN(value string) bool { return govalidator.IsISBN(value, 10) || govalidator.IsISBN(value, 13) } func isDigit(value string) bool { return reDigit.MatchString(value) } func isE164Number(value string) bool { return reE164.MatchString(value) } func isSubdomain(value string) bool { return reSubdomain.MatchString(value) } func isDomain(value string) bool { if len(value) > 255 { return false } return reDomain.MatchString(value) } func isUTFNumeric(value string) bool { for _, c := range value { if unicode.IsNumber(c) == false { return false } } return true } ozzo-validation-4.3.0/is/rules_test.go000066400000000000000000000143321374327524300200170ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package is import ( "strings" "testing" "github.com/go-ozzo/ozzo-validation/v4" "github.com/stretchr/testify/assert" ) func TestAll(t *testing.T) { tests := []struct { tag string rule validation.Rule valid, invalid string err string }{ {"Email", Email, "test@example.com", "example.com", "must be a valid email address"}, {"EmailFormat", EmailFormat, "test@example.com", "example.com", "must be a valid email address"}, {"URL", URL, "http://example.com", "examplecom", "must be a valid URL"}, {"RequestURL", RequestURL, "http://example.com", "examplecom", "must be a valid request URL"}, {"RequestURI", RequestURI, "http://example.com", "examplecom", "must be a valid request URI"}, {"Alpha", Alpha, "abcd", "ab12", "must contain English letters only"}, {"Digit", Digit, "123", "12ab", "must contain digits only"}, {"Alphanumeric", Alphanumeric, "abc123", "abc.123", "must contain English letters and digits only"}, {"UTFLetter", UTFLetter, "abc", "123", "must contain unicode letter characters only"}, {"UTFDigit", UTFDigit, "123", "abc", "must contain unicode decimal digits only"}, {"UTFNumeric", UTFNumeric, "123", "abc.123", "must contain unicode number characters only"}, {"UTFLetterNumeric", UTFLetterNumeric, "abc123", "abc.123", "must contain unicode letters and numbers only"}, {"LowerCase", LowerCase, "abc", "Abc", "must be in lower case"}, {"UpperCase", UpperCase, "ABC", "ABc", "must be in upper case"}, {"IP", IP, "74.125.19.99", "74.125.19.999", "must be a valid IP address"}, {"IPv4", IPv4, "74.125.19.99", "2001:4860:0:2001::68", "must be a valid IPv4 address"}, {"IPv6", IPv6, "2001:4860:0:2001::68", "74.125.19.99", "must be a valid IPv6 address"}, {"MAC", MAC, "0123.4567.89ab", "74.125.19.99", "must be a valid MAC address"}, {"Subdomain", Subdomain, "example-subdomain", "example.com", "must be a valid subdomain"}, {"Domain", Domain, "example-domain.com", "localhost", "must be a valid domain"}, {"Domain", Domain, "example-domain.com", strings.Repeat("a", 256), "must be a valid domain"}, {"DNSName", DNSName, "example.com", "abc%", "must be a valid DNS name"}, {"Host", Host, "example.com", "abc%", "must be a valid IP address or DNS name"}, {"Port", Port, "123", "99999", "must be a valid port number"}, {"Latitude", Latitude, "23.123", "100", "must be a valid latitude"}, {"Longitude", Longitude, "123.123", "abc", "must be a valid longitude"}, {"SSN", SSN, "100-00-1000", "100-0001000", "must be a valid social security number"}, {"Semver", Semver, "1.0.0", "1.0.0.0", "must be a valid semantic version"}, {"ISBN", ISBN, "1-61729-085-8", "1-61729-085-81", "must be a valid ISBN"}, {"ISBN10", ISBN10, "1-61729-085-8", "1-61729-085-81", "must be a valid ISBN-10"}, {"ISBN13", ISBN13, "978-4-87311-368-5", "978-4-87311-368-a", "must be a valid ISBN-13"}, {"UUID", UUID, "a987fbc9-4bed-3078-cf07-9141ba07c9f1", "a987fbc9-4bed-3078-cf07-9141ba07c9f3a", "must be a valid UUID"}, {"UUIDv3", UUIDv3, "b987fbc9-4bed-3078-cf07-9141ba07c9f3", "b987fbc9-4bed-4078-cf07-9141ba07c9f3", "must be a valid UUID v3"}, {"UUIDv4", UUIDv4, "57b73598-8764-4ad0-a76a-679bb6640eb1", "b987fbc9-4bed-3078-cf07-9141ba07c9f3", "must be a valid UUID v4"}, {"UUIDv5", UUIDv5, "987fbc97-4bed-5078-af07-9141ba07c9f3", "b987fbc9-4bed-3078-cf07-9141ba07c9f3", "must be a valid UUID v5"}, {"MongoID", MongoID, "507f1f77bcf86cd799439011", "507f1f77bcf86cd79943901", "must be a valid hex-encoded MongoDB ObjectId"}, {"CreditCard", CreditCard, "375556917985515", "375556917985516", "must be a valid credit card number"}, {"JSON", JSON, "[1, 2]", "[1, 2,]", "must be in valid JSON format"}, {"ASCII", ASCII, "abc", "aabc", "must contain ASCII characters only"}, {"PrintableASCII", PrintableASCII, "abc", "aabc", "must contain printable ASCII characters only"}, {"E164", E164, "+19251232233", "+00124222333", "must be a valid E164 number"}, {"CountryCode2", CountryCode2, "US", "XY", "must be a valid two-letter country code"}, {"CountryCode3", CountryCode3, "USA", "XYZ", "must be a valid three-letter country code"}, {"CurrencyCode", CurrencyCode, "USD", "USS", "must be valid ISO 4217 currency code"}, {"DialString", DialString, "localhost.local:1", "localhost.loc:100000", "must be a valid dial string"}, {"DataURI", DataURI, "data:image/png;base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", "image/gif;base64,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", "must be a Base64-encoded data URI"}, {"Base64", Base64, "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", "image", "must be encoded in Base64"}, {"Multibyte", Multibyte, "abc", "abc", "must contain multibyte characters"}, {"FullWidth", FullWidth, "3ー0", "abc", "must contain full-width characters"}, {"HalfWidth", HalfWidth, "abc123い", "0011", "must contain half-width characters"}, {"VariableWidth", VariableWidth, "3ー0123", "abc", "must contain both full-width and half-width characters"}, {"Hexadecimal", Hexadecimal, "FEF", "FTF", "must be a valid hexadecimal number"}, {"HexColor", HexColor, "F00", "FTF", "must be a valid hexadecimal color code"}, {"RGBColor", RGBColor, "rgb(100, 200, 1)", "abc", "must be a valid RGB color code"}, {"Int", Int, "100", "1.1", "must be an integer number"}, {"Float", Float, "1.1", "a.1", "must be a floating point number"}, {"VariableWidth", VariableWidth, "", "", ""}, } for _, test := range tests { err := test.rule.Validate("") assert.Nil(t, err, test.tag) err = test.rule.Validate(test.valid) assert.Nil(t, err, test.tag) err = test.rule.Validate(&test.valid) assert.Nil(t, err, test.tag) err = test.rule.Validate(test.invalid) assertError(t, test.err, err, test.tag) err = test.rule.Validate(&test.invalid) assertError(t, test.err, err, test.tag) } } func assertError(t *testing.T, expected string, err error, tag string) { if expected == "" { assert.Nil(t, err, tag) } else if assert.NotNil(t, err, tag) { assert.Equal(t, expected, err.Error(), tag) } } ozzo-validation-4.3.0/length.go000066400000000000000000000066111374327524300164750ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "unicode/utf8" ) var ( // ErrLengthTooLong is the error that returns in case of too long length. ErrLengthTooLong = NewError("validation_length_too_long", "the length must be no more than {{.max}}") // ErrLengthTooShort is the error that returns in case of too short length. ErrLengthTooShort = NewError("validation_length_too_short", "the length must be no less than {{.min}}") // ErrLengthInvalid is the error that returns in case of an invalid length. ErrLengthInvalid = NewError("validation_length_invalid", "the length must be exactly {{.min}}") // ErrLengthOutOfRange is the error that returns in case of out of range length. ErrLengthOutOfRange = NewError("validation_length_out_of_range", "the length must be between {{.min}} and {{.max}}") // ErrLengthEmptyRequired is the error that returns in case of non-empty value. ErrLengthEmptyRequired = NewError("validation_length_empty_required", "the value must be empty") ) // Length returns a validation rule that checks if a value's length is within the specified range. // If max is 0, it means there is no upper bound for the length. // This rule should only be used for validating strings, slices, maps, and arrays. // An empty value is considered valid. Use the Required rule to make sure a value is not empty. func Length(min, max int) LengthRule { return LengthRule{min: min, max: max, err: buildLengthRuleError(min, max)} } // RuneLength returns a validation rule that checks if a string's rune length is within the specified range. // If max is 0, it means there is no upper bound for the length. // This rule should only be used for validating strings, slices, maps, and arrays. // An empty value is considered valid. Use the Required rule to make sure a value is not empty. // If the value being validated is not a string, the rule works the same as Length. func RuneLength(min, max int) LengthRule { r := Length(min, max) r.rune = true return r } // LengthRule is a validation rule that checks if a value's length is within the specified range. type LengthRule struct { err Error min, max int rune bool } // Validate checks if the given value is valid or not. func (r LengthRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } var ( l int err error ) if s, ok := value.(string); ok && r.rune { l = utf8.RuneCountInString(s) } else if l, err = LengthOfValue(value); err != nil { return err } if r.min > 0 && l < r.min || r.max > 0 && l > r.max || r.min == 0 && r.max == 0 && l > 0 { return r.err } return nil } // Error sets the error message for the rule. func (r LengthRule) Error(message string) LengthRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r LengthRule) ErrorObject(err Error) LengthRule { r.err = err return r } func buildLengthRuleError(min, max int) (err Error) { if min == 0 && max > 0 { err = ErrLengthTooLong } else if min > 0 && max == 0 { err = ErrLengthTooShort } else if min > 0 && max > 0 { if min == max { err = ErrLengthInvalid } else { err = ErrLengthOutOfRange } } else { err = ErrLengthEmptyRequired } return err.SetParams(map[string]interface{}{"min": min, "max": max}) } ozzo-validation-4.3.0/length_test.go000066400000000000000000000063121374327524300175320ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "database/sql" "testing" "github.com/stretchr/testify/assert" ) func TestLength(t *testing.T) { var v *string tests := []struct { tag string min, max int value interface{} err string }{ {"t1", 2, 4, "abc", ""}, {"t2", 2, 4, "", ""}, {"t3", 2, 4, "abcdf", "the length must be between 2 and 4"}, {"t4", 0, 4, "ab", ""}, {"t5", 0, 4, "abcde", "the length must be no more than 4"}, {"t6", 2, 0, "ab", ""}, {"t7", 2, 0, "a", "the length must be no less than 2"}, {"t8", 2, 0, v, ""}, {"t9", 2, 0, 123, "cannot get the length of int"}, {"t10", 2, 4, sql.NullString{String: "abc", Valid: true}, ""}, {"t11", 2, 4, sql.NullString{String: "", Valid: true}, ""}, {"t12", 2, 4, &sql.NullString{String: "abc", Valid: true}, ""}, {"t13", 2, 2, "abcdf", "the length must be exactly 2"}, {"t14", 2, 2, "ab", ""}, {"t15", 0, 0, "", ""}, {"t16", 0, 0, "ab", "the value must be empty"}, } for _, test := range tests { r := Length(test.min, test.max) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestRuneLength(t *testing.T) { var v *string tests := []struct { tag string min, max int value interface{} err string }{ {"t1", 2, 4, "abc", ""}, {"t1.1", 2, 3, "💥💥", ""}, {"t1.2", 2, 3, "💥💥💥", ""}, {"t1.3", 2, 3, "💥", "the length must be between 2 and 3"}, {"t1.4", 2, 3, "💥💥💥💥", "the length must be between 2 and 3"}, {"t2", 2, 4, "", ""}, {"t3", 2, 4, "abcdf", "the length must be between 2 and 4"}, {"t4", 0, 4, "ab", ""}, {"t5", 0, 4, "abcde", "the length must be no more than 4"}, {"t6", 2, 0, "ab", ""}, {"t7", 2, 0, "a", "the length must be no less than 2"}, {"t8", 2, 0, v, ""}, {"t9", 2, 0, 123, "cannot get the length of int"}, {"t10", 2, 4, sql.NullString{String: "abc", Valid: true}, ""}, {"t11", 2, 4, sql.NullString{String: "", Valid: true}, ""}, {"t12", 2, 4, &sql.NullString{String: "abc", Valid: true}, ""}, {"t13", 2, 3, &sql.NullString{String: "💥💥", Valid: true}, ""}, {"t14", 2, 3, &sql.NullString{String: "💥", Valid: true}, "the length must be between 2 and 3"}, } for _, test := range tests { r := RuneLength(test.min, test.max) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func Test_LengthRule_Error(t *testing.T) { r := Length(10, 20) assert.Equal(t, "the length must be between 10 and 20", r.Validate("abc").Error()) r = Length(0, 20) assert.Equal(t, "the length must be no more than 20", r.Validate(make([]string, 21)).Error()) r = Length(10, 0) assert.Equal(t, "the length must be no less than 10", r.Validate([9]string{}).Error()) r = Length(0, 0) assert.Equal(t, "validation_length_empty_required", r.err.Code()) r = r.Error("123") assert.Equal(t, "123", r.err.Message()) } func TestLengthRule_ErrorObject(t *testing.T) { r := Length(10, 20) err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) } ozzo-validation-4.3.0/map.go000066400000000000000000000073301374327524300157700ustar00rootroot00000000000000package validation import ( "context" "errors" "fmt" "reflect" ) var ( // ErrNotMap is the error that the value being validated is not a map. ErrNotMap = errors.New("only a map can be validated") // ErrKeyWrongType is the error returned in case of an incorrect key type. ErrKeyWrongType = NewError("validation_key_wrong_type", "key not the correct type") // ErrKeyMissing is the error returned in case of a missing key. ErrKeyMissing = NewError("validation_key_missing", "required key is missing") // ErrKeyUnexpected is the error returned in case of an unexpected key. ErrKeyUnexpected = NewError("validation_key_unexpected", "key not expected") ) type ( // MapRule represents a rule set associated with a map. MapRule struct { keys []*KeyRules allowExtraKeys bool } // KeyRules represents a rule set associated with a map key. KeyRules struct { key interface{} optional bool rules []Rule } ) // Map returns a validation rule that checks the keys and values of a map. // This rule should only be used for validating maps, or a validation error will be reported. // Use Key() to specify map keys that need to be validated. Each Key() call specifies a single key which can // be associated with multiple rules. // For example, // validation.Map( // validation.Key("Name", validation.Required), // validation.Key("Value", validation.Required, validation.Length(5, 10)), // ) // // A nil value is considered valid. Use the Required rule to make sure a map value is present. func Map(keys ...*KeyRules) MapRule { return MapRule{keys: keys} } // AllowExtraKeys configures the rule to ignore extra keys. func (r MapRule) AllowExtraKeys() MapRule { r.allowExtraKeys = true return r } // Validate checks if the given value is valid or not. func (r MapRule) Validate(m interface{}) error { return r.ValidateWithContext(nil, m) } // ValidateWithContext checks if the given value is valid or not. func (r MapRule) ValidateWithContext(ctx context.Context, m interface{}) error { value := reflect.ValueOf(m) if value.Kind() == reflect.Ptr { value = value.Elem() } if value.Kind() != reflect.Map { // must be a map return NewInternalError(ErrNotMap) } if value.IsNil() { // treat a nil map as valid return nil } errs := Errors{} kt := value.Type().Key() var extraKeys map[interface{}]bool if !r.allowExtraKeys { extraKeys = make(map[interface{}]bool, value.Len()) for _, k := range value.MapKeys() { extraKeys[k.Interface()] = true } } for _, kr := range r.keys { var err error if kv := reflect.ValueOf(kr.key); !kt.AssignableTo(kv.Type()) { err = ErrKeyWrongType } else if vv := value.MapIndex(kv); !vv.IsValid() { if !kr.optional { err = ErrKeyMissing } } else if ctx == nil { err = Validate(vv.Interface(), kr.rules...) } else { err = ValidateWithContext(ctx, vv.Interface(), kr.rules...) } if err != nil { if ie, ok := err.(InternalError); ok && ie.InternalError() != nil { return err } errs[getErrorKeyName(kr.key)] = err } if !r.allowExtraKeys { delete(extraKeys, kr.key) } } if !r.allowExtraKeys { for key := range extraKeys { errs[getErrorKeyName(key)] = ErrKeyUnexpected } } if len(errs) > 0 { return errs } return nil } // Key specifies a map key and the corresponding validation rules. func Key(key interface{}, rules ...Rule) *KeyRules { return &KeyRules{ key: key, rules: rules, } } // Optional configures the rule to ignore the key if missing. func (r *KeyRules) Optional() *KeyRules { r.optional = true return r } // getErrorKeyName returns the name that should be used to represent the validation error of a map key. func getErrorKeyName(key interface{}) string { return fmt.Sprintf("%v", key) } ozzo-validation-4.3.0/map_test.go000066400000000000000000000130171374327524300170260ustar00rootroot00000000000000package validation import ( "context" "testing" "github.com/stretchr/testify/assert" ) func TestMap(t *testing.T) { var m0 map[string]interface{} 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"}} m2 := map[string]interface{}{"E": String123("xyz"), "F": (*String123)(nil)} m3 := map[string]interface{}{"M3": Model3{}} m4 := map[string]interface{}{"M3": Model3{A: "abc"}} m5 := map[string]interface{}{"A": "internal", "B": ""} m6 := map[int]string{11: "abc", 22: "xyz"} tests := []struct { tag string model interface{} rules []*KeyRules err string }{ // empty rules {"t1.1", m1, []*KeyRules{}, ""}, {"t1.2", m1, []*KeyRules{Key("A"), Key("B")}, ""}, // normal rules {"t2.1", m1, []*KeyRules{Key("A", &validateAbc{}), Key("B", &validateXyz{})}, ""}, {"t2.2", m1, []*KeyRules{Key("A", &validateXyz{}), Key("B", &validateAbc{})}, "A: error xyz; B: error abc."}, {"t2.3", m1, []*KeyRules{Key("A", &validateXyz{}), Key("c", &validateXyz{})}, "A: error xyz; c: error xyz."}, {"t2.4", m1, []*KeyRules{Key("D", Length(0, 5))}, ""}, {"t2.5", m1, []*KeyRules{Key("F", Length(0, 5))}, ""}, {"t2.6", m1, []*KeyRules{Key("H", Each(&validateAbc{})), Key("I", Each(&validateAbc{}))}, ""}, {"t2.7", m1, []*KeyRules{Key("H", Each(&validateXyz{})), Key("I", Each(&validateXyz{}))}, "H: (0: error xyz; 1: error xyz.); I: (foo: error xyz.)."}, {"t2.8", m1, []*KeyRules{Key("I", Map(Key("foo", &validateAbc{})))}, ""}, {"t2.9", m1, []*KeyRules{Key("I", Map(Key("foo", &validateXyz{})))}, "I: (foo: error xyz.)."}, // non-map value {"t3.1", &m1, []*KeyRules{}, ""}, {"t3.2", nil, []*KeyRules{}, ErrNotMap.Error()}, {"t3.3", m0, []*KeyRules{}, ""}, {"t3.4", &m0, []*KeyRules{}, ""}, {"t3.5", 123, []*KeyRules{}, ErrNotMap.Error()}, // invalid key spec {"t4.1", m1, []*KeyRules{Key(123)}, "123: key not the correct type."}, {"t4.2", m1, []*KeyRules{Key("X")}, "X: required key is missing."}, {"t4.3", m1, []*KeyRules{Key("X").Optional()}, ""}, // non-string keys {"t5.1", m6, []*KeyRules{Key(11, &validateAbc{}), Key(22, &validateXyz{})}, ""}, {"t5.2", m6, []*KeyRules{Key(11, &validateXyz{}), Key(22, &validateAbc{})}, "11: error xyz; 22: error abc."}, // validatable value {"t6.1", m2, []*KeyRules{Key("E")}, "E: error 123."}, {"t6.2", m2, []*KeyRules{Key("E", Skip)}, ""}, {"t6.3", m2, []*KeyRules{Key("E", Skip.When(true))}, ""}, {"t6.4", m2, []*KeyRules{Key("E", Skip.When(false))}, "E: error 123."}, // Required, NotNil {"t7.1", m2, []*KeyRules{Key("F", Required)}, "F: cannot be blank."}, {"t7.2", m2, []*KeyRules{Key("F", NotNil)}, "F: is required."}, {"t7.3", m2, []*KeyRules{Key("F", Skip, Required)}, ""}, {"t7.4", m2, []*KeyRules{Key("F", Skip, NotNil)}, ""}, {"t7.5", m2, []*KeyRules{Key("F", Skip.When(true), Required)}, ""}, {"t7.6", m2, []*KeyRules{Key("F", Skip.When(true), NotNil)}, ""}, {"t7.7", m2, []*KeyRules{Key("F", Skip.When(false), Required)}, "F: cannot be blank."}, {"t7.8", m2, []*KeyRules{Key("F", Skip.When(false), NotNil)}, "F: is required."}, // validatable structs {"t8.1", m3, []*KeyRules{Key("M3", Skip)}, ""}, {"t8.2", m3, []*KeyRules{Key("M3")}, "M3: (A: error abc.)."}, {"t8.3", m4, []*KeyRules{Key("M3")}, ""}, // internal error {"t9.1", m5, []*KeyRules{Key("A", &validateAbc{}), Key("B", Required), Key("A", &validateInternalError{})}, "error internal"}, } for _, test := range tests { err1 := Validate(test.model, Map(test.rules...).AllowExtraKeys()) err2 := ValidateWithContext(context.Background(), test.model, Map(test.rules...).AllowExtraKeys()) assertError(t, test.err, err1, test.tag) assertError(t, test.err, err2, test.tag) } a := map[string]interface{}{"Name": "name", "Value": "demo", "Extra": true} err := Validate(a, Map( Key("Name", Required), Key("Value", Required, Length(5, 10)), )) assert.EqualError(t, err, "Extra: key not expected; Value: the length must be between 5 and 10.") } func TestMapWithContext(t *testing.T) { m1 := map[string]interface{}{"A": "abc", "B": "xyz", "c": "abc", "g": "xyz"} m2 := map[string]interface{}{"A": "internal", "B": ""} tests := []struct { tag string model interface{} rules []*KeyRules err string }{ // normal rules {"t1.1", m1, []*KeyRules{Key("A", &validateContextAbc{}), Key("B", &validateContextXyz{})}, ""}, {"t1.2", m1, []*KeyRules{Key("A", &validateContextXyz{}), Key("B", &validateContextAbc{})}, "A: error xyz; B: error abc."}, {"t1.3", m1, []*KeyRules{Key("A", &validateContextXyz{}), Key("c", &validateContextXyz{})}, "A: error xyz; c: error xyz."}, {"t1.4", m1, []*KeyRules{Key("g", &validateContextAbc{})}, "g: error abc."}, // skip rule {"t2.1", m1, []*KeyRules{Key("g", Skip, &validateContextAbc{})}, ""}, {"t2.2", m1, []*KeyRules{Key("g", &validateContextAbc{}, Skip)}, "g: error abc."}, // internal error {"t3.1", m2, []*KeyRules{Key("A", &validateContextAbc{}), Key("B", Required), Key("A", &validateInternalError{})}, "error internal"}, } for _, test := range tests { err := ValidateWithContext(context.Background(), test.model, Map(test.rules...).AllowExtraKeys()) assertError(t, test.err, err, test.tag) } a := map[string]interface{}{"Name": "name", "Value": "demo", "Extra": true} err := ValidateWithContext(context.Background(), a, Map( Key("Name", Required), Key("Value", Required, Length(5, 10)), )) if assert.NotNil(t, err) { assert.Equal(t, "Extra: key not expected; Value: the length must be between 5 and 10.", err.Error()) } } ozzo-validation-4.3.0/match.go000066400000000000000000000030611374327524300163040ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "regexp" ) // ErrMatchInvalid is the error that returns in case of invalid format. var ErrMatchInvalid = NewError("validation_match_invalid", "must be in a valid format") // Match returns a validation rule that checks if a value matches the specified regular expression. // This rule should only be used for validating strings and byte slices, or a validation error will be reported. // An empty value is considered valid. Use the Required rule to make sure a value is not empty. func Match(re *regexp.Regexp) MatchRule { return MatchRule{ re: re, err: ErrMatchInvalid, } } // MatchRule is a validation rule that checks if a value matches the specified regular expression. type MatchRule struct { re *regexp.Regexp err Error } // Validate checks if the given value is valid or not. func (r MatchRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil { return nil } isString, str, isBytes, bs := StringOrBytes(value) if isString && (str == "" || r.re.MatchString(str)) { return nil } else if isBytes && (len(bs) == 0 || r.re.Match(bs)) { return nil } return r.err } // Error sets the error message for the rule. func (r MatchRule) Error(message string) MatchRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r MatchRule) ErrorObject(err Error) MatchRule { r.err = err return r } ozzo-validation-4.3.0/match_test.go000066400000000000000000000024751374327524300173530ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "regexp" "testing" "github.com/stretchr/testify/assert" ) func TestMatch(t *testing.T) { var v2 *string tests := []struct { tag string re string value interface{} err string }{ {"t1", "[a-z]+", "abc", ""}, {"t2", "[a-z]+", "", ""}, {"t3", "[a-z]+", v2, ""}, {"t4", "[a-z]+", "123", "must be in a valid format"}, {"t5", "[a-z]+", []byte("abc"), ""}, {"t6", "[a-z]+", []byte("123"), "must be in a valid format"}, {"t7", "[a-z]+", []byte(""), ""}, {"t8", "[a-z]+", nil, ""}, } for _, test := range tests { r := Match(regexp.MustCompile(test.re)) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func Test_MatchRule_Error(t *testing.T) { r := Match(regexp.MustCompile("[a-z]+")) assert.Equal(t, "must be in a valid format", r.Validate("13").Error()) r = r.Error("123") assert.Equal(t, "123", r.err.Message()) } func TestMatchRule_ErrorObject(t *testing.T) { r := Match(regexp.MustCompile("[a-z]+")) err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) } ozzo-validation-4.3.0/minmax.go000066400000000000000000000131261374327524300165040ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "fmt" "reflect" "time" ) var ( // ErrMinGreaterEqualThanRequired is the error that returns when a value is less than a specified threshold. ErrMinGreaterEqualThanRequired = NewError("validation_min_greater_equal_than_required", "must be no less than {{.threshold}}") // ErrMaxLessEqualThanRequired is the error that returns when a value is greater than a specified threshold. ErrMaxLessEqualThanRequired = NewError("validation_max_less_equal_than_required", "must be no greater than {{.threshold}}") // ErrMinGreaterThanRequired is the error that returns when a value is less than or equal to a specified threshold. ErrMinGreaterThanRequired = NewError("validation_min_greater_than_required", "must be greater than {{.threshold}}") // ErrMaxLessThanRequired is the error that returns when a value is greater than or equal to a specified threshold. ErrMaxLessThanRequired = NewError("validation_max_less_than_required", "must be less than {{.threshold}}") ) // ThresholdRule is a validation rule that checks if a value satisfies the specified threshold requirement. type ThresholdRule struct { threshold interface{} operator int err Error } const ( greaterThan = iota greaterEqualThan lessThan lessEqualThan ) // Min returns a validation rule that checks if a value is greater or equal than the specified value. // By calling Exclusive, the rule will check if the value is strictly greater than the specified value. // Note that the value being checked and the threshold value must be of the same type. // Only int, uint, float and time.Time types are supported. // An empty value is considered valid. Please use the Required rule to make sure a value is not empty. func Min(min interface{}) ThresholdRule { return ThresholdRule{ threshold: min, operator: greaterEqualThan, err: ErrMinGreaterEqualThanRequired, } } // Max returns a validation rule that checks if a value is less or equal than the specified value. // By calling Exclusive, the rule will check if the value is strictly less than the specified value. // Note that the value being checked and the threshold value must be of the same type. // Only int, uint, float and time.Time types are supported. // An empty value is considered valid. Please use the Required rule to make sure a value is not empty. func Max(max interface{}) ThresholdRule { return ThresholdRule{ threshold: max, operator: lessEqualThan, err: ErrMaxLessEqualThanRequired, } } // Exclusive sets the comparison to exclude the boundary value. func (r ThresholdRule) Exclusive() ThresholdRule { if r.operator == greaterEqualThan { r.operator = greaterThan r.err = ErrMinGreaterThanRequired } else if r.operator == lessEqualThan { r.operator = lessThan r.err = ErrMaxLessThanRequired } return r } // Validate checks if the given value is valid or not. func (r ThresholdRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } rv := reflect.ValueOf(r.threshold) switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v, err := ToInt(value) if err != nil { return err } if r.compareInt(rv.Int(), v) { return nil } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: v, err := ToUint(value) if err != nil { return err } if r.compareUint(rv.Uint(), v) { return nil } case reflect.Float32, reflect.Float64: v, err := ToFloat(value) if err != nil { return err } if r.compareFloat(rv.Float(), v) { return nil } case reflect.Struct: t, ok := r.threshold.(time.Time) if !ok { return fmt.Errorf("type not supported: %v", rv.Type()) } v, ok := value.(time.Time) if !ok { return fmt.Errorf("cannot convert %v to time.Time", reflect.TypeOf(value)) } if v.IsZero() || r.compareTime(t, v) { return nil } default: return fmt.Errorf("type not supported: %v", rv.Type()) } return r.err.SetParams(map[string]interface{}{"threshold": r.threshold}) } // Error sets the error message for the rule. func (r ThresholdRule) Error(message string) ThresholdRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r ThresholdRule) ErrorObject(err Error) ThresholdRule { r.err = err return r } func (r ThresholdRule) compareInt(threshold, value int64) bool { switch r.operator { case greaterThan: return value > threshold case greaterEqualThan: return value >= threshold case lessThan: return value < threshold default: return value <= threshold } } func (r ThresholdRule) compareUint(threshold, value uint64) bool { switch r.operator { case greaterThan: return value > threshold case greaterEqualThan: return value >= threshold case lessThan: return value < threshold default: return value <= threshold } } func (r ThresholdRule) compareFloat(threshold, value float64) bool { switch r.operator { case greaterThan: return value > threshold case greaterEqualThan: return value >= threshold case lessThan: return value < threshold default: return value <= threshold } } func (r ThresholdRule) compareTime(threshold, value time.Time) bool { switch r.operator { case greaterThan: return value.After(threshold) case greaterEqualThan: return value.After(threshold) || value.Equal(threshold) case lessThan: return value.Before(threshold) default: return value.Before(threshold) || value.Equal(threshold) } } ozzo-validation-4.3.0/minmax_test.go000066400000000000000000000115601374327524300175430ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestMin(t *testing.T) { date0 := time.Time{} date20000101 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) date20001201 := time.Date(2000, 12, 1, 0, 0, 0, 0, time.UTC) date20000601 := time.Date(2000, 6, 1, 0, 0, 0, 0, time.UTC) tests := []struct { tag string threshold interface{} exclusive bool value interface{} err string }{ // int cases {"t1.1", 1, false, 1, ""}, {"t1.2", 1, false, 2, ""}, {"t1.3", 1, false, -1, "must be no less than 1"}, {"t1.4", 1, false, 0, ""}, {"t1.5", 1, true, 1, "must be greater than 1"}, {"t1.6", 1, false, "1", "cannot convert string to int64"}, {"t1.7", "1", false, 1, "type not supported: string"}, // uint cases {"t2.1", uint(2), false, uint(2), ""}, {"t2.2", uint(2), false, uint(3), ""}, {"t2.3", uint(2), false, uint(1), "must be no less than 2"}, {"t2.4", uint(2), false, uint(0), ""}, {"t2.5", uint(2), true, uint(2), "must be greater than 2"}, {"t2.6", uint(2), false, "1", "cannot convert string to uint64"}, // float cases {"t3.1", float64(2), false, float64(2), ""}, {"t3.2", float64(2), false, float64(3), ""}, {"t3.3", float64(2), false, float64(1), "must be no less than 2"}, {"t3.4", float64(2), false, float64(0), ""}, {"t3.5", float64(2), true, float64(2), "must be greater than 2"}, {"t3.6", float64(2), false, "1", "cannot convert string to float64"}, // Time cases {"t4.1", date20000601, false, date20000601, ""}, {"t4.2", date20000601, false, date20001201, ""}, {"t4.3", date20000601, false, date20000101, "must be no less than 2000-06-01 00:00:00 +0000 UTC"}, {"t4.4", date20000601, false, date0, ""}, {"t4.5", date20000601, true, date20000601, "must be greater than 2000-06-01 00:00:00 +0000 UTC"}, {"t4.6", date20000601, true, 1, "cannot convert int to time.Time"}, {"t4.7", struct{}{}, false, 1, "type not supported: struct {}"}, {"t4.8", date0, false, date20000601, ""}, } for _, test := range tests { r := Min(test.threshold) if test.exclusive { r = r.Exclusive() } err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestMinError(t *testing.T) { r := Min(10) assert.Equal(t, "must be no less than 10", r.Validate(9).Error()) r = r.Error("123") assert.Equal(t, "123", r.err.Message()) } func TestMax(t *testing.T) { date0 := time.Time{} date20000101 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) date20001201 := time.Date(2000, 12, 1, 0, 0, 0, 0, time.UTC) date20000601 := time.Date(2000, 6, 1, 0, 0, 0, 0, time.UTC) tests := []struct { tag string threshold interface{} exclusive bool value interface{} err string }{ // int cases {"t1.1", 2, false, 2, ""}, {"t1.2", 2, false, 1, ""}, {"t1.3", 2, false, 3, "must be no greater than 2"}, {"t1.4", 2, false, 0, ""}, {"t1.5", 2, true, 2, "must be less than 2"}, {"t1.6", 2, false, "1", "cannot convert string to int64"}, {"t1.7", "1", false, 1, "type not supported: string"}, // uint cases {"t2.1", uint(2), false, uint(2), ""}, {"t2.2", uint(2), false, uint(1), ""}, {"t2.3", uint(2), false, uint(3), "must be no greater than 2"}, {"t2.4", uint(2), false, uint(0), ""}, {"t2.5", uint(2), true, uint(2), "must be less than 2"}, {"t2.6", uint(2), false, "1", "cannot convert string to uint64"}, // float cases {"t3.1", float64(2), false, float64(2), ""}, {"t3.2", float64(2), false, float64(1), ""}, {"t3.3", float64(2), false, float64(3), "must be no greater than 2"}, {"t3.4", float64(2), false, float64(0), ""}, {"t3.5", float64(2), true, float64(2), "must be less than 2"}, {"t3.6", float64(2), false, "1", "cannot convert string to float64"}, // Time cases {"t4.1", date20000601, false, date20000601, ""}, {"t4.2", date20000601, false, date20000101, ""}, {"t4.3", date20000601, false, date20001201, "must be no greater than 2000-06-01 00:00:00 +0000 UTC"}, {"t4.4", date20000601, false, date0, ""}, {"t4.5", date20000601, true, date20000601, "must be less than 2000-06-01 00:00:00 +0000 UTC"}, {"t4.6", date20000601, true, 1, "cannot convert int to time.Time"}, } for _, test := range tests { r := Max(test.threshold) if test.exclusive { r = r.Exclusive() } err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestMaxError(t *testing.T) { r := Max(10) assert.Equal(t, "must be no greater than 10", r.Validate(11).Error()) r = r.Error("123") assert.Equal(t, "123", r.err.Message()) } func TestThresholdRule_ErrorObject(t *testing.T) { r := Max(10) err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) } ozzo-validation-4.3.0/multipleof.go000066400000000000000000000032101374327524300173640ustar00rootroot00000000000000package validation import ( "fmt" "reflect" ) // ErrMultipleOfInvalid is the error that returns when a value is not multiple of a base. var ErrMultipleOfInvalid = NewError("validation_multiple_of_invalid", "must be multiple of {{.base}}") // MultipleOf returns a validation rule that checks if a value is a multiple of the "base" value. // Note that "base" should be of integer type. func MultipleOf(base interface{}) MultipleOfRule { return MultipleOfRule{ base: base, err: ErrMultipleOfInvalid, } } // MultipleOfRule is a validation rule that checks if a value is a multiple of the "base" value. type MultipleOfRule struct { base interface{} err Error } // Error sets the error message for the rule. func (r MultipleOfRule) Error(message string) MultipleOfRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r MultipleOfRule) ErrorObject(err Error) MultipleOfRule { r.err = err return r } // Validate checks if the value is a multiple of the "base" value. func (r MultipleOfRule) Validate(value interface{}) error { rv := reflect.ValueOf(r.base) switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v, err := ToInt(value) if err != nil { return err } if v%rv.Int() == 0 { return nil } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: v, err := ToUint(value) if err != nil { return err } if v%rv.Uint() == 0 { return nil } default: return fmt.Errorf("type not supported: %v", rv.Type()) } return r.err.SetParams(map[string]interface{}{"base": r.base}) } ozzo-validation-4.3.0/multipleof_test.go000066400000000000000000000024621374327524300204330ustar00rootroot00000000000000// Copyright 2016 Qiang Xue, Google LLC. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "github.com/stretchr/testify/assert" ) func TestMultipleOf(t *testing.T) { r := MultipleOf(10) assert.Equal(t, "must be multiple of 10", r.Validate(11).Error()) assert.Equal(t, nil, r.Validate(20)) assert.Equal(t, "cannot convert float32 to int64", r.Validate(float32(20)).Error()) r2 := MultipleOf("some string ....") assert.Equal(t, "type not supported: string", r2.Validate(10).Error()) r3 := MultipleOf(uint(10)) assert.Equal(t, "must be multiple of 10", r3.Validate(uint(11)).Error()) assert.Equal(t, nil, r3.Validate(uint(20))) assert.Equal(t, "cannot convert float32 to uint64", r3.Validate(float32(20)).Error()) } func Test_MultipleOf_Error(t *testing.T) { r := MultipleOf(10) assert.Equal(t, "must be multiple of 10", r.Validate(3).Error()) r = r.Error("some error string ...") assert.Equal(t, "some error string ...", r.err.Message()) } func TestMultipleOfRule_ErrorObject(t *testing.T) { r := MultipleOf(10) err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) } ozzo-validation-4.3.0/not_in.go000066400000000000000000000027021374327524300164770ustar00rootroot00000000000000// Copyright 2018 Qiang Xue, Google LLC. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation // ErrNotInInvalid is the error that returns when a value is in a list. var ErrNotInInvalid = NewError("validation_not_in_invalid", "must not be in list") // NotIn returns a validation rule that checks if a value is absent from the given list of values. // Note that the value being checked and the possible range of values must be of the same type. // An empty value is considered valid. Use the Required rule to make sure a value is not empty. func NotIn(values ...interface{}) NotInRule { return NotInRule{ elements: values, err: ErrNotInInvalid, } } // NotInRule is a validation rule that checks if a value is absent from the given list of values. type NotInRule struct { elements []interface{} err Error } // Validate checks if the given value is valid or not. func (r NotInRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } for _, e := range r.elements { if e == value { return r.err } } return nil } // Error sets the error message for the rule. func (r NotInRule) Error(message string) NotInRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r NotInRule) ErrorObject(err Error) NotInRule { r.err = err return r } ozzo-validation-4.3.0/not_in_test.go000066400000000000000000000025041374327524300175360ustar00rootroot00000000000000// Copyright 2016 Qiang Xue, Google LLC. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "github.com/stretchr/testify/assert" ) func TestNotIn(t *testing.T) { v := 1 var v2 *int var tests = []struct { tag string values []interface{} value interface{} err string }{ {"t0", []interface{}{1, 2}, 0, ""}, {"t1", []interface{}{1, 2}, 1, "must not be in list"}, {"t2", []interface{}{1, 2}, 2, "must not be in list"}, {"t3", []interface{}{1, 2}, 3, ""}, {"t4", []interface{}{}, 3, ""}, {"t5", []interface{}{1, 2}, "1", ""}, {"t6", []interface{}{1, 2}, &v, "must not be in list"}, {"t7", []interface{}{1, 2}, v2, ""}, } for _, test := range tests { r := NotIn(test.values...) err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func Test_NotInRule_Error(t *testing.T) { r := NotIn(1, 2, 3) assert.Equal(t, "must not be in list", r.Validate(1).Error()) r = r.Error("123") assert.Equal(t, "123", r.err.Message()) } func TestNotInRule_ErrorObject(t *testing.T) { r := NotIn(1, 2, 3) err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) } ozzo-validation-4.3.0/not_nil.go000066400000000000000000000021631374327524300166540ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation // ErrNotNilRequired is the error that returns when a value is Nil. var ErrNotNilRequired = NewError("validation_not_nil_required", "is required") // NotNil is a validation rule that checks if a value is not nil. // NotNil only handles types including interface, pointer, slice, and map. // All other types are considered valid. var NotNil = notNilRule{} type notNilRule struct { err Error } // Validate checks if the given value is valid or not. func (r notNilRule) Validate(value interface{}) error { _, isNil := Indirect(value) if isNil { if r.err != nil { return r.err } return ErrNotNilRequired } return nil } // Error sets the error message for the rule. func (r notNilRule) Error(message string) notNilRule { if r.err == nil { r.err = ErrNotNilRequired } r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r notNilRule) ErrorObject(err Error) notNilRule { r.err = err return r } ozzo-validation-4.3.0/not_nil_test.go000066400000000000000000000024231374327524300177120ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "github.com/stretchr/testify/assert" ) type MyInterface interface { Hello() } func TestNotNil(t *testing.T) { var v1 []int var v2 map[string]int var v3 *int var v4 interface{} var v5 MyInterface tests := []struct { tag string value interface{} err string }{ {"t1", v1, "is required"}, {"t2", v2, "is required"}, {"t3", v3, "is required"}, {"t4", v4, "is required"}, {"t5", v5, "is required"}, {"t6", "", ""}, {"t7", 0, ""}, } for _, test := range tests { r := NotNil err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func Test_notNilRule_Error(t *testing.T) { r := NotNil assert.Equal(t, "is required", r.Validate(nil).Error()) r2 := r.Error("123") assert.Equal(t, "is required", r.Validate(nil).Error()) assert.Equal(t, "123", r2.err.Message()) } func TestNotNilRule_ErrorObject(t *testing.T) { r := NotNil err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) assert.NotEqual(t, err, NotNil.err) } ozzo-validation-4.3.0/required.go000066400000000000000000000041771374327524300170410ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation var ( // ErrRequired is the error that returns when a value is required. ErrRequired = NewError("validation_required", "cannot be blank") // ErrNilOrNotEmpty is the error that returns when a value is not nil and is empty. ErrNilOrNotEmpty = NewError("validation_nil_or_not_empty_required", "cannot be blank") ) // Required is a validation rule that checks if a value is not empty. // A value is considered not empty if // - integer, float: not zero // - bool: true // - string, array, slice, map: len() > 0 // - interface, pointer: not nil and the referenced value is not empty // - any other types var Required = RequiredRule{skipNil: false, condition: true} // NilOrNotEmpty checks if a value is a nil pointer or a value that is not empty. // NilOrNotEmpty differs from Required in that it treats a nil pointer as valid. var NilOrNotEmpty = RequiredRule{skipNil: true, condition: true} // RequiredRule is a rule that checks if a value is not empty. type RequiredRule struct { condition bool skipNil bool err Error } // Validate checks if the given value is valid or not. func (r RequiredRule) Validate(value interface{}) error { if r.condition { value, isNil := Indirect(value) if r.skipNil && !isNil && IsEmpty(value) || !r.skipNil && (isNil || IsEmpty(value)) { if r.err != nil { return r.err } if r.skipNil { return ErrNilOrNotEmpty } return ErrRequired } } return nil } // When sets the condition that determines if the validation should be performed. func (r RequiredRule) When(condition bool) RequiredRule { r.condition = condition return r } // Error sets the error message for the rule. func (r RequiredRule) Error(message string) RequiredRule { if r.err == nil { if r.skipNil { r.err = ErrNilOrNotEmpty } else { r.err = ErrRequired } } r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r RequiredRule) ErrorObject(err Error) RequiredRule { r.err = err return r } ozzo-validation-4.3.0/required_test.go000066400000000000000000000042301374327524300200660ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestRequired(t *testing.T) { s1 := "123" s2 := "" var time1 time.Time tests := []struct { tag string value interface{} err string }{ {"t1", 123, ""}, {"t2", "", "cannot be blank"}, {"t3", &s1, ""}, {"t4", &s2, "cannot be blank"}, {"t5", nil, "cannot be blank"}, {"t6", time1, "cannot be blank"}, } for _, test := range tests { r := Required err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func TestRequiredRule_When(t *testing.T) { r := Required.When(false) err := Validate(nil, r) assert.Nil(t, err) r = Required.When(true) err = Validate(nil, r) assert.Equal(t, ErrRequired, err) } func TestNilOrNotEmpty(t *testing.T) { s1 := "123" s2 := "" tests := []struct { tag string value interface{} err string }{ {"t1", 123, ""}, {"t2", "", "cannot be blank"}, {"t3", &s1, ""}, {"t4", &s2, "cannot be blank"}, {"t5", nil, ""}, } for _, test := range tests { r := NilOrNotEmpty err := r.Validate(test.value) assertError(t, test.err, err, test.tag) } } func Test_requiredRule_Error(t *testing.T) { r := Required assert.Equal(t, "cannot be blank", r.Validate(nil).Error()) assert.False(t, r.skipNil) r2 := r.Error("123") assert.Equal(t, "cannot be blank", r.Validate(nil).Error()) assert.False(t, r.skipNil) assert.Equal(t, "123", r2.err.Message()) assert.False(t, r2.skipNil) r = NilOrNotEmpty assert.Equal(t, "cannot be blank", r.Validate("").Error()) assert.True(t, r.skipNil) r2 = r.Error("123") assert.Equal(t, "cannot be blank", r.Validate("").Error()) assert.True(t, r.skipNil) assert.Equal(t, "123", r2.err.Message()) assert.True(t, r2.skipNil) } func TestRequiredRule_Error(t *testing.T) { r := Required err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, err.Code(), r.err.Code()) assert.Equal(t, err.Message(), r.err.Message()) assert.NotEqual(t, err, Required.err) } ozzo-validation-4.3.0/string.go000066400000000000000000000036021374327524300165170ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation type stringValidator func(string) bool // StringRule is a rule that checks a string variable using a specified stringValidator. type StringRule struct { validate stringValidator err Error } // NewStringRule creates a new validation rule using a function that takes a string value and returns a bool. // The rule returned will use the function to check if a given string or byte slice is valid or not. // An empty value is considered to be valid. Please use the Required rule to make sure a value is not empty. func NewStringRule(validator stringValidator, message string) StringRule { return StringRule{ validate: validator, err: NewError("", message), } } // NewStringRuleWithError creates a new validation rule using a function that takes a string value and returns a bool. // The rule returned will use the function to check if a given string or byte slice is valid or not. // An empty value is considered to be valid. Please use the Required rule to make sure a value is not empty. func NewStringRuleWithError(validator stringValidator, err Error) StringRule { return StringRule{ validate: validator, err: err, } } // Error sets the error message for the rule. func (r StringRule) Error(message string) StringRule { r.err = r.err.SetMessage(message) return r } // ErrorObject sets the error struct for the rule. func (r StringRule) ErrorObject(err Error) StringRule { r.err = err return r } // Validate checks if the given value is valid or not. func (r StringRule) Validate(value interface{}) error { value, isNil := Indirect(value) if isNil || IsEmpty(value) { return nil } str, err := EnsureString(value) if err != nil { return err } if r.validate(str) { return nil } return r.err } ozzo-validation-4.3.0/string_test.go000066400000000000000000000054731374327524300175660ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "database/sql" "reflect" "testing" "github.com/stretchr/testify/assert" ) func validateMe(s string) bool { return s == "me" } func TestNewStringRule(t *testing.T) { v := NewStringRule(validateMe, "abc") assert.NotNil(t, v.validate) assert.Equal(t, "", v.err.Code()) assert.Equal(t, "abc", v.err.Message()) } func TestNewStringRuleWithError(t *testing.T) { err := NewError("C", "abc") v := NewStringRuleWithError(validateMe, err) assert.NotNil(t, v.validate) assert.Equal(t, err, v.err) assert.Equal(t, "C", v.err.Code()) assert.Equal(t, "abc", v.err.Message()) } func TestStringRule_Error(t *testing.T) { err := NewError("code", "abc") v := NewStringRuleWithError(validateMe, err).Error("abc") assert.Equal(t, "code", v.err.Code()) assert.Equal(t, "abc", v.err.Message()) v2 := v.Error("correct") assert.Equal(t, "code", v.err.Code()) assert.Equal(t, "correct", v2.err.Message()) assert.Equal(t, "abc", v.err.Message()) } func TestStringValidator_Validate(t *testing.T) { v := NewStringRule(validateMe, "wrong_rule").Error("wrong") value := "me" err := v.Validate(value) assert.Nil(t, err) err = v.Validate(&value) assert.Nil(t, err) value = "" err = v.Validate(value) assert.Nil(t, err) err = v.Validate(&value) assert.Nil(t, err) nullValue := sql.NullString{String: "me", Valid: true} err = v.Validate(nullValue) assert.Nil(t, err) nullValue = sql.NullString{String: "", Valid: true} err = v.Validate(nullValue) assert.Nil(t, err) var s *string err = v.Validate(s) assert.Nil(t, err) err = v.Validate("not me") if assert.NotNil(t, err) { assert.Equal(t, "wrong", err.Error()) } err = v.Validate(100) if assert.NotNil(t, err) { assert.NotEqual(t, "wrong", err.Error()) } v2 := v.Error("Wrong!") err = v2.Validate("not me") if assert.NotNil(t, err) { assert.Equal(t, "Wrong!", err.Error()) } } func TestGetErrorFieldName(t *testing.T) { type A struct { T0 string T1 string `json:"t1"` T2 string `json:"t2,omitempty"` T3 string `json:",omitempty"` T4 string `json:"t4,x1,omitempty"` } tests := []struct { tag string field string name string }{ {"t1", "T0", "T0"}, {"t2", "T1", "t1"}, {"t3", "T2", "t2"}, {"t4", "T3", "T3"}, {"t5", "T4", "t4"}, } a := reflect.TypeOf(A{}) for _, test := range tests { field, _ := a.FieldByName(test.field) assert.Equal(t, test.name, getErrorFieldName(&field), test.tag) } } func TestStringRule_ErrorObject(t *testing.T) { r := NewStringRule(validateMe, "wrong_rule") err := NewError("code", "abc") r = r.ErrorObject(err) assert.Equal(t, err, r.err) assert.Equal(t, "code", r.err.Code()) assert.Equal(t, "abc", r.err.Message()) } ozzo-validation-4.3.0/struct.go000066400000000000000000000123101374327524300165310ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "context" "errors" "fmt" "reflect" "strings" ) var ( // ErrStructPointer is the error that a struct being validated is not specified as a pointer. ErrStructPointer = errors.New("only a pointer to a struct can be validated") ) type ( // ErrFieldPointer is the error that a field is not specified as a pointer. ErrFieldPointer int // ErrFieldNotFound is the error that a field cannot be found in the struct. ErrFieldNotFound int // FieldRules represents a rule set associated with a struct field. FieldRules struct { fieldPtr interface{} rules []Rule } ) // Error returns the error string of ErrFieldPointer. func (e ErrFieldPointer) Error() string { return fmt.Sprintf("field #%v must be specified as a pointer", int(e)) } // Error returns the error string of ErrFieldNotFound. func (e ErrFieldNotFound) Error() string { return fmt.Sprintf("field #%v cannot be found in the struct", int(e)) } // ValidateStruct validates a struct by checking the specified struct fields against the corresponding validation rules. // Note that the struct being validated must be specified as a pointer to it. If the pointer is nil, it is considered valid. // Use Field() to specify struct fields that need to be validated. Each Field() call specifies a single field which // should be specified as a pointer to the field. A field can be associated with multiple rules. // For example, // // value := struct { // Name string // Value string // }{"name", "demo"} // err := validation.ValidateStruct(&value, // validation.Field(&a.Name, validation.Required), // validation.Field(&a.Value, validation.Required, validation.Length(5, 10)), // ) // fmt.Println(err) // // Value: the length must be between 5 and 10. // // An error will be returned if validation fails. func ValidateStruct(structPtr interface{}, fields ...*FieldRules) error { return ValidateStructWithContext(nil, structPtr, fields...) } // ValidateStructWithContext validates a struct with the given context. // The only difference between ValidateStructWithContext and ValidateStruct is that the former will // validate struct fields with the provided context. // Please refer to ValidateStruct for the detailed instructions on how to use this function. func ValidateStructWithContext(ctx context.Context, structPtr interface{}, fields ...*FieldRules) error { value := reflect.ValueOf(structPtr) if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct { // must be a pointer to a struct return NewInternalError(ErrStructPointer) } if value.IsNil() { // treat a nil struct pointer as valid return nil } value = value.Elem() errs := Errors{} for i, fr := range fields { fv := reflect.ValueOf(fr.fieldPtr) if fv.Kind() != reflect.Ptr { return NewInternalError(ErrFieldPointer(i)) } ft := findStructField(value, fv) if ft == nil { return NewInternalError(ErrFieldNotFound(i)) } var err error if ctx == nil { err = Validate(fv.Elem().Interface(), fr.rules...) } else { err = ValidateWithContext(ctx, fv.Elem().Interface(), fr.rules...) } if err != nil { if ie, ok := err.(InternalError); ok && ie.InternalError() != nil { return err } if ft.Anonymous { // merge errors from anonymous struct field if es, ok := err.(Errors); ok { for name, value := range es { errs[name] = value } continue } } errs[getErrorFieldName(ft)] = err } } if len(errs) > 0 { return errs } return nil } // Field specifies a struct field and the corresponding validation rules. // The struct field must be specified as a pointer to it. func Field(fieldPtr interface{}, rules ...Rule) *FieldRules { return &FieldRules{ fieldPtr: fieldPtr, rules: rules, } } // findStructField looks for a field in the given struct. // The field being looked for should be a pointer to the actual struct field. // If found, the field info will be returned. Otherwise, nil will be returned. func findStructField(structValue reflect.Value, fieldValue reflect.Value) *reflect.StructField { ptr := fieldValue.Pointer() for i := structValue.NumField() - 1; i >= 0; i-- { sf := structValue.Type().Field(i) if ptr == structValue.Field(i).UnsafeAddr() { // do additional type comparison because it's possible that the address of // an embedded struct is the same as the first field of the embedded struct if sf.Type == fieldValue.Elem().Type() { return &sf } } if sf.Anonymous { // delve into anonymous struct to look for the field fi := structValue.Field(i) if sf.Type.Kind() == reflect.Ptr { fi = fi.Elem() } if fi.Kind() == reflect.Struct { if f := findStructField(fi, fieldValue); f != nil { return f } } } } return nil } // getErrorFieldName returns the name that should be used to represent the validation error of a struct field. func getErrorFieldName(f *reflect.StructField) string { if tag := f.Tag.Get(ErrorTag); tag != "" && tag != "-" { if cps := strings.SplitN(tag, ",", 2); cps[0] != "" { return cps[0] } } return f.Name } ozzo-validation-4.3.0/struct_test.go000066400000000000000000000204221374327524300175730ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "context" "reflect" "testing" "github.com/stretchr/testify/assert" ) type Struct1 struct { Field1 int Field2 *int Field3 []int Field4 [4]int field5 int Struct2 S1 *Struct2 S2 Struct2 JSONField int `json:"some_json_field"` JSONIgnoredField int `json:"-"` } type Struct2 struct { Field21 string Field22 string } type Struct3 struct { *Struct2 S1 string } func TestFindStructField(t *testing.T) { var s1 Struct1 v1 := reflect.ValueOf(&s1).Elem() assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field1))) assert.Nil(t, findStructField(v1, reflect.ValueOf(s1.Field2))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field2))) assert.Nil(t, findStructField(v1, reflect.ValueOf(s1.Field3))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field3))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field4))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.field5))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Struct2))) assert.Nil(t, findStructField(v1, reflect.ValueOf(s1.S1))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.S1))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field21))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Field22))) assert.NotNil(t, findStructField(v1, reflect.ValueOf(&s1.Struct2.Field22))) s2 := reflect.ValueOf(&s1.Struct2).Elem() assert.NotNil(t, findStructField(s2, reflect.ValueOf(&s1.Field21))) assert.NotNil(t, findStructField(s2, reflect.ValueOf(&s1.Struct2.Field21))) assert.NotNil(t, findStructField(s2, reflect.ValueOf(&s1.Struct2.Field22))) s3 := Struct3{ Struct2: &Struct2{}, } v3 := reflect.ValueOf(&s3).Elem() assert.NotNil(t, findStructField(v3, reflect.ValueOf(&s3.Struct2))) assert.NotNil(t, findStructField(v3, reflect.ValueOf(&s3.Field21))) } func TestValidateStruct(t *testing.T) { var m0 *Model1 m1 := Model1{A: "abc", B: "xyz", c: "abc", G: "xyz", H: []string{"abc", "abc"}, I: map[string]string{"foo": "abc"}} m2 := Model1{E: String123("xyz")} m3 := Model2{} m4 := Model2{M3: Model3{A: "abc"}, Model3: Model3{A: "abc"}} m5 := Model2{Model3: Model3{A: "internal"}} tests := []struct { tag string model interface{} rules []*FieldRules err string }{ // empty rules {"t1.1", &m1, []*FieldRules{}, ""}, {"t1.2", &m1, []*FieldRules{Field(&m1.A), Field(&m1.B)}, ""}, // normal rules {"t2.1", &m1, []*FieldRules{Field(&m1.A, &validateAbc{}), Field(&m1.B, &validateXyz{})}, ""}, {"t2.2", &m1, []*FieldRules{Field(&m1.A, &validateXyz{}), Field(&m1.B, &validateAbc{})}, "A: error xyz; B: error abc."}, {"t2.3", &m1, []*FieldRules{Field(&m1.A, &validateXyz{}), Field(&m1.c, &validateXyz{})}, "A: error xyz; c: error xyz."}, {"t2.4", &m1, []*FieldRules{Field(&m1.D, Length(0, 5))}, ""}, {"t2.5", &m1, []*FieldRules{Field(&m1.F, Length(0, 5))}, ""}, {"t2.6", &m1, []*FieldRules{Field(&m1.H, Each(&validateAbc{})), Field(&m1.I, Each(&validateAbc{}))}, ""}, {"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.)."}, // non-struct pointer {"t3.1", m1, []*FieldRules{}, ErrStructPointer.Error()}, {"t3.2", nil, []*FieldRules{}, ErrStructPointer.Error()}, {"t3.3", m0, []*FieldRules{}, ""}, {"t3.4", &m0, []*FieldRules{}, ErrStructPointer.Error()}, // invalid field spec {"t4.1", &m1, []*FieldRules{Field(m1)}, ErrFieldPointer(0).Error()}, {"t4.2", &m1, []*FieldRules{Field(&m1)}, ErrFieldNotFound(0).Error()}, // struct tag {"t5.1", &m1, []*FieldRules{Field(&m1.G, &validateAbc{})}, "g: error abc."}, // validatable field {"t6.1", &m2, []*FieldRules{Field(&m2.E)}, "E: error 123."}, {"t6.2", &m2, []*FieldRules{Field(&m2.E, Skip)}, ""}, {"t6.3", &m2, []*FieldRules{Field(&m2.E, Skip.When(true))}, ""}, {"t6.4", &m2, []*FieldRules{Field(&m2.E, Skip.When(false))}, "E: error 123."}, // Required, NotNil {"t7.1", &m2, []*FieldRules{Field(&m2.F, Required)}, "F: cannot be blank."}, {"t7.2", &m2, []*FieldRules{Field(&m2.F, NotNil)}, "F: is required."}, {"t7.3", &m2, []*FieldRules{Field(&m2.F, Skip, Required)}, ""}, {"t7.4", &m2, []*FieldRules{Field(&m2.F, Skip, NotNil)}, ""}, {"t7.5", &m2, []*FieldRules{Field(&m2.F, Skip.When(true), Required)}, ""}, {"t7.6", &m2, []*FieldRules{Field(&m2.F, Skip.When(true), NotNil)}, ""}, {"t7.7", &m2, []*FieldRules{Field(&m2.F, Skip.When(false), Required)}, "F: cannot be blank."}, {"t7.8", &m2, []*FieldRules{Field(&m2.F, Skip.When(false), NotNil)}, "F: is required."}, // embedded structs {"t8.1", &m3, []*FieldRules{Field(&m3.M3, Skip)}, ""}, {"t8.2", &m3, []*FieldRules{Field(&m3.M3)}, "M3: (A: error abc.)."}, {"t8.3", &m3, []*FieldRules{Field(&m3.Model3, Skip)}, ""}, {"t8.4", &m3, []*FieldRules{Field(&m3.Model3)}, "A: error abc."}, {"t8.5", &m4, []*FieldRules{Field(&m4.M3)}, ""}, {"t8.6", &m4, []*FieldRules{Field(&m4.Model3)}, ""}, {"t8.7", &m3, []*FieldRules{Field(&m3.A, Required), Field(&m3.B, Required)}, "A: cannot be blank; B: cannot be blank."}, {"t8.8", &m3, []*FieldRules{Field(&m4.A, Required)}, "field #0 cannot be found in the struct"}, // internal error {"t9.1", &m5, []*FieldRules{Field(&m5.A, &validateAbc{}), Field(&m5.B, Required), Field(&m5.A, &validateInternalError{})}, "error internal"}, } for _, test := range tests { err1 := ValidateStruct(test.model, test.rules...) err2 := ValidateStructWithContext(context.Background(), test.model, test.rules...) assertError(t, test.err, err1, test.tag) assertError(t, test.err, err2, test.tag) } // embedded struct err := Validate(&m3) assert.EqualError(t, err, "A: error abc.") a := struct { Name string Value string }{"name", "demo"} err = ValidateStruct(&a, Field(&a.Name, Required), Field(&a.Value, Required, Length(5, 10)), ) assert.EqualError(t, err, "Value: the length must be between 5 and 10.") } func TestValidateStructWithContext(t *testing.T) { m1 := Model1{A: "abc", B: "xyz", c: "abc", G: "xyz"} m2 := Model2{Model3: Model3{A: "internal"}} m3 := Model5{} tests := []struct { tag string model interface{} rules []*FieldRules err string }{ // normal rules {"t1.1", &m1, []*FieldRules{Field(&m1.A, &validateContextAbc{}), Field(&m1.B, &validateContextXyz{})}, ""}, {"t1.2", &m1, []*FieldRules{Field(&m1.A, &validateContextXyz{}), Field(&m1.B, &validateContextAbc{})}, "A: error xyz; B: error abc."}, {"t1.3", &m1, []*FieldRules{Field(&m1.A, &validateContextXyz{}), Field(&m1.c, &validateContextXyz{})}, "A: error xyz; c: error xyz."}, {"t1.4", &m1, []*FieldRules{Field(&m1.G, &validateContextAbc{})}, "g: error abc."}, // skip rule {"t2.1", &m1, []*FieldRules{Field(&m1.G, Skip, &validateContextAbc{})}, ""}, {"t2.2", &m1, []*FieldRules{Field(&m1.G, &validateContextAbc{}, Skip)}, "g: error abc."}, // internal error {"t3.1", &m2, []*FieldRules{Field(&m2.A, &validateContextAbc{}), Field(&m2.B, Required), Field(&m2.A, &validateInternalError{})}, "error internal"}, } for _, test := range tests { err := ValidateStructWithContext(context.Background(), test.model, test.rules...) assertError(t, test.err, err, test.tag) } //embedded struct err := ValidateWithContext(context.Background(), &m3) if assert.NotNil(t, err) { assert.Equal(t, "A: error abc.", err.Error()) } a := struct { Name string Value string }{"name", "demo"} err = ValidateStructWithContext(context.Background(), &a, Field(&a.Name, Required), Field(&a.Value, Required, Length(5, 10)), ) if assert.NotNil(t, err) { assert.Equal(t, "Value: the length must be between 5 and 10.", err.Error()) } } func Test_getErrorFieldName(t *testing.T) { var s1 Struct1 v1 := reflect.ValueOf(&s1).Elem() sf1 := findStructField(v1, reflect.ValueOf(&s1.Field1)) assert.NotNil(t, sf1) assert.Equal(t, "Field1", getErrorFieldName(sf1)) jsonField := findStructField(v1, reflect.ValueOf(&s1.JSONField)) assert.NotNil(t, jsonField) assert.Equal(t, "some_json_field", getErrorFieldName(jsonField)) jsonIgnoredField := findStructField(v1, reflect.ValueOf(&s1.JSONIgnoredField)) assert.NotNil(t, jsonIgnoredField) assert.Equal(t, "JSONIgnoredField", getErrorFieldName(jsonIgnoredField)) } ozzo-validation-4.3.0/util.go000066400000000000000000000113451374327524300161710ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "database/sql/driver" "errors" "fmt" "reflect" "time" ) var ( bytesType = reflect.TypeOf([]byte(nil)) valuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem() ) // EnsureString ensures the given value is a string. // If the value is a byte slice, it will be typecast into a string. // An error is returned otherwise. func EnsureString(value interface{}) (string, error) { v := reflect.ValueOf(value) if v.Kind() == reflect.String { return v.String(), nil } if v.Type() == bytesType { return string(v.Interface().([]byte)), nil } return "", errors.New("must be either a string or byte slice") } // StringOrBytes typecasts a value into a string or byte slice. // Boolean flags are returned to indicate if the typecasting succeeds or not. func StringOrBytes(value interface{}) (isString bool, str string, isBytes bool, bs []byte) { v := reflect.ValueOf(value) if v.Kind() == reflect.String { str = v.String() isString = true } else if v.Kind() == reflect.Slice && v.Type() == bytesType { bs = v.Interface().([]byte) isBytes = true } return } // LengthOfValue returns the length of a value that is a string, slice, map, or array. // An error is returned for all other types. func LengthOfValue(value interface{}) (int, error) { v := reflect.ValueOf(value) switch v.Kind() { case reflect.String, reflect.Slice, reflect.Map, reflect.Array: return v.Len(), nil } return 0, fmt.Errorf("cannot get the length of %v", v.Kind()) } // ToInt converts the given value to an int64. // An error is returned for all incompatible types. func ToInt(value interface{}) (int64, error) { v := reflect.ValueOf(value) switch v.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int(), nil } return 0, fmt.Errorf("cannot convert %v to int64", v.Kind()) } // ToUint converts the given value to an uint64. // An error is returned for all incompatible types. func ToUint(value interface{}) (uint64, error) { v := reflect.ValueOf(value) switch v.Kind() { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint(), nil } return 0, fmt.Errorf("cannot convert %v to uint64", v.Kind()) } // ToFloat converts the given value to a float64. // An error is returned for all incompatible types. func ToFloat(value interface{}) (float64, error) { v := reflect.ValueOf(value) switch v.Kind() { case reflect.Float32, reflect.Float64: return v.Float(), nil } return 0, fmt.Errorf("cannot convert %v to float64", v.Kind()) } // IsEmpty checks if a value is empty or not. // A value is considered empty if // - integer, float: zero // - bool: false // - string, array: len() == 0 // - slice, map: nil or len() == 0 // - interface, pointer: nil or the referenced value is empty func IsEmpty(value interface{}) bool { v := reflect.ValueOf(value) switch v.Kind() { case reflect.String, reflect.Array, reflect.Map, reflect.Slice: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Invalid: return true case reflect.Interface, reflect.Ptr: if v.IsNil() { return true } return IsEmpty(v.Elem().Interface()) case reflect.Struct: v, ok := value.(time.Time) if ok && v.IsZero() { return true } } return false } // Indirect returns the value that the given interface or pointer references to. // If the value implements driver.Valuer, it will deal with the value returned by // the Value() method instead. A boolean value is also returned to indicate if // the value is nil or not (only applicable to interface, pointer, map, and slice). // If the value is neither an interface nor a pointer, it will be returned back. func Indirect(value interface{}) (interface{}, bool) { rv := reflect.ValueOf(value) kind := rv.Kind() switch kind { case reflect.Invalid: return nil, true case reflect.Ptr, reflect.Interface: if rv.IsNil() { return nil, true } return Indirect(rv.Elem().Interface()) case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: if rv.IsNil() { return nil, true } } if rv.Type().Implements(valuerType) { return indirectValuer(value.(driver.Valuer)) } return value, false } func indirectValuer(valuer driver.Valuer) (interface{}, bool) { if value, err := valuer.Value(); value != nil && err == nil { return Indirect(value) } return nil, true } ozzo-validation-4.3.0/util_test.go000066400000000000000000000164501374327524300172320ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "database/sql" "testing" "time" "github.com/stretchr/testify/assert" ) func TestEnsureString(t *testing.T) { str := "abc" bytes := []byte("abc") tests := []struct { tag string value interface{} expected string hasError bool }{ {"t1", "abc", "abc", false}, {"t2", &str, "", true}, {"t3", bytes, "abc", false}, {"t4", &bytes, "", true}, {"t5", 100, "", true}, } for _, test := range tests { s, err := EnsureString(test.value) if test.hasError { assert.NotNil(t, err, test.tag) } else { assert.Nil(t, err, test.tag) assert.Equal(t, test.expected, s, test.tag) } } } type MyString string func TestStringOrBytes(t *testing.T) { str := "abc" bytes := []byte("abc") var str2 string var bytes2 []byte var str3 MyString = "abc" var str4 *string tests := []struct { tag string value interface{} str string bs []byte isString bool isBytes bool }{ {"t1", str, "abc", nil, true, false}, {"t2", &str, "", nil, false, false}, {"t3", bytes, "", []byte("abc"), false, true}, {"t4", &bytes, "", nil, false, false}, {"t5", 100, "", nil, false, false}, {"t6", str2, "", nil, true, false}, {"t7", &str2, "", nil, false, false}, {"t8", bytes2, "", nil, false, true}, {"t9", &bytes2, "", nil, false, false}, {"t10", str3, "abc", nil, true, false}, {"t11", &str3, "", nil, false, false}, {"t12", str4, "", nil, false, false}, } for _, test := range tests { isString, str, isBytes, bs := StringOrBytes(test.value) assert.Equal(t, test.str, str, test.tag) assert.Equal(t, test.bs, bs, test.tag) assert.Equal(t, test.isString, isString, test.tag) assert.Equal(t, test.isBytes, isBytes, test.tag) } } func TestLengthOfValue(t *testing.T) { var a [3]int tests := []struct { tag string value interface{} length int err string }{ {"t1", "abc", 3, ""}, {"t2", []int{1, 2}, 2, ""}, {"t3", map[string]int{"A": 1, "B": 2}, 2, ""}, {"t4", a, 3, ""}, {"t5", &a, 0, "cannot get the length of ptr"}, {"t6", 123, 0, "cannot get the length of int"}, } for _, test := range tests { l, err := LengthOfValue(test.value) assert.Equal(t, test.length, l, test.tag) assertError(t, test.err, err, test.tag) } } func TestToInt(t *testing.T) { var a int tests := []struct { tag string value interface{} result int64 err string }{ {"t1", 1, 1, ""}, {"t2", int8(1), 1, ""}, {"t3", int16(1), 1, ""}, {"t4", int32(1), 1, ""}, {"t5", int64(1), 1, ""}, {"t6", &a, 0, "cannot convert ptr to int64"}, {"t7", uint(1), 0, "cannot convert uint to int64"}, {"t8", float64(1), 0, "cannot convert float64 to int64"}, {"t9", "abc", 0, "cannot convert string to int64"}, {"t10", []int{1, 2}, 0, "cannot convert slice to int64"}, {"t11", map[string]int{"A": 1}, 0, "cannot convert map to int64"}, } for _, test := range tests { l, err := ToInt(test.value) assert.Equal(t, test.result, l, test.tag) assertError(t, test.err, err, test.tag) } } func TestToUint(t *testing.T) { var a int var b uint tests := []struct { tag string value interface{} result uint64 err string }{ {"t1", uint(1), 1, ""}, {"t2", uint8(1), 1, ""}, {"t3", uint16(1), 1, ""}, {"t4", uint32(1), 1, ""}, {"t5", uint64(1), 1, ""}, {"t6", 1, 0, "cannot convert int to uint64"}, {"t7", &a, 0, "cannot convert ptr to uint64"}, {"t8", &b, 0, "cannot convert ptr to uint64"}, {"t9", float64(1), 0, "cannot convert float64 to uint64"}, {"t10", "abc", 0, "cannot convert string to uint64"}, {"t11", []int{1, 2}, 0, "cannot convert slice to uint64"}, {"t12", map[string]int{"A": 1}, 0, "cannot convert map to uint64"}, } for _, test := range tests { l, err := ToUint(test.value) assert.Equal(t, test.result, l, test.tag) assertError(t, test.err, err, test.tag) } } func TestToFloat(t *testing.T) { var a int var b uint tests := []struct { tag string value interface{} result float64 err string }{ {"t1", float32(1), 1, ""}, {"t2", float64(1), 1, ""}, {"t3", 1, 0, "cannot convert int to float64"}, {"t4", uint(1), 0, "cannot convert uint to float64"}, {"t5", &a, 0, "cannot convert ptr to float64"}, {"t6", &b, 0, "cannot convert ptr to float64"}, {"t7", "abc", 0, "cannot convert string to float64"}, {"t8", []int{1, 2}, 0, "cannot convert slice to float64"}, {"t9", map[string]int{"A": 1}, 0, "cannot convert map to float64"}, } for _, test := range tests { l, err := ToFloat(test.value) assert.Equal(t, test.result, l, test.tag) assertError(t, test.err, err, test.tag) } } func TestIsEmpty(t *testing.T) { var s1 string var s2 = "a" var s3 *string s4 := struct{}{} time1 := time.Now() var time2 time.Time tests := []struct { tag string value interface{} empty bool }{ // nil {"t0", nil, true}, // string {"t1.1", "", true}, {"t1.2", "1", false}, {"t1.3", MyString(""), true}, {"t1.4", MyString("1"), false}, // slice {"t2.1", []byte(""), true}, {"t2.2", []byte("1"), false}, // map {"t3.1", map[string]int{}, true}, {"t3.2", map[string]int{"a": 1}, false}, // bool {"t4.1", false, true}, {"t4.2", true, false}, // int {"t5.1", 0, true}, {"t5.2", int8(0), true}, {"t5.3", int16(0), true}, {"t5.4", int32(0), true}, {"t5.5", int64(0), true}, {"t5.6", 1, false}, {"t5.7", int8(1), false}, {"t5.8", int16(1), false}, {"t5.9", int32(1), false}, {"t5.10", int64(1), false}, // uint {"t6.1", uint(0), true}, {"t6.2", uint8(0), true}, {"t6.3", uint16(0), true}, {"t6.4", uint32(0), true}, {"t6.5", uint64(0), true}, {"t6.6", uint(1), false}, {"t6.7", uint8(1), false}, {"t6.8", uint16(1), false}, {"t6.9", uint32(1), false}, {"t6.10", uint64(1), false}, // float {"t7.1", float32(0), true}, {"t7.2", float64(0), true}, {"t7.3", float32(1), false}, {"t7.4", float64(1), false}, // interface, ptr {"t8.1", &s1, true}, {"t8.2", &s2, false}, {"t8.3", s3, true}, // struct {"t9.1", s4, false}, {"t9.2", &s4, false}, // time.Time {"t10.1", time1, false}, {"t10.2", &time1, false}, {"t10.3", time2, true}, {"t10.4", &time2, true}, } for _, test := range tests { empty := IsEmpty(test.value) assert.Equal(t, test.empty, empty, test.tag) } } func TestIndirect(t *testing.T) { var a = 100 var b *int var c *sql.NullInt64 tests := []struct { tag string value interface{} result interface{} isNil bool }{ {"t1", 100, 100, false}, {"t2", &a, 100, false}, {"t3", b, nil, true}, {"t4", nil, nil, true}, {"t5", sql.NullInt64{Int64: 0, Valid: false}, nil, true}, {"t6", sql.NullInt64{Int64: 1, Valid: false}, nil, true}, {"t7", &sql.NullInt64{Int64: 0, Valid: false}, nil, true}, {"t8", &sql.NullInt64{Int64: 1, Valid: false}, nil, true}, {"t9", sql.NullInt64{Int64: 0, Valid: true}, int64(0), false}, {"t10", sql.NullInt64{Int64: 1, Valid: true}, int64(1), false}, {"t11", &sql.NullInt64{Int64: 0, Valid: true}, int64(0), false}, {"t12", &sql.NullInt64{Int64: 1, Valid: true}, int64(1), false}, {"t13", c, nil, true}, } for _, test := range tests { result, isNil := Indirect(test.value) assert.Equal(t, test.result, result, test.tag) assert.Equal(t, test.isNil, isNil, test.tag) } } ozzo-validation-4.3.0/validation.go000066400000000000000000000200431374327524300173410ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. // Package validation provides configurable and extensible rules for validating data of various types. package validation import ( "context" "fmt" "reflect" "strconv" ) type ( // Validatable is the interface indicating the type implementing it supports data validation. Validatable interface { // Validate validates the data and returns an error if validation fails. Validate() error } // ValidatableWithContext is the interface indicating the type implementing it supports context-aware data validation. ValidatableWithContext interface { // ValidateWithContext validates the data with the given context and returns an error if validation fails. ValidateWithContext(ctx context.Context) error } // Rule represents a validation rule. Rule interface { // Validate validates a value and returns a value if validation fails. Validate(value interface{}) error } // RuleWithContext represents a context-aware validation rule. RuleWithContext interface { // ValidateWithContext validates a value and returns a value if validation fails. ValidateWithContext(ctx context.Context, value interface{}) error } // RuleFunc represents a validator function. // You may wrap it as a Rule by calling By(). RuleFunc func(value interface{}) error // RuleWithContextFunc represents a validator function that is context-aware. // You may wrap it as a Rule by calling WithContext(). RuleWithContextFunc func(ctx context.Context, value interface{}) error ) var ( // ErrorTag is the struct tag name used to customize the error field name for a struct field. ErrorTag = "json" // Skip is a special validation rule that indicates all rules following it should be skipped. Skip = skipRule{skip: true} validatableType = reflect.TypeOf((*Validatable)(nil)).Elem() validatableWithContextType = reflect.TypeOf((*ValidatableWithContext)(nil)).Elem() ) // Validate validates the given value and returns the validation error, if any. // // Validate performs validation using the following steps: // 1. For each rule, call its `Validate()` to validate the value. Return if any error is found. // 2. If the value being validated implements `Validatable`, call the value's `Validate()`. // Return with the validation result. // 3. If the value being validated is a map/slice/array, and the element type implements `Validatable`, // for each element call the element value's `Validate()`. Return with the validation result. func Validate(value interface{}, rules ...Rule) error { for _, rule := range rules { if s, ok := rule.(skipRule); ok && s.skip { return nil } if err := rule.Validate(value); err != nil { return err } } rv := reflect.ValueOf(value) if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() { return nil } if v, ok := value.(Validatable); ok { return v.Validate() } switch rv.Kind() { case reflect.Map: if rv.Type().Elem().Implements(validatableType) { return validateMap(rv) } case reflect.Slice, reflect.Array: if rv.Type().Elem().Implements(validatableType) { return validateSlice(rv) } case reflect.Ptr, reflect.Interface: return Validate(rv.Elem().Interface()) } return nil } // ValidateWithContext validates the given value with the given context and returns the validation error, if any. // // ValidateWithContext performs validation using the following steps: // 1. For each rule, call its `ValidateWithContext()` to validate the value if the rule implements `RuleWithContext`. // Otherwise call `Validate()` of the rule. Return if any error is found. // 2. If the value being validated implements `ValidatableWithContext`, call the value's `ValidateWithContext()` // and return with the validation result. // 3. If the value being validated implements `Validatable`, call the value's `Validate()` // and return with the validation result. // 4. If the value being validated is a map/slice/array, and the element type implements `ValidatableWithContext`, // for each element call the element value's `ValidateWithContext()`. Return with the validation result. // 5. If the value being validated is a map/slice/array, and the element type implements `Validatable`, // for each element call the element value's `Validate()`. Return with the validation result. func ValidateWithContext(ctx context.Context, value interface{}, rules ...Rule) error { for _, rule := range rules { if s, ok := rule.(skipRule); ok && s.skip { return nil } if rc, ok := rule.(RuleWithContext); ok { if err := rc.ValidateWithContext(ctx, value); err != nil { return err } } else if err := rule.Validate(value); err != nil { return err } } rv := reflect.ValueOf(value) if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() { return nil } if v, ok := value.(ValidatableWithContext); ok { return v.ValidateWithContext(ctx) } if v, ok := value.(Validatable); ok { return v.Validate() } switch rv.Kind() { case reflect.Map: if rv.Type().Elem().Implements(validatableWithContextType) { return validateMapWithContext(ctx, rv) } if rv.Type().Elem().Implements(validatableType) { return validateMap(rv) } case reflect.Slice, reflect.Array: if rv.Type().Elem().Implements(validatableWithContextType) { return validateSliceWithContext(ctx, rv) } if rv.Type().Elem().Implements(validatableType) { return validateSlice(rv) } case reflect.Ptr, reflect.Interface: return ValidateWithContext(ctx, rv.Elem().Interface()) } return nil } // validateMap validates a map of validatable elements func validateMap(rv reflect.Value) error { errs := Errors{} for _, key := range rv.MapKeys() { if mv := rv.MapIndex(key).Interface(); mv != nil { if err := mv.(Validatable).Validate(); err != nil { errs[fmt.Sprintf("%v", key.Interface())] = err } } } if len(errs) > 0 { return errs } return nil } // validateMapWithContext validates a map of validatable elements with the given context. func validateMapWithContext(ctx context.Context, rv reflect.Value) error { errs := Errors{} for _, key := range rv.MapKeys() { if mv := rv.MapIndex(key).Interface(); mv != nil { if err := mv.(ValidatableWithContext).ValidateWithContext(ctx); err != nil { errs[fmt.Sprintf("%v", key.Interface())] = err } } } if len(errs) > 0 { return errs } return nil } // validateSlice validates a slice/array of validatable elements func validateSlice(rv reflect.Value) error { errs := Errors{} l := rv.Len() for i := 0; i < l; i++ { if ev := rv.Index(i).Interface(); ev != nil { if err := ev.(Validatable).Validate(); err != nil { errs[strconv.Itoa(i)] = err } } } if len(errs) > 0 { return errs } return nil } // validateSliceWithContext validates a slice/array of validatable elements with the given context. func validateSliceWithContext(ctx context.Context, rv reflect.Value) error { errs := Errors{} l := rv.Len() for i := 0; i < l; i++ { if ev := rv.Index(i).Interface(); ev != nil { if err := ev.(ValidatableWithContext).ValidateWithContext(ctx); err != nil { errs[strconv.Itoa(i)] = err } } } if len(errs) > 0 { return errs } return nil } type skipRule struct { skip bool } func (r skipRule) Validate(interface{}) error { return nil } // When determines if all rules following it should be skipped. func (r skipRule) When(condition bool) skipRule { r.skip = condition return r } type inlineRule struct { f RuleFunc fc RuleWithContextFunc } func (r *inlineRule) Validate(value interface{}) error { if r.f == nil { return r.fc(context.Background(), value) } return r.f(value) } func (r *inlineRule) ValidateWithContext(ctx context.Context, value interface{}) error { if r.fc == nil { return r.f(value) } return r.fc(ctx, value) } // By wraps a RuleFunc into a Rule. func By(f RuleFunc) Rule { return &inlineRule{f: f} } // WithContext wraps a RuleWithContextFunc into a context-aware Rule. func WithContext(f RuleWithContextFunc) Rule { return &inlineRule{fc: f} } ozzo-validation-4.3.0/validation_test.go000066400000000000000000000156301374327524300204060ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "context" "errors" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestValidate(t *testing.T) { slice := []String123{String123("abc"), String123("123"), String123("xyz")} ctxSlice := []Model4{{A: "abc"}, {A: "def"}} mp := map[string]String123{"c": String123("abc"), "b": String123("123"), "a": String123("xyz")} mpCtx := map[string]StringValidateContext{"c": StringValidateContext("abc"), "b": StringValidateContext("123"), "a": StringValidateContext("xyz")} var ( ptr *string noCtx StringValidate = "abc" withCtx StringValidateContext = "xyz" ) tests := []struct { tag string value interface{} err string errWithContext string }{ {"t1", 123, "", ""}, {"t2", String123("123"), "", ""}, {"t3", String123("abc"), "error 123", "error 123"}, {"t4", []String123{}, "", ""}, {"t4.1", []StringValidateContext{}, "", ""}, {"t4.2", map[string]StringValidateContext{}, "", ""}, {"t5", slice, "0: error 123; 2: error 123.", "0: error 123; 2: error 123."}, {"t6", &slice, "0: error 123; 2: error 123.", "0: error 123; 2: error 123."}, {"t7", ctxSlice, "", "1: (A: error abc.)."}, {"t8", mp, "a: error 123; c: error 123.", "a: error 123; c: error 123."}, {"t8.1", mpCtx, "a: must be abc; b: must be abc.", "a: must be abc with context; b: must be abc with context."}, {"t9", &mp, "a: error 123; c: error 123.", "a: error 123; c: error 123."}, {"t10", map[string]String123{}, "", ""}, {"t11", ptr, "", ""}, {"t12", noCtx, "called validate", "called validate"}, {"t13", withCtx, "must be abc", "must be abc with context"}, } for _, test := range tests { err := Validate(test.value) assertError(t, test.err, err, test.tag) // rules that are not context-aware should still be applied in context-aware validation err = ValidateWithContext(context.Background(), test.value) assertError(t, test.errWithContext, err, test.tag) } // with rules err := Validate("123", &validateAbc{}, &validateXyz{}) assert.EqualError(t, err, "error abc") err = Validate("abc", &validateAbc{}, &validateXyz{}) assert.EqualError(t, err, "error xyz") err = Validate("abcxyz", &validateAbc{}, &validateXyz{}) assert.NoError(t, err) err = Validate("123", &validateAbc{}, Skip, &validateXyz{}) assert.EqualError(t, err, "error abc") err = Validate("abc", &validateAbc{}, Skip, &validateXyz{}) assert.NoError(t, err) err = Validate("123", &validateAbc{}, Skip.When(true), &validateXyz{}) assert.EqualError(t, err, "error abc") err = Validate("abc", &validateAbc{}, Skip.When(true), &validateXyz{}) assert.NoError(t, err) err = Validate("123", &validateAbc{}, Skip.When(false), &validateXyz{}) assert.EqualError(t, err, "error abc") err = Validate("abc", &validateAbc{}, Skip.When(false), &validateXyz{}) assert.EqualError(t, err, "error xyz") } func stringEqual(str string) RuleFunc { return func(value interface{}) error { s, _ := value.(string) if s != str { return errors.New("unexpected string") } return nil } } func TestBy(t *testing.T) { abcRule := By(func(value interface{}) error { s, _ := value.(string) if s != "abc" { return errors.New("must be abc") } return nil }) assert.Nil(t, Validate("abc", abcRule)) err := Validate("xyz", abcRule) if assert.NotNil(t, err) { assert.Equal(t, "must be abc", err.Error()) } xyzRule := By(stringEqual("xyz")) assert.Nil(t, Validate("xyz", xyzRule)) assert.NotNil(t, Validate("abc", xyzRule)) assert.Nil(t, ValidateWithContext(context.Background(), "xyz", xyzRule)) assert.NotNil(t, ValidateWithContext(context.Background(), "abc", xyzRule)) } type key int func TestByWithContext(t *testing.T) { k := key(1) abcRule := WithContext(func(ctx context.Context, value interface{}) error { if ctx.Value(k) != value.(string) { return errors.New("must be abc") } return nil }) ctx := context.WithValue(context.Background(), k, "abc") assert.Nil(t, ValidateWithContext(ctx, "abc", abcRule)) err := ValidateWithContext(ctx, "xyz", abcRule) if assert.NotNil(t, err) { assert.Equal(t, "must be abc", err.Error()) } assert.NotNil(t, Validate("abc", abcRule)) } func Test_skipRule_Validate(t *testing.T) { assert.Nil(t, Skip.Validate(100)) } func assertError(t *testing.T, expected string, err error, tag string) { if expected == "" { assert.NoError(t, err, tag) } else { assert.EqualError(t, err, expected, tag) } } type validateAbc struct{} func (v *validateAbc) Validate(obj interface{}) error { if !strings.Contains(obj.(string), "abc") { return errors.New("error abc") } return nil } type validateContextAbc struct{} func (v *validateContextAbc) Validate(obj interface{}) error { return v.ValidateWithContext(context.Background(), obj) } func (v *validateContextAbc) ValidateWithContext(_ context.Context, obj interface{}) error { if !strings.Contains(obj.(string), "abc") { return errors.New("error abc") } return nil } type validateXyz struct{} func (v *validateXyz) Validate(obj interface{}) error { if !strings.Contains(obj.(string), "xyz") { return errors.New("error xyz") } return nil } type validateContextXyz struct{} func (v *validateContextXyz) Validate(obj interface{}) error { return v.ValidateWithContext(context.Background(), obj) } func (v *validateContextXyz) ValidateWithContext(_ context.Context, obj interface{}) error { if !strings.Contains(obj.(string), "xyz") { return errors.New("error xyz") } return nil } type validateInternalError struct{} func (v *validateInternalError) Validate(obj interface{}) error { if strings.Contains(obj.(string), "internal") { return NewInternalError(errors.New("error internal")) } return nil } type Model1 struct { A string B string c string D *string E String123 F *String123 G string `json:"g"` H []string I map[string]string } type String123 string func (s String123) Validate() error { if !strings.Contains(string(s), "123") { return errors.New("error 123") } return nil } type Model2 struct { Model3 M3 Model3 B string } type Model3 struct { A string } func (m Model3) Validate() error { return ValidateStruct(&m, Field(&m.A, &validateAbc{}), ) } type Model4 struct { A string } func (m Model4) ValidateWithContext(ctx context.Context) error { return ValidateStructWithContext(ctx, &m, Field(&m.A, &validateContextAbc{}), ) } type Model5 struct { Model4 M4 Model4 B string } type StringValidate string func (s StringValidate) Validate() error { return errors.New("called validate") } type StringValidateContext string func (s StringValidateContext) Validate() error { if string(s) != "abc" { return errors.New("must be abc") } return nil } func (s StringValidateContext) ValidateWithContext(context.Context) error { if string(s) != "abc" { return errors.New("must be abc with context") } return nil } ozzo-validation-4.3.0/when.go000066400000000000000000000024551374327524300161570ustar00rootroot00000000000000package validation import "context" // When returns a validation rule that executes the given list of rules when the condition is true. func When(condition bool, rules ...Rule) WhenRule { return WhenRule{ condition: condition, rules: rules, elseRules: []Rule{}, } } // WhenRule is a validation rule that executes the given list of rules when the condition is true. type WhenRule struct { condition bool rules []Rule elseRules []Rule } // Validate checks if the condition is true and if so, it validates the value using the specified rules. func (r WhenRule) Validate(value interface{}) error { return r.ValidateWithContext(nil, value) } // ValidateWithContext checks if the condition is true and if so, it validates the value using the specified rules. func (r WhenRule) ValidateWithContext(ctx context.Context, value interface{}) error { if r.condition { if ctx == nil { return Validate(value, r.rules...) } else { return ValidateWithContext(ctx, value, r.rules...) } } if ctx == nil { return Validate(value, r.elseRules...) } else { return ValidateWithContext(ctx, value, r.elseRules...) } } // Else returns a validation rule that executes the given list of rules when the condition is false. func (r WhenRule) Else(rules ...Rule) WhenRule { r.elseRules = rules return r } ozzo-validation-4.3.0/when_test.go000066400000000000000000000057701374327524300172210ustar00rootroot00000000000000// Copyright 2016 Qiang Xue. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package validation import ( "context" "errors" "strings" "testing" ) func abcValidation(val string) bool { return val == "abc" } func TestWhen(t *testing.T) { abcRule := NewStringRule(abcValidation, "wrong_abc") validateMeRule := NewStringRule(validateMe, "wrong_me") tests := []struct { tag string condition bool value interface{} rules []Rule elseRules []Rule err string }{ // True condition {"t1.1", true, nil, []Rule{}, []Rule{}, ""}, {"t1.2", true, "", []Rule{}, []Rule{}, ""}, {"t1.3", true, "", []Rule{abcRule}, []Rule{}, ""}, {"t1.4", true, 12, []Rule{Required}, []Rule{}, ""}, {"t1.5", true, nil, []Rule{Required}, []Rule{}, "cannot be blank"}, {"t1.6", true, "123", []Rule{abcRule}, []Rule{}, "wrong_abc"}, {"t1.7", true, "abc", []Rule{abcRule}, []Rule{}, ""}, {"t1.8", true, "abc", []Rule{abcRule, abcRule}, []Rule{}, ""}, {"t1.9", true, "abc", []Rule{abcRule, validateMeRule}, []Rule{}, "wrong_me"}, {"t1.10", true, "me", []Rule{abcRule, validateMeRule}, []Rule{}, "wrong_abc"}, {"t1.11", true, "me", []Rule{}, []Rule{abcRule}, ""}, // False condition {"t2.1", false, "", []Rule{}, []Rule{}, ""}, {"t2.2", false, "", []Rule{abcRule}, []Rule{}, ""}, {"t2.3", false, "abc", []Rule{abcRule}, []Rule{}, ""}, {"t2.4", false, "abc", []Rule{abcRule, abcRule}, []Rule{}, ""}, {"t2.5", false, "abc", []Rule{abcRule, validateMeRule}, []Rule{}, ""}, {"t2.6", false, "me", []Rule{abcRule, validateMeRule}, []Rule{}, ""}, {"t2.7", false, "", []Rule{abcRule, validateMeRule}, []Rule{}, ""}, {"t2.8", false, "me", []Rule{}, []Rule{abcRule, validateMeRule}, "wrong_abc"}, } for _, test := range tests { err := Validate(test.value, When(test.condition, test.rules...).Else(test.elseRules...)) assertError(t, test.err, err, test.tag) } } func TestWhenWithContext(t *testing.T) { rule := WithContext(func(ctx context.Context, value interface{}) error { if !strings.Contains(value.(string), ctx.Value("contains").(string)) { return errors.New("unexpected value") } return nil }) ctx1 := context.WithValue(context.Background(), "contains", "abc") ctx2 := context.WithValue(context.Background(), "contains", "xyz") tests := []struct { tag string condition bool value interface{} ctx context.Context err string }{ // True condition {"t1.1", true, "abc", ctx1, ""}, {"t1.2", true, "abc", ctx2, "unexpected value"}, {"t1.3", true, "xyz", ctx1, "unexpected value"}, {"t1.4", true, "xyz", ctx2, ""}, // False condition {"t2.1", false, "abc", ctx1, ""}, {"t2.2", false, "abc", ctx2, "unexpected value"}, {"t2.3", false, "xyz", ctx1, "unexpected value"}, {"t2.4", false, "xyz", ctx2, ""}, } for _, test := range tests { err := ValidateWithContext(test.ctx, test.value, When(test.condition, rule).Else(rule)) assertError(t, test.err, err, test.tag) } }