Skip to content

Commit

Permalink
Fixes #112: customizing required error message globally does not work
Browse files Browse the repository at this point in the history
  • Loading branch information
qiangxue committed Apr 28, 2020
1 parent 31fe4f3 commit d05fa9f
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 111 deletions.
191 changes: 95 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,98 +436,11 @@ result := validation.ValidateStruct(&a,
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.')),
)


## 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.
* `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
```

### Customizing Error Messages

All built-in validation rules allow you to customize error messages. To do so, simply call the `Error()` method
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
Expand All @@ -542,7 +455,7 @@ fmt.Println(err)
```

You can also customize the pre-defined error(s) of a built-in rule such that the customization applies to *every*
instance of rule. For example, the `Required` rule uses the pre-defined error `ErrRequired`. You can customize it
instance of the rule. For example, the `Required` rule uses the pre-defined error `ErrRequired`. You can customize it
during the application initialization:
```go
import "github.com/go-ozzo/ozzo-validation/v4"
Expand All @@ -552,13 +465,12 @@ validation.ErrRequired = validation.ErrRequired.SetMessage("the value is require

### Error Code and Message Translation

The library defines an `Error` interface which includes the `Code()` method to provide the error code information.
While you often need to customize the message of a validation error, the error code is immutable once the error is
created. This allows you to programmatically check a validation error or use an error code to look for the translation
of an error message.
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 an error object
as the validation error of the rule. The error object implements the aforementioned `Error` interface.
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

Expand Down Expand Up @@ -666,6 +578,93 @@ When performing context-aware validation, if a rule does not implement `validati
`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.
* `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.
27 changes: 17 additions & 10 deletions absent.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ var (

// Nil is a validation rule that checks if a value is nil.
// It is the opposite of NotNil rule
var Nil = absentRule{err: ErrNil, condition: true, skipNil: false}
var Nil = absentRule{condition: true, skipNil: false}

// Empty checks if a not nil value is empty.
var Empty = absentRule{err: ErrEmpty, condition: true, skipNil: true}
var Empty = absentRule{condition: true, skipNil: true}

type absentRule struct {
condition bool
Expand All @@ -28,14 +28,14 @@ type absentRule struct {
func (r absentRule) Validate(value interface{}) error {
if r.condition {
value, isNil := Indirect(value)
if r.skipNil == false && isNil == false {
return r.err
}
if isNil {
return nil
}
if !IsEmpty(value) {
return r.err
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
Expand All @@ -49,6 +49,13 @@ func (r absentRule) When(condition bool) absentRule {

// 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
}
Expand Down
10 changes: 8 additions & 2 deletions not_nil.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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{err: ErrNotNilRequired}
var NotNil = notNilRule{}

type notNilRule struct {
err Error
Expand All @@ -20,13 +20,19 @@ type notNilRule struct {
func (r notNilRule) Validate(value interface{}) error {
_, isNil := Indirect(value)
if isNil {
return r.err
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
}
Expand Down
19 changes: 16 additions & 3 deletions required.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ var (
// - string, array, slice, map: len() > 0
// - interface, pointer: not nil and the referenced value is not empty
// - any other types
var Required = RequiredRule{err: ErrRequired, skipNil: false, condition: true}
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{err: ErrNilOrNotEmpty, skipNil: true, condition: true}
var NilOrNotEmpty = RequiredRule{skipNil: true, condition: true}

// RequiredRule is a rule that checks if a value is not empty.
type RequiredRule struct {
Expand All @@ -36,7 +36,13 @@ 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)) {
return r.err
if r.err != nil {
return r.err
}
if r.skipNil {
return ErrNilOrNotEmpty
}
return ErrRequired
}
}
return nil
Expand All @@ -50,6 +56,13 @@ func (r RequiredRule) When(condition bool) RequiredRule {

// 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
}
Expand Down

0 comments on commit d05fa9f

Please sign in to comment.