aboutsummaryrefslogtreecommitdiffstats
path: root/validator_derive/tests
diff options
context:
space:
mode:
authorSimon Sparks2018-09-13 17:33:49 +0100
committerVincent Prouillet2018-09-13 18:33:49 +0200
commitdfdc289626c448522c43c13f8d72033fe0d1cae8 (patch)
treec2fcd006466c587040be8b88076c1477d908effc /validator_derive/tests
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
Diffstat (limited to 'validator_derive/tests')
-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
14 files changed, 544 insertions, 36 deletions
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");