aboutsummaryrefslogtreecommitdiffstats
path: root/validator_derive
diff options
context:
space:
mode:
Diffstat (limited to 'validator_derive')
-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
16 files changed, 611 insertions, 56 deletions
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");