Repository: Keats/validator (rust)

Issue:
Check length in email-validation
There seems to be a maximum to the allowed number of characters in an email.

According to my current understanding, doing regex-checks on very long email-addresses might be expensive. Checking the length before going to regex would be a faster way of detecting malicious email-strings & preventing DOS attacks.

In any case, doing an additional length-check would improve recognition of impossible emails.

A few links that might be helpful:
- https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address#574698
- https://www.lifewire.com/is-email-address-length-limited-1171110
- https://blog.moonmail.io/what-is-the-maximum-length-of-a-valid-email-address-f712c6c4bc93


Pull request description:
Email length validation before regex
I added validation for emails before doing regex validation on the input. However these RFC standards are a bit confusing and I'm unsure what lengths to set each part of the address to.

The regex, I think, validates the lengths like [??].[??]@[63].[63] 

Is the length of the user part being validated?

Fixes #209 

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a96f755..85c0421 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,7 +11,7 @@ jobs:
         include:
           - build: pinned
             os: ubuntu-20.04
-            rust: 1.46.0
+            rust: 1.56.1
           - build: stable
             os: ubuntu-20.04
             rust: stable
diff --git a/README.md b/README.md
index 96e72fb..88821f4 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,17 @@ Examples:
 #[validate(contains(pattern = "gmail"))]
 ```
 
+### does_not_contain
+Pretty much the opposite of contains, provided just for ease-of-use. Tests whether the string does not contain
+the substring given or if a key is NOT present in a hashmap. `does_not_contain` takes 1 string argument.
+
+Examples:
+
+```rust
+#[validate(does_not_contain = "gmail")]
+#[validate(does_not_contain(pattern = "gmail"))]
+```
+
 ### regex
 Tests whether the string matches the regex given. `regex` takes
 1 string argument: the path to a static Regex instance.
@@ -249,7 +260,7 @@ To use this validator, you must enable the `phone` feature for the `validator` c
 This validator doesn't take any arguments: `#[validate(phone)]`;
 
 ### custom
-Calls one of your functions to perform a custom validation. The field will reference be given as a parameter to the function,
+Calls one of your functions to perform a custom validation. The field reference will be given as a parameter to the function,
 which should return a `Result<(), ValidationError>`.
 
 Examples:
@@ -360,6 +371,7 @@ For example, the following attributes all work:
 #[validate(regex(path = "static_regex", code = "code_str"))]
 #[validate(custom(function = "custom_fn", code = "code_str"))]
 #[validate(contains(pattern = "pattern_str", code = "code_str"))]
+#[validate(does_not_contain(pattern = "pattern_str", code = "code_str"))]
 #[validate(must_match(other = "match_value", code = "code_str"))]
 
 // message attribute
@@ -369,6 +381,7 @@ For example, the following attributes all work:
 #[validate(regex(path = "static_regex", message = "message_str"))]
 #[validate(custom(function = "custom_fn", message = "message_str"))]
 #[validate(contains(pattern = "pattern_str", message = "message_str"))]
+#[validate(does_not_contain(pattern = "pattern_str", message = "message_str"))]
 #[validate(must_match(other = "match_value", message = "message_str"))]
 
 // both attributes
diff --git a/validator/Cargo.toml b/validator/Cargo.toml
index c569fee..0d94358 100644
--- a/validator/Cargo.toml
+++ b/validator/Cargo.toml
@@ -8,6 +8,7 @@ homepage = "https://github.com/Keats/validator"
 repository = "https://github.com/Keats/validator"
 keywords = ["validation", "api", "validator"]
 edition = "2018"
+readme = "../README.md"
 
 [dependencies]
 url = "2"
diff --git a/validator/src/lib.rs b/validator/src/lib.rs
index 508b1d7..8a7a0c6 100644
--- a/validator/src/lib.rs
+++ b/validator/src/lib.rs
@@ -45,6 +45,7 @@
 //! | `range`                 |                                                       |
 //! | `must_match`            |                                                       |
 //! | `contains`              |                                                       |
+//! | `does_not_contain`      |                                                       |
 //! | `custom`                |                                                       |
 //! | `regex`                 |                                                       |
 //! | `credit_card`           | (Requires the feature `card` to be enabled)           |
@@ -71,6 +72,7 @@ mod validation;
 #[cfg(feature = "card")]
 pub use validation::cards::validate_credit_card;
 pub use validation::contains::validate_contains;
+pub use validation::does_not_contain::validate_does_not_contain;
 pub use validation::email::validate_email;
 pub use validation::ip::{validate_ip, validate_ip_v4, validate_ip_v6};
 pub use validation::length::validate_length;
diff --git a/validator/src/validation/cards.rs b/validator/src/validation/cards.rs
index 5c6ea95..df3929b 100644
--- a/validator/src/validation/cards.rs
+++ b/validator/src/validation/cards.rs
@@ -33,12 +33,12 @@ mod tests {
     #[test]
     fn test_credit_card_cow() {
         let test: Cow<'static, str> = "4539571147647251".into();
-        assert_eq!(validate_credit_card(test), true);
+        assert!(validate_credit_card(test));
         let test: Cow<'static, str> = String::from("4539571147647251").into();
-        assert_eq!(validate_credit_card(test), true);
+        assert!(validate_credit_card(test));
         let test: Cow<'static, str> = "5236313877109141".into();
-        assert_eq!(validate_credit_card(test), false);
+        assert!(!validate_credit_card(test));
         let test: Cow<'static, str> = String::from("5236313877109141").into();
-        assert_eq!(validate_credit_card(test), false);
+        assert!(!validate_credit_card(test));
     }
 }
diff --git a/validator/src/validation/contains.rs b/validator/src/validation/contains.rs
index 2de8a65..7cd274d 100644
--- a/validator/src/validation/contains.rs
+++ b/validator/src/validation/contains.rs
@@ -22,7 +22,7 @@ mod tests {
 
     #[test]
     fn test_validate_contains_string_can_fail() {
-        assert_eq!(validate_contains("hey", "o"), false);
+        assert!(!validate_contains("hey", "o"));
     }
 
     #[test]
@@ -36,7 +36,7 @@ mod tests {
     fn test_validate_contains_hashmap_key_can_fail() {
         let mut map = HashMap::new();
         map.insert("hey".to_string(), 1);
-        assert_eq!(validate_contains(map, "bob"), false);
+        assert!(!validate_contains(map, "bob"));
     }
 
     #[test]
@@ -50,8 +50,8 @@ mod tests {
     #[test]
     fn test_validate_contains_cow_can_fail() {
         let test: Cow<'static, str> = "hey".into();
-        assert_eq!(validate_contains(test, "o"), false);
+        assert!(!validate_contains(test, "o"));
         let test: Cow<'static, str> = String::from("hey").into();
-        assert_eq!(validate_contains(test, "o"), false);
+        assert!(!validate_contains(test, "o"));
     }
 }
diff --git a/validator/src/validation/does_not_contain.rs b/validator/src/validation/does_not_contain.rs
new file mode 100644
index 0000000..06d34de
--- /dev/null
+++ b/validator/src/validation/does_not_contain.rs
@@ -0,0 +1,57 @@
+use crate::traits::Contains;
+
+/// Validates whether the value does not contain the needle
+/// The value needs to implement the Contains trait, which is implement on String, str and Hashmap<String>
+/// by default.
+#[must_use]
+pub fn validate_does_not_contain<T: Contains>(val: T, needle: &str) -> bool {
+    !val.has_element(needle)
+}
+
+#[cfg(test)]
+mod tests {
+    use std::borrow::Cow;
+    use std::collections::HashMap;
+
+    use super::*;
+
+    #[test]
+    fn test_validate_does_not_contain_string() {
+        assert_eq!(validate_does_not_contain("hey", "e"), false);
+    }
+
+    #[test]
+    fn test_validate_does_not_contain_string_can_fail() {
+        assert!(validate_does_not_contain("hey", "o"));
+    }
+
+    #[test]
+    fn test_validate_does_not_contain_hashmap_key() {
+        let mut map = HashMap::new();
+        map.insert("hey".to_string(), 1);
+        assert_eq!(validate_does_not_contain(map, "hey"), false);
+    }
+
+    #[test]
+    fn test_validate_does_not_contain_hashmap_key_can_fail() {
+        let mut map = HashMap::new();
+        map.insert("hey".to_string(), 1);
+        assert!(validate_does_not_contain(map, "bob"));
+    }
+
+    #[test]
+    fn test_validate_does_not_contain_cow() {
+        let test: Cow<'static, str> = "hey".into();
+        assert_eq!(validate_does_not_contain(test, "e"), false);
+        let test: Cow<'static, str> = String::from("hey").into();
+        assert_eq!(validate_does_not_contain(test, "e"), false);
+    }
+
+    #[test]
+    fn test_validate_does_not_contain_cow_can_fail() {
+        let test: Cow<'static, str> = "hey".into();
+        assert!(validate_does_not_contain(test, "o"));
+        let test: Cow<'static, str> = String::from("hey").into();
+        assert!(validate_does_not_contain(test, "o"));
+    }
+}
diff --git a/validator/src/validation/email.rs b/validator/src/validation/email.rs
index 2d168ab..e5aab3b 100644
--- a/validator/src/validation/email.rs
+++ b/validator/src/validation/email.rs
@@ -3,7 +3,7 @@ use lazy_static::lazy_static;
 use regex::Regex;
 use std::borrow::Cow;
 
-use crate::validation::ip::validate_ip;
+use crate::{validation::ip::validate_ip, HasLen};
 
 lazy_static! {
     // Regex from the specs
@@ -33,6 +33,14 @@ where
     let user_part = parts[1];
     let domain_part = parts[0];
 
+    // validate the length of each part of the email, BEFORE doing the regex
+    // according to RFC5321 the max length of the local part is 64 characters
+    // and the max length of the domain part is 255 characters
+    // https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.1
+    if user_part.length() > 64 || domain_part.length() > 255 {
+        return false;
+    }
+
     if !EMAIL_USER_RE.is_match(user_part) {
         return false;
     }
@@ -140,12 +148,22 @@ mod tests {
     #[test]
     fn test_validate_email_cow() {
         let test: Cow<'static, str> = "email@here.com".into();
-        assert_eq!(validate_email(test), true);
+        assert!(validate_email(test));
         let test: Cow<'static, str> = String::from("email@here.com").into();
-        assert_eq!(validate_email(test), true);
+        assert!(validate_email(test));
         let test: Cow<'static, str> = "a@[127.0.0.1]\n".into();
-        assert_eq!(validate_email(test), false);
+        assert!(!validate_email(test));
         let test: Cow<'static, str> = String::from("a@[127.0.0.1]\n").into();
+        assert!(!validate_email(test));
+    }
+
+    #[test]
+    fn test_validate_email_rfc5321() {
+        // 65 character local part
+        let test = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@mail.com";
+        assert_eq!(validate_email(test), false);
+        // 256 character domain part
+        let test = "a@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com";
         assert_eq!(validate_email(test), false);
     }
 }
diff --git a/validator/src/validation/ip.rs b/validator/src/validation/ip.rs
index 576b8c3..cae95c0 100644
--- a/validator/src/validation/ip.rs
+++ b/validator/src/validation/ip.rs
@@ -57,13 +57,13 @@ mod tests {
     #[test]
     fn test_validate_ip_cow() {
         let test: Cow<'static, str> = "1.1.1.1".into();
-        assert_eq!(validate_ip(test), true);
+        assert!(validate_ip(test));
         let test: Cow<'static, str> = String::from("1.1.1.1").into();
-        assert_eq!(validate_ip(test), true);
+        assert!(validate_ip(test));
         let test: Cow<'static, str> = "2a02::223:6cff :fe8a:2e8a".into();
-        assert_eq!(validate_ip(test), false);
+        assert!(!validate_ip(test));
         let test: Cow<'static, str> = String::from("2a02::223:6cff :fe8a:2e8a").into();
-        assert_eq!(validate_ip(test), false);
+        assert!(!validate_ip(test));
     }
 
     #[test]
@@ -88,13 +88,13 @@ mod tests {
     #[test]
     fn test_validate_ip_v4_cow() {
         let test: Cow<'static, str> = "1.1.1.1".into();
-        assert_eq!(validate_ip_v4(test), true);
+        assert!(validate_ip_v4(test));
         let test: Cow<'static, str> = String::from("1.1.1.1").into();
-        assert_eq!(validate_ip_v4(test), true);
+        assert!(validate_ip_v4(test));
         let test: Cow<'static, str> = "٧.2٥.3٣.243".into();
-        assert_eq!(validate_ip_v4(test), false);
+        assert!(!validate_ip_v4(test));
         let test: Cow<'static, str> = String::from("٧.2٥.3٣.243").into();
-        assert_eq!(validate_ip_v4(test), false);
+        assert!(!validate_ip_v4(test));
     }
 
     #[test]
@@ -130,12 +130,12 @@ mod tests {
     #[test]
     fn test_validate_ip_v6_cow() {
         let test: Cow<'static, str> = "fe80::223:6cff:fe8a:2e8a".into();
-        assert_eq!(validate_ip_v6(test), true);
+        assert!(validate_ip_v6(test));
         let test: Cow<'static, str> = String::from("fe80::223:6cff:fe8a:2e8a").into();
-        assert_eq!(validate_ip_v6(test), true);
+        assert!(validate_ip_v6(test));
         let test: Cow<'static, str> = "::ffff:zzzz:0a0a".into();
-        assert_eq!(validate_ip_v6(test), false);
+        assert!(!validate_ip_v6(test));
         let test: Cow<'static, str> = String::from("::ffff:zzzz:0a0a").into();
-        assert_eq!(validate_ip_v6(test), false);
+        assert!(!validate_ip_v6(test));
     }
 }
diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs
index e4a9d83..759c681 100644
--- a/validator/src/validation/length.rs
+++ b/validator/src/validation/length.rs
@@ -40,40 +40,40 @@ mod tests {
 
     #[test]
     fn test_validate_length_equal_overrides_min_max() {
-        assert_eq!(validate_length("hello", Some(1), Some(2), Some(5)), true);
+        assert!(validate_length("hello", Some(1), Some(2), Some(5)));
     }
 
     #[test]
     fn test_validate_length_string_min_max() {
-        assert_eq!(validate_length("hello", Some(1), Some(10), None), true);
+        assert!(validate_length("hello", Some(1), Some(10), None));
     }
 
     #[test]
     fn test_validate_length_string_min_only() {
-        assert_eq!(validate_length("hello", Some(10), None, None), false);
+        assert!(!validate_length("hello", Some(10), None, None));
     }
 
     #[test]
     fn test_validate_length_string_max_only() {
-        assert_eq!(validate_length("hello", None, Some(1), None), false);
+        assert!(!validate_length("hello", None, Some(1), None));
     }
 
     #[test]
     fn test_validate_length_cow() {
         let test: Cow<'static, str> = "hello".into();
-        assert_eq!(validate_length(test, None, None, Some(5)), true);
+        assert!(validate_length(test, None, None, Some(5)));
 
         let test: Cow<'static, str> = String::from("hello").into();
-        assert_eq!(validate_length(test, None, None, Some(5)), true);
+        assert!(validate_length(test, None, None, Some(5)));
     }
 
     #[test]
     fn test_validate_length_vec() {
-        assert_eq!(validate_length(vec![1, 2, 3], None, None, Some(3)), true);
+        assert!(validate_length(vec![1, 2, 3], None, None, Some(3)));
     }
 
     #[test]
     fn test_validate_length_unicode_chars() {
-        assert_eq!(validate_length("日本", None, None, Some(2)), true);
+        assert!(validate_length("日本", None, None, Some(2)));
     }
 }
diff --git a/validator/src/validation/mod.rs b/validator/src/validation/mod.rs
index 664e940..d430715 100644
--- a/validator/src/validation/mod.rs
+++ b/validator/src/validation/mod.rs
@@ -1,6 +1,7 @@
 #[cfg(feature = "card")]
 pub mod cards;
 pub mod contains;
+pub mod does_not_contain;
 pub mod email;
 pub mod ip;
 pub mod length;
diff --git a/validator/src/validation/must_match.rs b/validator/src/validation/must_match.rs
index 5c93de1..ea96cb0 100644
--- a/validator/src/validation/must_match.rs
+++ b/validator/src/validation/must_match.rs
@@ -30,12 +30,12 @@ mod tests {
 
     #[test]
     fn test_validate_must_match_numbers_false() {
-        assert_eq!(false, validate_must_match(2, 3));
+        assert!(!validate_must_match(2, 3));
     }
 
     #[test]
     fn test_validate_must_match_numbers_option_false() {
-        assert_eq!(false, validate_must_match(Some(2), Some(3)));
+        assert!(!validate_must_match(Some(2), Some(3)));
     }
 
     #[test]
@@ -45,12 +45,12 @@ mod tests {
 
     #[test]
     fn test_validate_must_match_none_some_false() {
-        assert_eq!(false, validate_must_match(None, Some(3)));
+        assert!(!validate_must_match(None, Some(3)));
     }
 
     #[test]
     fn test_validate_must_match_some_none_false() {
-        assert_eq!(false, validate_must_match(Some(3), None));
+        assert!(!validate_must_match(Some(3), None));
     }
 
     #[test]
diff --git a/validator/src/validation/non_control_character.rs b/validator/src/validation/non_control_character.rs
index c64d37c..6ba2692 100644
--- a/validator/src/validation/non_control_character.rs
+++ b/validator/src/validation/non_control_character.rs
@@ -36,12 +36,12 @@ mod tests {
     #[test]
     fn test_non_control_character_cow() {
         let test: Cow<'static, str> = "आकाश".into();
-        assert_eq!(validate_non_control_character(test), true);
+        assert!(validate_non_control_character(test));
         let test: Cow<'static, str> = String::from("வானத்தில்").into();
-        assert_eq!(validate_non_control_character(test), true);
+        assert!(validate_non_control_character(test));
         let test: Cow<'static, str> = "\u{000c}".into();
-        assert_eq!(validate_non_control_character(test), false);
+        assert!(!validate_non_control_character(test));
         let test: Cow<'static, str> = String::from("\u{009F}").into();
-        assert_eq!(validate_non_control_character(test), false);
+        assert!(!validate_non_control_character(test));
     }
 }
diff --git a/validator/src/validation/phone.rs b/validator/src/validation/phone.rs
index 0b629e8..96a8137 100644
--- a/validator/src/validation/phone.rs
+++ b/validator/src/validation/phone.rs
@@ -40,12 +40,12 @@ mod tests {
     #[test]
     fn test_phone_cow() {
         let test: Cow<'static, str> = "+1 (415) 237-0800".into();
-        assert_eq!(validate_phone(test), true);
+        assert!(validate_phone(test));
         let test: Cow<'static, str> = String::from("+1 (415) 237-0800").into();
-        assert_eq!(validate_phone(test), true);
+        assert!(validate_phone(test));
         let test: Cow<'static, str> = "TEXT".into();
-        assert_eq!(validate_phone(test), false);
+        assert!(!validate_phone(test));
         let test: Cow<'static, str> = String::from("TEXT").into();
-        assert_eq!(validate_phone(test), false);
+        assert!(!validate_phone(test));
     }
 }
diff --git a/validator/src/validation/range.rs b/validator/src/validation/range.rs
index 7db1637..3b3cf6a 100644
--- a/validator/src/validation/range.rs
+++ b/validator/src/validation/range.rs
@@ -28,30 +28,30 @@ mod tests {
     #[test]
     fn test_validate_range_generic_ok() {
         // Unspecified generic type:
-        assert_eq!(true, validate_range(10, Some(-10), Some(10)));
-        assert_eq!(true, validate_range(0.0, Some(0.0), Some(10.0)));
+        assert!(validate_range(10, Some(-10), Some(10)));
+        assert!(validate_range(0.0, Some(0.0), Some(10.0)));
 
         // Specified type:
-        assert_eq!(true, validate_range(5u8, Some(0), Some(255)));
-        assert_eq!(true, validate_range(4u16, Some(0), Some(16)));
-        assert_eq!(true, validate_range(6u32, Some(0), Some(23)));
+        assert!(validate_range(5u8, Some(0), Some(255)));
+        assert!(validate_range(4u16, Some(0), Some(16)));
+        assert!(validate_range(6u32, Some(0), Some(23)));
     }
 
     #[test]
     fn test_validate_range_generic_fail() {
-        assert_eq!(false, validate_range(5, Some(17), Some(19)));
-        assert_eq!(false, validate_range(-1.0, Some(0.0), Some(10.0)));
+        assert!(!validate_range(5, Some(17), Some(19)));
+        assert!(!validate_range(-1.0, Some(0.0), Some(10.0)));
     }
 
     #[test]
     fn test_validate_range_generic_min_only() {
-        assert_eq!(false, validate_range(5, Some(10), None));
-        assert_eq!(true, validate_range(15, Some(10), None));
+        assert!(!validate_range(5, Some(10), None));
+        assert!(validate_range(15, Some(10), None));
     }
 
     #[test]
     fn test_validate_range_generic_max_only() {
-        assert_eq!(true, validate_range(5, None, Some(10)));
-        assert_eq!(false, validate_range(15, None, Some(10)));
+        assert!(validate_range(5, None, Some(10)));
+        assert!(!validate_range(15, None, Some(10)));
     }
 }
diff --git a/validator/src/validation/urls.rs b/validator/src/validation/urls.rs
index 1759dcd..41774d1 100644
--- a/validator/src/validation/urls.rs
+++ b/validator/src/validation/urls.rs
@@ -33,12 +33,12 @@ mod tests {
     #[test]
     fn test_validate_url_cow() {
         let test: Cow<'static, str> = "http://localhost:80".into();
-        assert_eq!(validate_url(test), true);
+        assert!(validate_url(test));
         let test: Cow<'static, str> = String::from("http://localhost:80").into();
-        assert_eq!(validate_url(test), true);
+        assert!(validate_url(test));
         let test: Cow<'static, str> = "http".into();
-        assert_eq!(validate_url(test), false);
+        assert!(!validate_url(test));
         let test: Cow<'static, str> = String::from("http").into();
-        assert_eq!(validate_url(test), false);
+        assert!(!validate_url(test));
     }
 }
diff --git a/validator_derive/Cargo.toml b/validator_derive/Cargo.toml
index 9eceb5f..53e253d 100644
--- a/validator_derive/Cargo.toml
+++ b/validator_derive/Cargo.toml
@@ -8,6 +8,7 @@ homepage = "https://github.com/Keats/validator"
 repository = "https://github.com/Keats/validator"
 keywords = ["validation", "api", "validator"]
 edition = "2018"
+readme = "../README.md"
 
 [lib]
 proc-macro = true
diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs
index 0ceb903..afdf0ef 100644
--- a/validator_derive/src/lib.rs
+++ b/validator_derive/src/lib.rs
@@ -135,20 +135,19 @@ fn collect_field_validations(ast: &syn::DeriveInput) -> Vec<FieldInformation> {
 }
 
 fn construct_validator_argument_type(
-    fields_validations: &mut Vec<FieldInformation>,
-    struct_validations: &mut Vec<SchemaValidation>,
+    fields_validations: &mut [FieldInformation],
+    struct_validations: &mut [SchemaValidation],
 ) -> (proc_macro2::TokenStream, bool) {
     const ARGS_PARAMETER_NAME: &str = "args";
 
     // This iterator only holds custom validations with a argument_type
     let mut customs: Vec<&mut CustomArgument> = fields_validations
         .iter_mut()
-        .map(|x| x.validations.iter_mut().filter_map(|x| x.validator.get_custom_argument_mut()))
-        .flatten()
+        .flat_map(|x| x.validations.iter_mut().filter_map(|x| x.validator.get_custom_argument_mut()))
         .collect();
 
     let mut schemas: Vec<&mut CustomArgument> =
-        struct_validations.iter_mut().map(|x| x.args.as_mut()).flatten().collect();
+        struct_validations.iter_mut().filter_map(|x| x.args.as_mut()).collect();
 
     customs.append(&mut schemas);
 
@@ -468,6 +467,12 @@ fn find_validators_for_field(
                                             None => error(lit.span(), "invalid argument for `contains` validator: only strings are allowed"),
                                         };
                                     }
+                                    "does_not_contain" => {
+                                        match lit_to_string(lit) {
+                                            Some(s) => validators.push(FieldValidation::new(Validator::DoesNotContain(s))),
+                                            None => error(lit.span(), "invalid argument for `does_not_contain` validator: only strings are allowed"),
+                                        };
+                                    }
                                     "regex" => {
                                         match lit_to_string(lit) {
                                             Some(s) => validators.push(FieldValidation::new(Validator::Regex(s))),
@@ -522,14 +527,15 @@ fn find_validators_for_field(
                                     | "url"
                                     | "phone"
                                     | "credit_card"
-                                    | "non_control_character" => {
+                                    | "non_control_character"
+                                    | "required" => {
                                         validators.push(extract_argless_validation(
                                             ident.to_string(),
                                             rust_ident.clone(),
                                             &meta_items,
                                         ));
                                     }
-                                    "contains" => {
+                                    "contains" | "does_not_contain" => {
                                         validators.push(extract_one_arg_validation(
                                             "pattern",
                                             ident.to_string(),
diff --git a/validator_derive/src/lit.rs b/validator_derive/src/lit.rs
index d98d4f2..fe57eb0 100644
--- a/validator_derive/src/lit.rs
+++ b/validator_derive/src/lit.rs
@@ -73,7 +73,7 @@ where
         ValueOrPath::Value(ref t) => quote!(#t),
         ValueOrPath::Path(ref path) => {
             // Global space
-            let ident: syn::Path = syn::parse_str(&path.to_string()).unwrap();
+            let ident: syn::Path = syn::parse_str(path).unwrap();
             quote!(#ident)
         }
     }
diff --git a/validator_derive/src/quoting.rs b/validator_derive/src/quoting.rs
index d4a1e33..b9e1523 100644
--- a/validator_derive/src/quoting.rs
+++ b/validator_derive/src/quoting.rs
@@ -144,8 +144,8 @@ impl FieldQuoter {
 }
 
 fn is_map(_type: &str) -> bool {
-    if _type.starts_with("Option<") {
-        return is_map(&_type[7..]);
+    if let Some(stripped) = _type.strip_prefix("Option<") {
+        return is_map(stripped);
     }
 
     _type.starts_with("HashMap<")
@@ -540,6 +540,9 @@ pub fn quote_validator(
         Validator::Required | Validator::RequiredNested => {
             validations.push(quote_required_validation(field_quoter, validation))
         }
+        Validator::DoesNotContain(_) => {
+            validations.push(quote_does_not_contain_validation(field_quoter, validation))
+        }
     }
 }
 
@@ -603,3 +606,27 @@ pub fn quote_required_validation(
 
     quoted
 }
+
+pub fn quote_does_not_contain_validation(
+    field_quoter: &FieldQuoter,
+    validation: &FieldValidation,
+) -> proc_macro2::TokenStream {
+    let field_name = &field_quoter.name;
+    let validator_param = field_quoter.quote_validator_param();
+
+    if let Validator::DoesNotContain(ref needle) = validation.validator {
+        let quoted_error = quote_error(validation);
+        let quoted = quote!(
+            if !::validator::validate_does_not_contain(#validator_param, &#needle) {
+                #quoted_error
+                err.add_param(::std::borrow::Cow::from("value"), &#validator_param);
+                err.add_param(::std::borrow::Cow::from("needle"), &#needle);
+                errors.add(#field_name, err);
+            }
+        );
+
+        return field_quoter.wrap_if_option(quoted);
+    }
+
+    unreachable!();
+}
diff --git a/validator_derive/src/validation.rs b/validator_derive/src/validation.rs
index 271dc06..b889cca 100644
--- a/validator_derive/src/validation.rs
+++ b/validator_derive/src/validation.rs
@@ -301,6 +301,7 @@ pub fn extract_argless_validation(
         "phone" => Validator::Phone,
         #[cfg(feature = "unic")]
         "non_control_character" => Validator::NonControlCharacter,
+        "required" => Validator::Required,
         _ => Validator::Url,
     };
 
@@ -370,6 +371,7 @@ pub fn extract_one_arg_validation(
     let validator = match validator_name.as_ref() {
         "custom" => Validator::Custom { function: value.unwrap(), argument: Box::new(None) },
         "contains" => Validator::Contains(value.unwrap()),
+        "does_not_contain" => Validator::DoesNotContain(value.unwrap()),
         "must_match" => Validator::MustMatch(value.unwrap()),
         "regex" => Validator::Regex(value.unwrap()),
         _ => unreachable!(),
diff --git a/validator_types/Cargo.toml b/validator_types/Cargo.toml
index 23b4b7a..0290dc3 100644
--- a/validator_types/Cargo.toml
+++ b/validator_types/Cargo.toml
@@ -8,6 +8,7 @@ homepage = "https://github.com/Keats/validator"
 repository = "https://github.com/Keats/validator"
 keywords = ["validation", "api", "validator"]
 edition = "2018"
+readme = "../README.md"
 
 
 [features]
diff --git a/validator_types/src/lib.rs b/validator_types/src/lib.rs
index 12350cd..42dbdaf 100644
--- a/validator_types/src/lib.rs
+++ b/validator_types/src/lib.rs
@@ -40,6 +40,7 @@ pub enum Validator {
     NonControlCharacter,
     Required,
     RequiredNested,
+    DoesNotContain(String),
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -89,6 +90,7 @@ impl Validator {
             Validator::NonControlCharacter => "non_control_character",
             Validator::Required => "required",
             Validator::RequiredNested => "required_nested",
+            Validator::DoesNotContain(_) => "does_not_contain",
         }
     }
 


Test patch:
diff --git a/validator_derive_tests/tests/does_not_contain.rs b/validator_derive_tests/tests/does_not_contain.rs
new file mode 100644
index 0000000..b7af99f
--- /dev/null
+++ b/validator_derive_tests/tests/does_not_contain.rs
@@ -0,0 +1,68 @@
+use validator::Validate;
+
+#[test]
+fn can_validate_does_not_contain_ok() {
+    #[derive(Debug, Validate)]
+    struct TestStruct {
+        #[validate(does_not_contain = "asdf")]
+        val: String,
+    }
+
+    let s = TestStruct { val: "hello".to_string() };
+
+    assert!(s.validate().is_ok());
+}
+
+#[test]
+fn value_containing_needle_fails_validation() {
+    #[derive(Debug, Validate)]
+    struct TestStruct {
+        #[validate(does_not_contain = "he")]
+        val: String,
+    }
+
+    let s = TestStruct { val: "hello".to_string() };
+    let res = s.validate();
+    assert!(res.is_err());
+    let err = res.unwrap_err();
+    let errs = err.field_errors();
+    assert!(errs.contains_key("val"));
+    assert_eq!(errs["val"].len(), 1);
+    assert_eq!(errs["val"][0].code, "does_not_contain");
+    assert_eq!(errs["val"][0].params["value"], "hello");
+    assert_eq!(errs["val"][0].params["needle"], "he");
+}
+
+#[test]
+fn can_specify_code_for_does_not_contain() {
+    #[derive(Debug, Validate)]
+    struct TestStruct {
+        #[validate(does_not_contain(pattern = "he", code = "oops"))]
+        val: String,
+    }
+    let s = TestStruct { val: "hello".to_string() };
+    let res = s.validate();
+    assert!(res.is_err());
+    let err = res.unwrap_err();
+    let errs = err.field_errors();
+    assert!(errs.contains_key("val"));
+    assert_eq!(errs["val"].len(), 1);
+    assert_eq!(errs["val"][0].code, "oops");
+}
+
+#[test]
+fn can_specify_message_for_does_not_contain() {
+    #[derive(Debug, Validate)]
+    struct TestStruct {
+        #[validate(does_not_contain(pattern = "he", message = "oops"))]
+        val: String,
+    }
+    let s = TestStruct { val: "hello".to_string() };
+    let res = s.validate();
+    assert!(res.is_err());
+    let err = res.unwrap_err();
+    let errs = 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/tests/length.rs b/validator_derive_tests/tests/length.rs
index 8e8d131..9a3ed63 100644
--- a/validator_derive_tests/tests/length.rs
+++ b/validator_derive_tests/tests/length.rs
@@ -42,7 +42,7 @@ fn validate_length_with_ref_fails() {
 
     let s = TestStruct { val: "".to_string() };
 
-    assert_eq!(s.validate().is_ok(), false);
+    assert!(s.validate().is_err());
 }
 
 #[test]
@@ -55,7 +55,7 @@ fn validate_length_with_ref_i32_fails() {
 
     let s = TestStruct { val: "TO_LONG_YAY".to_string() };
 
-    assert_eq!(s.validate().is_ok(), false);
+    assert!(s.validate().is_err());
 }
 
 #[test]
@@ -68,7 +68,7 @@ fn validate_length_with_ref_negative_i32_fails() {
 
     let s = TestStruct { val: "TO_LONG_YAY".to_string() };
 
-    assert_eq!(s.validate().is_ok(), true);
+    assert!(s.validate().is_ok());
 }
 
 #[test]
diff --git a/validator_derive_tests/tests/required.rs b/validator_derive_tests/tests/required.rs
index 548b378..81cd7d5 100644
--- a/validator_derive_tests/tests/required.rs
+++ b/validator_derive_tests/tests/required.rs
@@ -56,3 +56,37 @@ fn none_fails_required_nested() {
 
     assert!(s.validate().is_err());
 }
+
+#[test]
+fn can_specify_code_for_required() {
+	#[derive(Debug, Validate)]
+	struct TestStruct {
+		#[validate(required(code = "oops"))]
+		val: Option<String>
+	}
+	let s = TestStruct { val: None };
+	let res = s.validate();
+	assert!(res.is_err());
+	let err = res.unwrap_err();
+	let errs = err.field_errors();
+	assert!(errs.contains_key("val"));
+	assert_eq!(errs["val"].len(), 1);
+	assert_eq!(errs["val"][0].code, "oops");
+}
+
+#[test]
+fn can_specify_message_for_required() {
+	#[derive(Debug, Validate)]
+	struct TestStruct {
+		#[validate(required(message = "oops"))]
+		val: Option<String>
+	}
+	let s = TestStruct { val: None };
+	let res = s.validate();
+	assert!(res.is_err());
+	let err = res.unwrap_err();
+	let errs = err.field_errors();
+	assert!(errs.contains_key("val"));
+	assert_eq!(errs["val"].len(), 1);
+	assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
+}
\ No newline at end of file