aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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");