aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Sparks2018-09-13 17:33:49 +0100
committerVincent Prouillet2018-09-13 18:33:49 +0200
commitdfdc289626c448522c43c13f8d72033fe0d1cae8 (patch)
treec2fcd006466c587040be8b88076c1477d908effc
parent5edb34ac261f7025ee0bb9c25ec1c216ded5aba2 (diff)
downloadvalidator-dfdc289626c448522c43c13f8d72033fe0d1cae8.tar.bz2
Nested Validation (#60)
* Nested Validation Added support for a plain validate attribute on a struct's field to imply that validate() should be called on it with results being merged. Validation errors are now keyed with a string that indicates the path to the invalid field within a complex data structure. The errors themselves also include the path to the invalid field, expressed as a vector of strings. Added support for option and vector fields where the wrapped values perform nested validation. Refactored vector wrapping for more reusable nested validation quoting. Vector index is now more conveniently represented as an individual item in the ValidationError's path attribute. A few ergonomic changes to support custom (i.e. non-derived) Validate implementations. A custom Validator implementation may either implement the validate() method as before or implement the new validate_path(ValidationPath) method which gives context of where the validation is taking place in a complex data structure. It is not necessary to implement both methods. Refactored ValidationErrors to adopt a structure reflecting that of the data being validated. Instead of holding a vector of ValidationError instances for each field, the ValidationErrors map may now include 3 different kinds of error values representing the field, nested struct and nested vector of struct scenarios. Note that this implies a breaking change to the ValidationErrors map and the "inner" method signature for accessing errors programmatically compared to previous versions. Added new accessor methods to the ValidationErrors type for retrieving either field-level errors for a validated struct or all errors for the struct and it's nested children. The existing `inner` method provides the field-level behaviour for backwards compatibility and has been marked as deprecated. Documented the new associated functions of the ValidationErrors implementation and removed unnecessary feature declaration in a test module. Refactored tests to use the new `field_errors` accessor method. Updated README.md to describe nested validation behaviour. Keats/validator#31
-rw-r--r--README.md81
-rw-r--r--validator/src/lib.rs2
-rw-r--r--validator/src/types.rs98
-rw-r--r--validator/src/validation/mod.rs2
-rw-r--r--validator_derive/src/lib.rs17
-rw-r--r--validator_derive/src/quoting.rs70
-rw-r--r--validator_derive/tests/compile-fail/no_nested_validations.rs17
-rw-r--r--validator_derive/tests/complex.rs129
-rw-r--r--validator_derive/tests/contains.rs6
-rw-r--r--validator_derive/tests/credit_card.rs6
-rw-r--r--validator_derive/tests/custom.rs4
-rw-r--r--validator_derive/tests/email.rs6
-rw-r--r--validator_derive/tests/length.rs6
-rw-r--r--validator_derive/tests/must_match.rs6
-rw-r--r--validator_derive/tests/nested.rs370
-rw-r--r--validator_derive/tests/phone.rs6
-rw-r--r--validator_derive/tests/range.rs6
-rw-r--r--validator_derive/tests/regex.rs6
-rw-r--r--validator_derive/tests/schema.rs6
-rw-r--r--validator_derive/tests/url.rs6
20 files changed, 783 insertions, 67 deletions
diff --git a/README.md b/README.md
index 5a69c50..16ddd0e 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,22 @@ match signup_data.validate() {
};
```
-An error has the following structure:
+The `validate()` method returns a `Result<(), ValidationErrors>`. In the case of an invalid result, the
+`ValidationErrors` instance includes a map of errors keyed against the struct's field names. Errors may be represented
+in three ways, as described by the `ValidationErrorsKind` enum:
+
+```rust
+#[derive(Debug, Serialize, Clone, PartialEq)]
+#[serde(untagged)]
+pub enum ValidationErrorsKind {
+ Struct(Box<ValidationErrors>),
+ List(BTreeMap<usize, Box<ValidationErrors>>),
+ Field(Vec<ValidationError>),
+}
+```
+
+In the simple example above, any errors would be of the `Field(Vec<ValidationError>)` type, where a single
+`ValidationError` has the following structure:
```rust
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
@@ -73,6 +88,57 @@ If you are adding a validation on a `Option<..>` field, it will only be ran if t
being `must_match` that doesn't currently work with `Option` due to me not finding a use case for it. If you have one,
please comment on https://github.com/Keats/validator/issues/7.
+The other two `ValidationErrorsKind` types represent errors discovered in nested (vectors of) structs, as described in
+this example:
+
+ ```rust
+#[macro_use]
+extern crate validator_derive;
+extern crate validator;
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_json;
+
+#[derive(Debug, Validate, Deserialize)]
+struct SignupData {
+ #[validate]
+ contact_details: ContactDetails,
+ #[validate]
+ preferences: Vec<Preference>
+}
+
+#[derive(Debug, Validate, Deserialize)]
+struct ContactDetails {
+ #[validate(email)]
+ mail: String,
+ #[validate(phone)]
+ phone: String
+}
+
+#[derive(Debug, Validate, Deserialize)]
+struct Preference {
+ #[validate(length(min = "4"))]
+ name: String,
+ value: bool,
+}
+
+match signup_data.validate() {
+ Ok(_) => (),
+ Err(e) => return e;
+};
+ ```
+
+Here, the `ContactDetails` and `Preference` structs are nested within the parent `SignupData` struct. Because
+these child types also derive `Validate`, the fields where they appear can be tagged for inclusion in the parent
+struct's validation method.
+
+Any errors found in a single nested struct (the `contact_details` field in this example) would be returned as a
+`Struct(Box<ValidationErrors>)` type in the parent's `ValidationErrors` result.
+
+Any errors found in a vector of nested structs (the `preferences` field in this example) would be returned as a
+`List(BTreeMap<usize, Box<ValidationErrors>>)` type in the parent's `ValidationErrors` result, where the map is keyed on
+the index of invalid vector entries.
+
## Usage
You will need to import the `Validate` trait, and optionally use the `attr_literals` feature.
@@ -144,7 +210,7 @@ Examples:
```
### regex
-Tests whether the string matchs the regex given. `regex` takes
+Tests whether the string matches the regex given. `regex` takes
1 string argument: the path to a static Regex instance.
Examples:
@@ -154,7 +220,7 @@ Examples:
```
### credit\_card
-Test whetever the string is a valid credit card number.
+Test whether the string is a valid credit card number.
Examples:
@@ -181,6 +247,15 @@ Examples:
#[validate(custom = "::utils::validate_something")]
```
+### nested
+Performs validation on a field with a type that also implements the Validate trait (or a vector of such types).
+
+Examples:
+
+```rust
+#[validate]
+```
+
## Struct level validation
Often, some error validation can only be applied when looking at the full struct, here's how it works here:
diff --git a/validator/src/lib.rs b/validator/src/lib.rs
index 6cb0cb1..ef9685f 100644
--- a/validator/src/lib.rs
+++ b/validator/src/lib.rs
@@ -29,5 +29,5 @@ pub use validation::cards::validate_credit_card;
pub use validation::phone::validate_phone;
pub use validation::Validator;
-pub use types::{ValidationErrors, ValidationError};
+pub use types::{ValidationError, ValidationErrors, ValidationErrorsKind};
pub use traits::{Validate, HasLen, Contains};
diff --git a/validator/src/types.rs b/validator/src/types.rs
index 5c62e6d..3981989 100644
--- a/validator/src/types.rs
+++ b/validator/src/types.rs
@@ -1,6 +1,6 @@
use std::{self, fmt};
use std::borrow::Cow;
-use std::collections::HashMap;
+use std::collections::{BTreeMap, HashMap, hash_map::Entry::Vacant};
use serde_json::{Value, to_value};
use serde::ser::Serialize;
@@ -8,9 +8,9 @@ use serde::ser::Serialize;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ValidationError {
- pub code: Cow<'static, str>,
- pub message: Option<Cow<'static, str>>,
- pub params: HashMap<Cow<'static, str>, Value>,
+ pub code: Cow<'static, str>,
+ pub message: Option<Cow<'static, str>>,
+ pub params: HashMap<Cow<'static, str>, Value>,
}
impl ValidationError {
@@ -39,25 +39,109 @@ impl std::error::Error for ValidationError {
}
#[derive(Debug, Serialize, Clone, PartialEq)]
-pub struct ValidationErrors(HashMap<&'static str, Vec<ValidationError>>);
+#[serde(untagged)]
+pub enum ValidationErrorsKind {
+ Struct(Box<ValidationErrors>),
+ List(BTreeMap<usize, Box<ValidationErrors>>),
+ Field(Vec<ValidationError>),
+}
+#[derive(Debug, Serialize, Clone, PartialEq)]
+pub struct ValidationErrors(HashMap<&'static str, ValidationErrorsKind>);
impl ValidationErrors {
pub fn new() -> ValidationErrors {
ValidationErrors(HashMap::new())
}
- pub fn inner(self) -> HashMap<&'static str, Vec<ValidationError>> {
+ /// Returns a boolean indicating whether a validation result includes validation errors for a
+ /// given field. May be used as a condition for performing nested struct validations on a field
+ /// in the absence of field-level validation errors.
+ pub fn has_error(result: &Result<(), ValidationErrors>, field: &'static str) -> bool {
+ match result {
+ Ok(()) => false,
+ Err(ref errs) => errs.contains_key(field),
+ }
+ }
+
+ /// Returns the combined outcome of a struct's validation result along with the nested
+ /// validation result for one of its fields.
+ pub fn merge(parent: Result<(), ValidationErrors>, field: &'static str, child: Result<(), ValidationErrors>) -> Result<(), ValidationErrors> {
+ match child {
+ Ok(()) => parent,
+ Err(errors) => parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
+ parent_errors.add_nested(field, ValidationErrorsKind::Struct(Box::new(errors)));
+ parent_errors
+ })
+ }
+ }
+
+ /// Returns the combined outcome of a struct's validation result along with the nested
+ /// validation result for one of its fields where that field is a vector of validating structs.
+ pub fn merge_all(parent: Result<(), ValidationErrors>, field: &'static str, children: Vec<Result<(), ValidationErrors>>) -> Result<(), ValidationErrors> {
+ let errors = children.into_iter().enumerate()
+ .filter_map(|(i, res)| res.err().map(|mut err| (i, err.remove(field))))
+ .filter_map(|(i, entry)| match entry {
+ Some(ValidationErrorsKind::Struct(errors)) => Some((i, errors)),
+ _ => None,
+ })
+ .collect::<BTreeMap<_, _>>();
+
+ if errors.is_empty() {
+ parent
+ } else {
+ parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
+ parent_errors.add_nested(field, ValidationErrorsKind::List(errors));
+ parent_errors
+ })
+ }
+ }
+
+ /// Returns a map of field-level validation errors found for the struct that was validated and
+ /// any of it's nested structs that are tagged for validation.
+ pub fn errors(self) -> HashMap<&'static str, ValidationErrorsKind> {
self.0
}
+ /// Returns a map of only field-level validation errors found for the struct that was validated.
+ pub fn field_errors(self) -> HashMap<&'static str, Vec<ValidationError>> {
+ self.0.into_iter()
+ .filter_map(|(k, v)| if let ValidationErrorsKind::Field(errors) = v { Some((k, errors)) } else { None })
+ .collect()
+ }
+
+ #[deprecated(since="0.7.3", note="Use `field_errors` instead, or `errors` to also access any errors from nested structs")]
+ pub fn inner(self) -> HashMap<&'static str, Vec<ValidationError>> {
+ self.field_errors()
+ }
+
pub fn add(&mut self, field: &'static str, error: ValidationError) {
- self.0.entry(field).or_insert_with(|| vec![]).push(error);
+ if let ValidationErrorsKind::Field(ref mut vec) = self.0.entry(field).or_insert(ValidationErrorsKind::Field(vec![])) {
+ vec.push(error);
+ } else {
+ panic!("Attempt to add field validation to a non-Field ValidationErrorsKind instance");
+ }
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
+
+ fn add_nested(&mut self, field: &'static str, errors: ValidationErrorsKind) {
+ if let Vacant(entry) = self.0.entry(field) {
+ entry.insert(errors);
+ } else {
+ panic!("Attempt to replace non-empty ValidationErrors entry");
+ }
+ }
+
+ fn contains_key(&self, field: &'static str) -> bool {
+ self.0.contains_key(field)
+ }
+
+ fn remove(&mut self, field: &'static str) -> Option<ValidationErrorsKind> {
+ self.0.remove(field)
+ }
}
impl std::error::Error for ValidationErrors {
diff --git a/validator/src/validation/mod.rs b/validator/src/validation/mod.rs
index 3f1e158..34d1087 100644
--- a/validator/src/validation/mod.rs
+++ b/validator/src/validation/mod.rs
@@ -40,6 +40,7 @@ pub enum Validator {
CreditCard,
#[cfg(feature = "phone")]
Phone,
+ Nested,
}
impl Validator {
@@ -57,6 +58,7 @@ impl Validator {
Validator::CreditCard => "credit_card",
#[cfg(feature = "phone")]
Validator::Phone => "phone",
+ Validator::Nested => "nested",
}
}
}
diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs
index b5a76c1..2a23ba4 100644
--- a/validator_derive/src/lib.rs
+++ b/validator_derive/src/lib.rs
@@ -33,9 +33,7 @@ use quoting::{FieldQuoter, quote_field_validation, quote_schema_validation};
#[proc_macro_derive(Validate, attributes(validate))]
pub fn derive_validation(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
-
- let expanded = impl_validate(&ast);
- expanded.into()
+ impl_validate(&ast).into()
}
@@ -52,6 +50,7 @@ fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
};
let mut validations = vec![];
+ let mut nested_validations = vec![];
let field_types = find_fields_type(&fields);
@@ -62,7 +61,7 @@ fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
let field_quoter = FieldQuoter::new(field_ident, name, field_type);
for validation in &field_validations {
- validations.push(quote_field_validation(&field_quoter, validation));
+ quote_field_validation(&field_quoter, validation, &mut validations, &mut nested_validations);
}
}
@@ -82,11 +81,14 @@ fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
#schema_validation
- if errors.is_empty() {
+ let mut result = if errors.is_empty() {
::std::result::Result::Ok(())
} else {
::std::result::Result::Err(errors)
- }
+ };
+
+ #(#nested_validations)*
+ result
}
}
);
@@ -352,6 +354,9 @@ fn find_validators_for_field(field: &syn::Field, field_types: &HashMap<String, S
};
}
},
+ Some(syn::Meta::Word(_)) => {
+ validators.push(FieldValidation::new(Validator::Nested))
+ },
_ => unreachable!("Got something other than a list of attributes while checking field `{}`", field_ident),
}
}
diff --git a/validator_derive/src/quoting.rs b/validator_derive/src/quoting.rs
index 20ca7a7..b47db6a 100644
--- a/validator_derive/src/quoting.rs
+++ b/validator_derive/src/quoting.rs
@@ -40,13 +40,25 @@ impl FieldQuoter {
}
}
+ pub fn quote_validator_field(&self) -> proc_macro2::TokenStream {
+ let ident = &self.ident;
+
+ if self._type.starts_with("Option<") || self._type.starts_with("Vec<") {
+ quote!(#ident)
+ } else if COW_TYPE.is_match(&self._type.as_ref()) {
+ quote!(self.#ident.as_ref())
+ } else {
+ quote!(self.#ident)
+ }
+ }
+
pub fn get_optional_validator_param(&self) -> proc_macro2::TokenStream {
let ident = &self.ident;
if self._type.starts_with("Option<&") || self._type.starts_with("Option<Option<&")
|| NUMBER_TYPES.contains(&self._type.as_ref()) {
- quote!(#ident)
+ quote!(#ident)
} else {
- quote!(ref #ident)
+ quote!(ref #ident)
}
}
@@ -71,6 +83,27 @@ impl FieldQuoter {
tokens
}
+
+
+ /// Wrap the quoted output of a validation with a for loop if
+ /// the field type is a vector
+ pub fn wrap_if_vector(&self, tokens: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
+ let field_ident = &self.ident;
+ let field_name = &self.name;
+ if self._type.starts_with("Vec<") {
+ return quote!(
+ if !::validator::ValidationErrors::has_error(&result, #field_name) {
+ let results: Vec<_> = self.#field_ident.iter().map(|#field_ident| {
+ let mut result = ::std::result::Result::Ok(());
+ #tokens
+ result
+ }).collect();
+ result = ::validator::ValidationErrors::merge_all(result, #field_name, results);
+ })
+ }
+
+ tokens
+ }
}
/// Quote an actual end-user error creation automatically
@@ -326,24 +359,33 @@ pub fn quote_regex_validation(field_quoter: &FieldQuoter, validation: &FieldVali
unreachable!();
}
-pub fn quote_field_validation(field_quoter: &FieldQuoter, validation: &FieldValidation) -> proc_macro2::TokenStream {
+pub fn quote_nested_validation(field_quoter: &FieldQuoter) -> proc_macro2::TokenStream {
+ let field_name = &field_quoter.name;
+ let validator_field = field_quoter.quote_validator_field();
+ let quoted = quote!(result = ::validator::ValidationErrors::merge(result, #field_name, #validator_field.validate()););
+ field_quoter.wrap_if_option(field_quoter.wrap_if_vector(quoted))
+}
+
+pub fn quote_field_validation(field_quoter: &FieldQuoter, validation: &FieldValidation,
+ validations: &mut Vec<proc_macro2::TokenStream>,
+ nested_validations: &mut Vec<proc_macro2::TokenStream>) {
match validation.validator {
- Validator::Length {..} => quote_length_validation(&field_quoter, validation),
- Validator::Range {..} => quote_range_validation(&field_quoter, validation),
- Validator::Email => quote_email_validation(&field_quoter, validation),
- Validator::Url => quote_url_validation(&field_quoter, validation),
- Validator::MustMatch(_) => quote_must_match_validation(&field_quoter, validation),
- Validator::Custom(_) => quote_custom_validation(&field_quoter, validation),
- Validator::Contains(_) => quote_contains_validation(&field_quoter, validation),
- Validator::Regex(_) => quote_regex_validation(&field_quoter, validation),
+ Validator::Length {..} => validations.push(quote_length_validation(&field_quoter, validation)),
+ Validator::Range {..} => validations.push(quote_range_validation(&field_quoter, validation)),
+ Validator::Email => validations.push(quote_email_validation(&field_quoter, validation)),
+ Validator::Url => validations.push(quote_url_validation(&field_quoter, validation)),
+ Validator::MustMatch(_) => validations.push(quote_must_match_validation(&field_quoter, validation)),
+ Validator::Custom(_) => validations.push(quote_custom_validation(&field_quoter, validation)),
+ Validator::Contains(_) => validations.push(quote_contains_validation(&field_quoter, validation)),
+ Validator::Regex(_) => validations.push(quote_regex_validation(&field_quoter, validation)),
#[cfg(feature = "card")]
- Validator::CreditCard => quote_credit_card_validation(&field_quoter, validation),
+ Validator::CreditCard => validations.push(quote_credit_card_validation(&field_quoter, validation)),
#[cfg(feature = "phone")]
- Validator::Phone => quote_phone_validation(&field_quoter, validation),
+ Validator::Phone => validations.push(quote_phone_validation(&field_quoter, validation)),
+ Validator::Nested => nested_validations.push(quote_nested_validation(&field_quoter)),
}
}
-
pub fn quote_schema_validation(validation: Option<SchemaValidation>) -> proc_macro2::TokenStream {
if let Some(v) = validation {
let fn_ident = syn::Ident::new(&v.function, Span::call_site());
diff --git a/validator_derive/tests/compile-fail/no_nested_validations.rs b/validator_derive/tests/compile-fail/no_nested_validations.rs
new file mode 100644
index 0000000..788152d
--- /dev/null
+++ b/validator_derive/tests/compile-fail/no_nested_validations.rs
@@ -0,0 +1,17 @@
+#[macro_use] extern crate validator_derive;
+extern crate validator;
+use validator::Validate;
+
+#[derive(Validate)]
+//~^ ERROR: no method named `validate` found for type `Nested` in the current scope [E0599]
+//~^^ HELP: items from traits can only be used if the trait is implemented and in scope
+struct Test {
+ #[validate]
+ nested: Nested,
+}
+
+struct Nested {
+ value: String
+}
+
+fn main() {}
diff --git a/validator_derive/tests/complex.rs b/validator_derive/tests/complex.rs
index bbbc2e6..0df26c0 100644
--- a/validator_derive/tests/complex.rs
+++ b/validator_derive/tests/complex.rs
@@ -9,7 +9,8 @@ extern crate regex;
extern crate lazy_static;
use regex::Regex;
-use validator::{Validate, ValidationError, ValidationErrors};
+use validator::{Validate, ValidationError, ValidationErrors, ValidationErrorsKind};
+use std::collections::HashMap;
fn validate_unique_username(username: &str) -> Result<(), ValidationError> {
@@ -40,8 +41,34 @@ struct SignupData {
first_name: String,
#[validate(range(min = "18", max = "20"))]
age: u32,
+ #[validate]
+ phone: Phone,
+ #[validate]
+ card: Option<Card>,
+ #[validate]
+ preferences: Vec<Preference>
}
+#[derive(Debug, Validate, Deserialize)]
+struct Phone {
+ #[validate(phone)]
+ number: String
+}
+
+#[derive(Debug, Validate, Deserialize)]
+struct Card {
+ #[validate(credit_card)]
+ number: String,
+ #[validate(range(min = "100", max = "9999"))]
+ cvv: u32,
+}
+
+#[derive(Debug, Validate, Deserialize)]
+struct Preference {
+ #[validate(length(min = "4"))]
+ name: String,
+ value: bool,
+}
#[test]
fn is_fine_with_many_valid_validations() {
@@ -50,6 +77,19 @@ fn is_fine_with_many_valid_validations() {
site: "http://hello.com".to_string(),
first_name: "Bob".to_string(),
age: 18,
+ phone: Phone {
+ number: "+14152370800".to_string()
+ },
+ card: Some(Card {
+ number: "5236313877109142".to_string(),
+ cvv: 123
+ }),
+ preferences: vec![
+ Preference {
+ name: "marketing".to_string(),
+ value: false
+ },
+ ]
};
assert!(signup.validate().is_ok());
@@ -62,13 +102,82 @@ fn failed_validation_points_to_original_field_name() {
site: "http://hello.com".to_string(),
first_name: "".to_string(),
age: 18,
+ phone: Phone {
+ number: "123 invalid".to_string(),
+ },
+ card: Some(Card {
+ number: "1234567890123456".to_string(),
+ cvv: 1
+ }),
+ preferences: vec![
+ Preference {
+ name: "abc".to_string(),
+ value: true
+ },
+ ]
};
let res = signup.validate();
+ // println!("{}", serde_json::to_string(&res).unwrap());
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().errors();
assert!(errs.contains_key("firstName"));
- assert_eq!(errs["firstName"].len(), 1);
- assert_eq!(errs["firstName"][0].code, "length");
+ if let ValidationErrorsKind::Field(ref err) = errs["firstName"] {
+ assert_eq!(err.len(), 1);
+ assert_eq!(err[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ assert!(errs.contains_key("phone"));
+ if let ValidationErrorsKind::Struct(ref errs) = errs["phone"] {
+ unwrap_map(errs, |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("number"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["number"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "phone");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ } else {
+ panic!("Expected struct validation errors");
+ }
+ assert!(errs.contains_key("card"));
+ if let ValidationErrorsKind::Struct(ref errs) = errs["card"] {
+ unwrap_map(errs, |errs| {
+ assert_eq!(errs.len(), 2);
+ assert!(errs.contains_key("number"));
+ if let ValidationErrorsKind::Field(ref err) = errs["number"] {
+ assert_eq!(err.len(), 1);
+ assert_eq!(err[0].code, "credit_card");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ assert!(errs.contains_key("cvv"));
+ if let ValidationErrorsKind::Field(ref err) = errs["cvv"] {
+ assert_eq!(err.len(), 1);
+ assert_eq!(err[0].code, "range");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ } else {
+ panic!("Expected struct validation errors");
+ }
+ assert!(errs.contains_key("preferences"));
+ if let ValidationErrorsKind::List(ref errs) = errs["preferences"] {
+ assert!(errs.contains_key(&0));
+ unwrap_map(&errs[&0], |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("name"));
+ if let ValidationErrorsKind::Field(ref err) = errs["name"] {
+ assert_eq!(err.len(), 1);
+ assert_eq!(err[0].code, "length");
+ }
+ });
+ } else {
+ panic!("Expected list validation errors");
+ }
}
#[test]
@@ -177,6 +286,11 @@ fn test_works_with_question_mark_operator() {
site: "http://hello.com".to_string(),
first_name: "Bob".to_string(),
age: 18,
+ phone: Phone {
+ number: "+14152370800".to_string()
+ },
+ card: None,
+ preferences: Vec::new(),
};
signup.validate()?;
@@ -216,4 +330,11 @@ fn test_works_with_none_values() {
assert!(p.validate().is_ok());
assert!(q.validate().is_ok());
+}
+
+fn unwrap_map<F>(errors: &Box<ValidationErrors>, f: F)
+ where F: FnOnce(HashMap<&'static str, ValidationErrorsKind>)
+{
+ let errors = *errors.clone();
+ f(errors.errors());
} \ No newline at end of file
diff --git a/validator_derive/tests/contains.rs b/validator_derive/tests/contains.rs
index 847de0f..5e9060e 100644
--- a/validator_derive/tests/contains.rs
+++ b/validator_derive/tests/contains.rs
@@ -32,7 +32,7 @@ fn value_not_containing_needle_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "contains");
@@ -52,7 +52,7 @@ fn can_specify_code_for_contains() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -70,7 +70,7 @@ fn can_specify_message_for_contains() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/credit_card.rs b/validator_derive/tests/credit_card.rs
index a430971..b1a9da3 100644
--- a/validator_derive/tests/credit_card.rs
+++ b/validator_derive/tests/credit_card.rs
@@ -34,7 +34,7 @@ fn bad_credit_card_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "credit_card");
@@ -54,7 +54,7 @@ fn can_specify_code_for_credit_card() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -73,7 +73,7 @@ fn can_specify_message_for_credit_card() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/custom.rs b/validator_derive/tests/custom.rs
index 0cac75f..cd1b354 100644
--- a/validator_derive/tests/custom.rs
+++ b/validator_derive/tests/custom.rs
@@ -40,7 +40,7 @@ fn can_fail_custom_fn_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "meh");
@@ -59,7 +59,7 @@ fn can_specify_message_for_custom_fn() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/email.rs b/validator_derive/tests/email.rs
index fc05bf9..4fbc123 100644
--- a/validator_derive/tests/email.rs
+++ b/validator_derive/tests/email.rs
@@ -33,7 +33,7 @@ fn bad_email_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "email");
@@ -52,7 +52,7 @@ fn can_specify_code_for_email() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -70,7 +70,7 @@ fn can_specify_message_for_email() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/length.rs b/validator_derive/tests/length.rs
index 892cf74..12bffc6 100644
--- a/validator_derive/tests/length.rs
+++ b/validator_derive/tests/length.rs
@@ -32,7 +32,7 @@ fn value_out_of_length_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "length");
@@ -53,7 +53,7 @@ fn can_specify_code_for_length() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -71,7 +71,7 @@ fn can_specify_message_for_length() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/must_match.rs b/validator_derive/tests/must_match.rs
index 038616a..a8c95f9 100644
--- a/validator_derive/tests/must_match.rs
+++ b/validator_derive/tests/must_match.rs
@@ -38,7 +38,7 @@ fn not_matching_fails_validation() {
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "must_match");
@@ -60,7 +60,7 @@ fn can_specify_code_for_must_match() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -80,7 +80,7 @@ fn can_specify_message_for_must_match() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/nested.rs b/validator_derive/tests/nested.rs
new file mode 100644
index 0000000..4f03714
--- /dev/null
+++ b/validator_derive/tests/nested.rs
@@ -0,0 +1,370 @@
+#[macro_use]
+extern crate validator_derive;
+extern crate validator;
+#[macro_use]
+extern crate serde_derive;
+
+use validator::{validate_length, Validate, ValidationError, ValidationErrors, ValidationErrorsKind, Validator};
+use std::{borrow::Cow, collections::HashMap};
+
+#[derive(Debug, Validate)]
+struct Root<'a> {
+ #[validate(length(min = "1"))]
+ value: String,
+
+ #[validate]
+ a: &'a A,
+}
+
+#[derive(Debug, Validate)]
+struct A {
+ #[validate(length(min = "1"))]
+ value: String,
+
+ #[validate]
+ b: B,
+}
+
+#[derive(Debug, Validate)]
+struct B {
+ #[validate(length(min = "1"))]
+ value: String,
+}
+
+#[derive(Debug, Validate)]
+struct ParentWithOptionalChild {
+ #[validate]
+ child: Option<Child>,
+}
+
+#[derive(Debug, Validate)]
+struct ParentWithVectorOfChildren {
+ #[validate]
+ #[validate(length(min = "1"))]
+ child: Vec<Child>,
+}
+
+#[derive(Debug, Validate, Serialize)]
+struct Child {
+ #[validate(length(min = "1"))]
+ value: String,
+}
+
+#[test]
+fn is_fine_with_nested_validations() {
+ let root = Root {
+ value: "valid".to_string(),
+ a: &A {
+ value: "valid".to_string(),
+ b: B {
+ value: "valid".to_string(),
+ }
+ }
+ };
+
+ assert!(root.validate().is_ok());
+}
+
+#[test]
+fn failed_validation_points_to_original_field_names() {
+ let root = Root {
+ value: String::new(),
+ a: &A {
+ value: String::new(),
+ b: B {
+ value: String::new(),
+ }
+ }
+ };
+
+ let res = root.validate();
+ assert!(res.is_err());
+ let errs = res.unwrap_err().errors();
+ assert_eq!(errs.len(), 2);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ assert!(errs.contains_key("a"));
+ if let ValidationErrorsKind::Struct(ref errs) = errs["a"] {
+ unwrap_map(errs, |errs| {
+ assert_eq!(errs.len(), 2);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ assert!(errs.contains_key("b"));
+ if let ValidationErrorsKind::Struct(ref errs) = errs["b"] {
+ unwrap_map(errs, |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ } else {
+ panic!("Expected struct validation errors");
+ }
+ });
+ } else {
+ panic!("Expected struct validation errors");
+ }
+}
+
+#[test]
+fn test_can_validate_option_fields_without_lifetime() {
+ let instance = ParentWithOptionalChild {
+ child: Some(Child {
+ value: String::new(),
+ })
+ };
+
+ let res = instance.validate();
+ assert!(res.is_err());
+ let errs = res.unwrap_err().errors();
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("child"));
+ if let ValidationErrorsKind::Struct(ref errs) = errs["child"] {
+ unwrap_map(errs, |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ } else {
+ panic!("Expected struct validation errors");
+ }
+}
+
+#[test]
+fn test_can_validate_option_fields_with_lifetime() {
+ #[derive(Debug, Validate)]
+ struct ParentWithLifetimeAndOptionalChild<'a> {
+ #[validate]
+ child: Option<&'a Child>,
+ }
+
+ let child = Child {
+ value: String::new(),
+ };
+
+ let instance = ParentWithLifetimeAndOptionalChild {
+ child: Some(&child)
+ };
+
+ let res = instance.validate();
+ assert!(res.is_err());
+ let errs = res.unwrap_err().errors();
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("child"));
+ if let ValidationErrorsKind::Struct(ref errs) = errs["child"] {
+ unwrap_map(errs, |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ } else {
+ panic!("Expected struct validation errors");
+ }
+}
+
+#[test]
+fn test_works_with_none_values() {
+ let instance = ParentWithOptionalChild {
+ child: None,
+ };
+
+ let res = instance.validate();
+ assert!(res.is_ok());
+}
+
+#[test]
+fn test_can_validate_vector_fields() {
+ let instance = ParentWithVectorOfChildren {
+ child: vec![
+ Child {
+ value: "valid".to_string(),
+ },
+ Child {
+ value: String::new(),
+ },
+ Child {
+ value: "valid".to_string(),
+ },
+ Child {
+ value: String::new(),
+ }
+ ],
+ };
+
+ let res = instance.validate();
+ assert!(res.is_err());
+ let errs = res.unwrap_err().errors();
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("child"));
+ if let ValidationErrorsKind::List(ref errs) = errs["child"] {
+ assert!(errs.contains_key(&1));
+ unwrap_map(&errs[&1], |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ assert!(errs.contains_key(&3));
+ unwrap_map(&errs[&3], |errs| {
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("value"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["value"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+ });
+ } else {
+ panic!("Expected list validation errors");
+ }
+}
+
+#[test]
+fn test_field_validations_take_priority_over_nested_validations() {
+ let instance = ParentWithVectorOfChildren {
+ child: Vec::new(),
+ };
+
+ let res = instance.validate();
+ assert!(res.is_err());
+ let errs = res.unwrap_err().errors();
+ assert_eq!(errs.len(), 1);
+ assert!(errs.contains_key("child"));
+ if let ValidationErrorsKind::Field(ref errs) = errs["child"] {
+ assert_eq!(errs.len(), 1);
+ assert_eq!(errs[0].code, "length");
+ } else {
+ panic!("Expected field validation errors");
+ }
+}
+
+#[test]
+#[should_panic(expected = "Attempt to replace non-empty ValidationErrors entry")]
+#[allow(unused)]
+fn test_field_validation_errors_replaced_with_nested_validations_fails() {
+
+ #[derive(Debug)]
+ struct ParentWithOverridingStructValidations {
+ child: Vec<Child>,
+ }
+
+ impl Validate for ParentWithOverridingStructValidations {
+ // Evaluating structs after fields validations have discovered errors should fail because
+ // field validations are expected to take priority over nested struct validations
+ #[allow(unused_mut)]
+ fn validate(&self) -> Result<(), ValidationErrors> {
+ // First validate the length of the vector:
+ let mut errors = ValidationErrors::new();
+ if !validate_length(Validator::Length { min: Some(2u64), max: None, equal: None }, &self.child) {
+ let mut err = ValidationError::new("length");
+ err.add_param(Cow::from("min"), &2u64);
+ err.add_param(Cow::from("value"), &&self.child);
+ errors.add("child", err);
+ }
+
+ // Then validate the nested vector of structs without checking for existing field errors:
+ let mut result = if errors.is_empty() { Ok(()) } else { Err(errors) };
+ {
+ let results: Vec<_> = self.child.iter().map(|child| {
+ let mut result = Ok(());
+ result = ValidationErrors::merge(result, "child", child.validate());
+ result
+ }).collect();
+ result = ValidationErrors::merge_all(result, "child", results);
+ }
+ result
+ }
+ }
+
+ let instance = ParentWithOverridingStructValidations {
+ child: vec![
+ Child {
+ value: String::new()
+ }]
+ };
+ instance.validate();
+}
+
+#[test]
+#[should_panic(expected = "Attempt to add field validation to a non-Field ValidationErrorsKind instance")]
+#[allow(unused)]
+fn test_field_validations_evaluated_after_nested_validations_fails() {
+ #[derive(Debug)]
+ struct ParentWithStructValidationsFirst {
+ child: Vec<Child>,
+ }
+
+ impl Validate for ParentWithStructValidationsFirst {
+ // Evaluating fields after their nested structs should fail because field
+ // validations are expected to take priority over nested struct validations
+ #[allow(unused_mut)]
+ fn validate(&self) -> Result<(), ValidationErrors> {
+ // First validate the nested vector of structs:
+ let mut result = Ok(());
+ if !ValidationErrors::has_error(&result, "child") {
+ let results: Vec<_> = self.child.iter().map(|child| {
+ let mut result = Ok(());
+ result = ValidationErrors::merge(result, "child", child.validate());
+ result
+ }).collect();
+ result = ValidationErrors::merge_all(result, "child", results);
+ }
+
+ // Then validate the length of the vector itself:
+ if !validate_length(Validator::Length { min: Some(2u64), max: None, equal: None }, &self.child) {
+ let mut err = ValidationError::new("length");
+ err.add_param(Cow::from("min"), &2u64);
+ err.add_param(Cow::from("value"), &&self.child);
+ result = result.and_then(|_| Err(ValidationErrors::new())).map_err(|mut errors| {
+ errors.add("child", err);
+ errors
+ });
+ }
+ result
+ }
+ }
+
+ let instance = ParentWithStructValidationsFirst {
+ child: vec![
+ Child {
+ value: String::new()
+ }]
+ };
+ let res = instance.validate();
+}
+
+fn unwrap_map<F>(errors: &Box<ValidationErrors>, f: F)
+ where F: FnOnce(HashMap<&'static str, ValidationErrorsKind>)
+{
+ let errors = *errors.clone();
+ f(errors.errors());
+} \ No newline at end of file
diff --git a/validator_derive/tests/phone.rs b/validator_derive/tests/phone.rs
index ad8f3cd..3131bad 100644
--- a/validator_derive/tests/phone.rs
+++ b/validator_derive/tests/phone.rs
@@ -35,7 +35,7 @@ fn bad_phone_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "phone");
@@ -54,7 +54,7 @@ fn can_specify_code_for_phone() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -74,7 +74,7 @@ fn can_specify_message_for_phone() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/range.rs b/validator_derive/tests/range.rs
index 2ecd678..084e530 100644
--- a/validator_derive/tests/range.rs
+++ b/validator_derive/tests/range.rs
@@ -32,7 +32,7 @@ fn value_out_of_range_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "range");
@@ -50,7 +50,7 @@ fn can_specify_code_for_range() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -71,7 +71,7 @@ fn can_specify_message_for_range() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/regex.rs b/validator_derive/tests/regex.rs
index 58f8f69..b54df03 100644
--- a/validator_derive/tests/regex.rs
+++ b/validator_derive/tests/regex.rs
@@ -40,7 +40,7 @@ fn bad_value_for_regex_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "regex");
@@ -59,7 +59,7 @@ fn can_specify_code_for_regex() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -77,7 +77,7 @@ fn can_specify_message_for_regex() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
diff --git a/validator_derive/tests/schema.rs b/validator_derive/tests/schema.rs
index 6b3ae4f..32684fa 100644
--- a/validator_derive/tests/schema.rs
+++ b/validator_derive/tests/schema.rs
@@ -41,7 +41,7 @@ fn can_fail_schema_fn_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("__all__"));
assert_eq!(errs["__all__"].len(), 1);
assert_eq!(errs["__all__"][0].code, "meh");
@@ -63,7 +63,7 @@ fn can_specify_message_for_schema_fn() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("__all__"));
assert_eq!(errs["__all__"].len(), 1);
assert_eq!(errs["__all__"][0].clone().message.unwrap(), "oops");
@@ -89,7 +89,7 @@ fn can_choose_to_run_schema_validation_even_after_field_errors() {
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("__all__"));
assert_eq!(errs["__all__"].len(), 1);
assert_eq!(errs["__all__"][0].clone().code, "meh");
diff --git a/validator_derive/tests/url.rs b/validator_derive/tests/url.rs
index 1182086..5a3b791 100644
--- a/validator_derive/tests/url.rs
+++ b/validator_derive/tests/url.rs
@@ -33,7 +33,7 @@ fn bad_url_fails_validation() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "url");
@@ -51,7 +51,7 @@ fn can_specify_code_for_url() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].code, "oops");
@@ -70,7 +70,7 @@ fn can_specify_message_for_url() {
};
let res = s.validate();
assert!(res.is_err());
- let errs = res.unwrap_err().inner();
+ let errs = res.unwrap_err().field_errors();
assert!(errs.contains_key("val"));
assert_eq!(errs["val"].len(), 1);
assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");