aboutsummaryrefslogtreecommitdiffstats
path: root/validator_derive/tests/nested.rs
diff options
context:
space:
mode:
Diffstat (limited to 'validator_derive/tests/nested.rs')
-rw-r--r--validator_derive/tests/nested.rs370
1 files changed, 370 insertions, 0 deletions
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