From 4f1796586b575fc958294ba3f47d4baf972bff27 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 7 Feb 2020 18:56:32 -0500 Subject: [PATCH] added documentation for conditional validation --- README.md | 28 ++++++++++++++++++++++++++++ required.go | 28 ++++++++++++++++------------ when.go | 7 +++---- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 531b618..412717d 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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 diff --git a/required.go b/required.go index 821953d..3db3c15 100644 --- a/required.go +++ b/required.go @@ -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) diff --git a/when.go b/when.go index 5f516d5..06fab3b 100644 --- a/when.go +++ b/when.go @@ -1,7 +1,6 @@ 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, @@ -9,13 +8,13 @@ func When(condition bool, rules ...Rule) WhenRule { } } -// 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...)