Skip to content

Commit

Permalink
added documentation for conditional validation
Browse files Browse the repository at this point in the history
  • Loading branch information
qiangxue committed Feb 7, 2020
1 parent 7bf1e8e commit 4f17965
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 16 deletions.
28 changes: 28 additions & 0 deletions README.md
Expand Up @@ -410,6 +410,33 @@ fmt.Println(err)
```


### 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 two examples:

```go
result := validation.ValidateStruct(&a,
validation.Field(&a.Unit, validation.When(a.Quantity != "", validation.Required)),
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` can take a list of validation rules. These rules will be executed only when the condition is true.

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.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:
Expand All @@ -432,6 +459,7 @@ The following rules are provided in the `validation` package:
* `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.

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
Expand Down
28 changes: 16 additions & 12 deletions required.go
Expand Up @@ -18,31 +18,35 @@ 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}
var Required = requiredRule{err: ErrRequired, 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}
var NilOrNotEmpty = requiredRule{err: ErrNilOrNotEmpty, skipNil: true, condition: true}

type requiredRule struct {
skipNil bool
err Error
}

// When validate provided value by "required" rule just when the condition is true.
func (r requiredRule) When(condition bool) WhenRule {
return When(condition, r)
condition bool
skipNil bool
err Error
}

// Validate checks if the given value is valid or not.
func (r requiredRule) Validate(value interface{}) error {
value, isNil := Indirect(value)
if r.skipNil && !isNil && IsEmpty(value) || !r.skipNil && (isNil || IsEmpty(value)) {
return r.err
if r.condition {
value, isNil := Indirect(value)
if r.skipNil && !isNil && IsEmpty(value) || !r.skipNil && (isNil || IsEmpty(value)) {
return r.err
}
}
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 {
r.err = r.err.SetMessage(message)
Expand Down
7 changes: 3 additions & 4 deletions when.go
@@ -1,21 +1,20 @@
package validation

// When returns a validation rule that checks if the specified condition
//is true, validate the value by the specified rules.
// 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,
}
}

// WhenRule is a validation rule that validate element if condition is true.
// WhenRule is a validation rule that executes the given list of rules when the condition is true.
type WhenRule struct {
condition bool
rules []Rule
}

// Validate checks if the condition is true, validate value by specified rules.
// 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 {
if r.condition {
return Validate(value, r.rules...)
Expand Down

0 comments on commit 4f17965

Please sign in to comment.