diff options
| author | Simon Sparks | 2018-07-27 10:02:04 +0100 | 
|---|---|---|
| committer | Vincent Prouillet | 2018-07-27 11:02:04 +0200 | 
| commit | f4b11eaecb8fcc4798eb3a932a68c47ed802d96d (patch) | |
| tree | 2ba683f8512308bfd10f3293f304bbee975ee8df | |
| parent | 889e7e7e7fad2a2156a46d8382558c6cfbf59628 (diff) | |
| download | validator-f4b11eaecb8fcc4798eb3a932a68c47ed802d96d.tar.bz2 | |
Added support for validating Cow<'a, str> fields. (#56)
* Added support for validating Cow<'a, str> fields.
Keats/validator#55
* Corrected error message comparison in compiler-fail/length test.
Keats/validator#55
| -rw-r--r-- | validator/src/traits.rs | 13 | ||||
| -rw-r--r-- | validator/src/validation/cards.rs | 23 | ||||
| -rw-r--r-- | validator/src/validation/contains.rs | 17 | ||||
| -rw-r--r-- | validator/src/validation/email.rs | 22 | ||||
| -rw-r--r-- | validator/src/validation/ip.rs | 59 | ||||
| -rw-r--r-- | validator/src/validation/length.rs | 12 | ||||
| -rw-r--r-- | validator/src/validation/must_match.rs | 10 | ||||
| -rw-r--r-- | validator/src/validation/phone.rs | 22 | ||||
| -rw-r--r-- | validator/src/validation/urls.rs | 21 | ||||
| -rw-r--r-- | validator_derive/Cargo.toml | 4 | ||||
| -rw-r--r-- | validator_derive/src/asserts.rs | 11 | ||||
| -rw-r--r-- | validator_derive/src/lib.rs | 13 | ||||
| -rw-r--r-- | validator_derive/src/quoting.rs | 4 | ||||
| -rw-r--r-- | validator_derive/tests/compile-fail/length/wrong_type.rs | 2 | 
14 files changed, 205 insertions, 28 deletions
| diff --git a/validator/src/traits.rs b/validator/src/traits.rs index 896fcc7..c34819c 100644 --- a/validator/src/traits.rs +++ b/validator/src/traits.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow;  use std::collections::HashMap;  use types::ValidationErrors; @@ -29,6 +30,12 @@ impl<'a> HasLen for &'a str {      }  } +impl<'a> HasLen for Cow<'a, str> { +    fn length(&self) -> u64 { +        self.len() as u64 +    } +} +  impl<T> HasLen for Vec<T> {      fn length(&self) -> u64 {          self.len() as u64 @@ -64,6 +71,12 @@ impl<'a> Contains for &'a str {      }  } +impl<'a> Contains for Cow<'a, str> { +    fn has_element(&self, needle: &str) -> bool { +        self.contains(needle) +    } +} +  impl<S> Contains for HashMap<String, S> {      fn has_element(&self, needle: &str) -> bool {          self.contains_key(needle) diff --git a/validator/src/validation/cards.rs b/validator/src/validation/cards.rs index b72663d..b565ed1 100644 --- a/validator/src/validation/cards.rs +++ b/validator/src/validation/cards.rs @@ -1,13 +1,20 @@ +use std::borrow::Cow; +  use card_validate::{Validate as CardValidate}; -pub fn validate_credit_card(card: &str) -> bool { -    CardValidate::from(card).is_ok() +pub fn validate_credit_card<'a, T>(card: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    CardValidate::from(&card.into()).is_ok()  }  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::validate_credit_card; +      #[test]      fn test_credit_card() {          let tests = vec![ @@ -21,4 +28,16 @@ mod tests {              assert_eq!(validate_credit_card(input), expected);          }      } + +    #[test] +    fn test_credit_card_cow() { +        let test: Cow<'static, str> = "4539571147647251".into(); +        assert_eq!(validate_credit_card(test), true); +        let test: Cow<'static, str> = String::from("4539571147647251").into(); +        assert_eq!(validate_credit_card(test), true); +        let test: Cow<'static, str> = "5236313877109141".into(); +        assert_eq!(validate_credit_card(test), false); +        let test: Cow<'static, str> = String::from("5236313877109141").into(); +        assert_eq!(validate_credit_card(test), false); +    }  } diff --git a/validator/src/validation/contains.rs b/validator/src/validation/contains.rs index 58baabe..8c7b297 100644 --- a/validator/src/validation/contains.rs +++ b/validator/src/validation/contains.rs @@ -9,6 +9,7 @@ pub fn validate_contains<T: Contains>(val: T, needle: &str) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow;      use std::collections::HashMap;      use super::*; @@ -36,4 +37,20 @@ mod tests {          map.insert("hey".to_string(), 1);          assert_eq!(validate_contains(map, "bob"), false);      } + +    #[test] +    fn test_validate_contains_cow() { +        let test: Cow<'static, str> = "hey".into(); +        assert!(validate_contains(test, "e")); +        let test: Cow<'static, str> = String::from("hey").into(); +        assert!(validate_contains(test, "e")); +    } + +    #[test] +    fn test_validate_contains_cow_can_fail() { +        let test: Cow<'static, str> = "hey".into(); +        assert_eq!(validate_contains(test, "o"), false); +        let test: Cow<'static, str> = String::from("hey").into(); +        assert_eq!(validate_contains(test, "o"), false); +    }  } diff --git a/validator/src/validation/email.rs b/validator/src/validation/email.rs index cf6adc5..699ab77 100644 --- a/validator/src/validation/email.rs +++ b/validator/src/validation/email.rs @@ -1,5 +1,6 @@ -use regex::Regex;  use idna::domain_to_ascii; +use std::borrow::Cow; +use regex::Regex;  use validation::ip::validate_ip; @@ -17,7 +18,10 @@ lazy_static! {  }  /// Validates whether the given string is an email based on Django `EmailValidator` and HTML5 specs -pub fn validate_email(val: &str) -> bool { +pub fn validate_email<'a, T>(val: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    let val = val.into();      if val.is_empty() || !val.contains('@') {          return false;      } @@ -61,6 +65,8 @@ fn validate_domain_part(domain_part: &str) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::validate_email;      #[test] @@ -118,4 +124,16 @@ mod tests {              assert_eq!(validate_email(input), expected);          }      } + +    #[test] +    fn test_validate_email_cow() { +        let test: Cow<'static, str> = "email@here.com".into(); +        assert_eq!(validate_email(test), true); +        let test: Cow<'static, str> = String::from("email@here.com").into(); +        assert_eq!(validate_email(test), true); +        let test: Cow<'static, str> = "a@[127.0.0.1]\n".into(); +        assert_eq!(validate_email(test), false); +        let test: Cow<'static, str> = String::from("a@[127.0.0.1]\n").into(); +        assert_eq!(validate_email(test), false); +    }  } diff --git a/validator/src/validation/ip.rs b/validator/src/validation/ip.rs index 9a94522..955cf3c 100644 --- a/validator/src/validation/ip.rs +++ b/validator/src/validation/ip.rs @@ -1,10 +1,13 @@ -use std::str::FromStr; +use std::borrow::Cow;  use std::net::IpAddr; +use std::str::FromStr;  /// Validates whether the given string is an IP V4 -pub fn validate_ip_v4(val: &str) -> bool { -    match IpAddr::from_str(val) { +pub fn validate_ip_v4<'a, T>(val: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    match IpAddr::from_str(val.into().as_ref()) {          Ok(i) => match i {              IpAddr::V4(_) => true,              IpAddr::V6(_) => false, @@ -14,8 +17,10 @@ pub fn validate_ip_v4(val: &str) -> bool {  }  /// Validates whether the given string is an IP V6 -pub fn validate_ip_v6(val: &str) -> bool { -    match IpAddr::from_str(val) { +pub fn validate_ip_v6<'a, T>(val: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    match IpAddr::from_str(val.into().as_ref()) {          Ok(i) => match i {              IpAddr::V4(_) => false,              IpAddr::V6(_) => true, @@ -25,8 +30,10 @@ pub fn validate_ip_v6(val: &str) -> bool {  }  /// Validates whether the given string is an IP -pub fn validate_ip(val: &str) -> bool { -    match IpAddr::from_str(val) { +pub fn validate_ip<'a, T>(val: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    match IpAddr::from_str(val.into().as_ref()) {          Ok(_) => true,          Err(_) => false,      } @@ -35,6 +42,8 @@ pub fn validate_ip(val: &str) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::{validate_ip_v4, validate_ip_v6, validate_ip};      #[test] @@ -57,6 +66,18 @@ mod tests {      }      #[test] +    fn test_validate_ip_cow() { +        let test: Cow<'static, str> = "1.1.1.1".into(); +        assert_eq!(validate_ip(test), true); +        let test: Cow<'static, str> = String::from("1.1.1.1").into(); +        assert_eq!(validate_ip(test), true); +        let test: Cow<'static, str> = "2a02::223:6cff :fe8a:2e8a".into(); +        assert_eq!(validate_ip(test), false); +        let test: Cow<'static, str> = String::from("2a02::223:6cff :fe8a:2e8a").into(); +        assert_eq!(validate_ip(test), false); +    } + +    #[test]      fn test_validate_ip_v4() {          let tests = vec![              ("1.1.1.1", true), @@ -76,6 +97,18 @@ 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); +        let test: Cow<'static, str> = String::from("1.1.1.1").into(); +        assert_eq!(validate_ip_v4(test), true); +        let test: Cow<'static, str> = "٧.2٥.3٣.243".into(); +        assert_eq!(validate_ip_v4(test), false); +        let test: Cow<'static, str> = String::from("٧.2٥.3٣.243").into(); +        assert_eq!(validate_ip_v4(test), false); +    } + +    #[test]      fn test_validate_ip_v6() {          let tests = vec![              ("fe80::223:6cff:fe8a:2e8a", true), @@ -104,4 +137,16 @@ mod tests {              assert_eq!(validate_ip_v6(input), expected);          }      } + +    #[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); +        let test: Cow<'static, str> = String::from("fe80::223:6cff:fe8a:2e8a").into(); +        assert_eq!(validate_ip_v6(test), true); +        let test: Cow<'static, str> = "::ffff:zzzz:0a0a".into(); +        assert_eq!(validate_ip_v6(test), false); +        let test: Cow<'static, str> = String::from("::ffff:zzzz:0a0a").into(); +        assert_eq!(validate_ip_v6(test), false); +    }  } diff --git a/validator/src/validation/length.rs b/validator/src/validation/length.rs index 1c3affc..c59a6cb 100644 --- a/validator/src/validation/length.rs +++ b/validator/src/validation/length.rs @@ -32,6 +32,8 @@ pub fn validate_length<T: HasLen>(length: Validator, val: T) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::{validate_length, Validator};      #[test] @@ -59,6 +61,16 @@ mod tests {      }      #[test] +    fn test_validate_length_cow() { +        let validator = Validator::Length { min: Some(1), max: Some(2), equal: Some(5) }; +        let test: Cow<'static, str> = "hello".into(); +        assert_eq!(validate_length(validator, test), true); +        let validator = Validator::Length { min: Some(1), max: Some(2), equal: Some(5) }; +        let test: Cow<'static, str> = String::from("hello").into(); +        assert_eq!(validate_length(validator, test), true); +    } + +    #[test]      fn test_validate_length_vec() {          let validator = Validator::Length { min: None, max: None, equal: Some(3) };          assert_eq!(validate_length(validator, vec![1, 2, 3]), true); diff --git a/validator/src/validation/must_match.rs b/validator/src/validation/must_match.rs index 35fbee6..5024e9f 100644 --- a/validator/src/validation/must_match.rs +++ b/validator/src/validation/must_match.rs @@ -6,6 +6,8 @@ pub fn validate_must_match<T: Eq>(a: T, b: T) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::{validate_must_match};      #[test] @@ -14,6 +16,13 @@ mod tests {      }      #[test] +    fn test_validate_must_match_cows_valid() { +        let left: Cow<'static, str> = "hey".into(); +        let right: Cow<'static, str> = String::from("hey").into(); +        assert!(validate_must_match(left, right)) +    } + +    #[test]      fn test_validate_must_match_numbers() {          assert!(validate_must_match(2, 2))      } @@ -22,5 +31,4 @@ mod tests {      fn test_validate_must_match_numbers_false() {          assert_eq!(false, validate_must_match(2, 3));      } -  } diff --git a/validator/src/validation/phone.rs b/validator/src/validation/phone.rs index 7772d61..41d45a5 100644 --- a/validator/src/validation/phone.rs +++ b/validator/src/validation/phone.rs @@ -1,8 +1,11 @@  use phonenumber; +use std::borrow::Cow; -pub fn validate_phone(phone_number: &str) -> bool { -    if let Ok(parsed) = phonenumber::parse(None, phone_number) { +pub fn validate_phone<'a, T>(phone_number: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    if let Ok(parsed) = phonenumber::parse(None, phone_number.into()) {          phonenumber::is_valid(&parsed)      } else {          false @@ -11,7 +14,10 @@ pub fn validate_phone(phone_number: &str) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::validate_phone; +      #[test]      fn test_phone() {          let tests = vec![ @@ -30,4 +36,16 @@ mod tests {              assert_eq!(validate_phone(input), expected);          }      } + +    #[test] +    fn test_phone_cow() { +        let test: Cow<'static, str> = "+1 (415) 237-0800".into(); +        assert_eq!(validate_phone(test), true); +        let test: Cow<'static, str> = String::from("+1 (415) 237-0800").into(); +        assert_eq!(validate_phone(test), true); +        let test: Cow<'static, str> = "TEXT".into(); +        assert_eq!(validate_phone(test), false); +        let test: Cow<'static, str> = String::from("TEXT").into(); +        assert_eq!(validate_phone(test), false); +    }  } diff --git a/validator/src/validation/urls.rs b/validator/src/validation/urls.rs index d948050..189ce8f 100644 --- a/validator/src/validation/urls.rs +++ b/validator/src/validation/urls.rs @@ -1,9 +1,12 @@ +use std::borrow::Cow;  use url::Url;  /// Validates whether the string given is a url -pub fn validate_url(val: &str) -> bool { -    match Url::parse(val) { +pub fn validate_url<'a, T>(val: T) -> bool +    where T: Into<Cow<'a, str>> +{ +    match Url::parse(val.into().as_ref()) {          Ok(_) => true,          Err(_) => false,      } @@ -11,6 +14,8 @@ pub fn validate_url(val: &str) -> bool {  #[cfg(test)]  mod tests { +    use std::borrow::Cow; +      use super::{validate_url}; @@ -27,4 +32,16 @@ mod tests {              assert_eq!(validate_url(url), expected);          }      } + +    #[test] +    fn test_validate_url_cow() { +        let test: Cow<'static, str> = "http://localhost:80".into(); +        assert_eq!(validate_url(test), true); +        let test: Cow<'static, str> = String::from("http://localhost:80").into(); +        assert_eq!(validate_url(test), true); +        let test: Cow<'static, str> = "http".into(); +        assert_eq!(validate_url(test), false); +        let test: Cow<'static, str> = String::from("http").into(); +        assert_eq!(validate_url(test), false); +    }  } diff --git a/validator_derive/Cargo.toml b/validator_derive/Cargo.toml index 3e80bbc..30c2d50 100644 --- a/validator_derive/Cargo.toml +++ b/validator_derive/Cargo.toml @@ -21,13 +21,13 @@ quote = "0.6"  proc-macro2 = "0.4"  if_chain = "0"  validator = { version = "0.7", path = "../validator"} +regex = "1" +lazy_static = "1"  [dev-dependencies]  serde = "1.0"  serde_derive = "1.0"  serde_json = "1.0"  compiletest_rs = "0.3" -regex = "1" -lazy_static = "1" diff --git a/validator_derive/src/asserts.rs b/validator_derive/src/asserts.rs index 53d76ae..cdd0028 100644 --- a/validator_derive/src/asserts.rs +++ b/validator_derive/src/asserts.rs @@ -1,3 +1,8 @@ +use regex::Regex; + +lazy_static! { +    pub static ref COW_TYPE: Regex = Regex::new(r"Cow<'[a-z]+,str>").unwrap(); +}  pub static NUMBER_TYPES: [&'static str; 36] = [      "usize", "u8", "u16", "u32", "u64", @@ -17,11 +22,12 @@ pub static NUMBER_TYPES: [&'static str; 36] = [  pub fn assert_string_type(name: &str, field_type: &String) {      if field_type != "String"          && field_type != "&str" +        && !COW_TYPE.is_match(field_type)          && field_type != "Option<String>"          && field_type != "Option<Option<String>>"          && !(field_type.starts_with("Option<") && field_type.ends_with("str>"))          && !(field_type.starts_with("Option<Option<") && field_type.ends_with("str>>")) { -        panic!("`{}` validator can only be used on String, &str or an Option of those", name); +        panic!("`{}` validator can only be used on String, &str, Cow<'_,str> or an Option of those", name);      }  } @@ -45,9 +51,10 @@ pub fn assert_has_len(field_name: String, field_type: &String) {          // a bit ugly          && !(field_type.starts_with("Option<") && field_type.ends_with("str>"))          && !(field_type.starts_with("Option<Option<") && field_type.ends_with("str>>")) +        && !COW_TYPE.is_match(field_type)          && field_type != "&str" {              panic!( -                "Validator `length` can only be used on types `String`, `&str` or `Vec` but found `{}` for field `{}`", +                "Validator `length` can only be used on types `String`, `&str`, Cow<'_,str> or `Vec` but found `{}` for field `{}`",                  field_type, field_name              );      } diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs index a0a1c82..b5a76c1 100644 --- a/validator_derive/src/lib.rs +++ b/validator_derive/src/lib.rs @@ -1,20 +1,21 @@  #![recursion_limit = "128"]  #[macro_use] -extern crate quote; +extern crate if_chain; +#[macro_use] +extern crate lazy_static;  extern crate proc_macro;  extern crate proc_macro2;  #[macro_use] -extern crate syn; +extern crate quote; +extern crate regex;  #[macro_use] -extern crate if_chain; +extern crate syn;  extern crate validator; -use std::collections::HashMap; -  use proc_macro::TokenStream;  use quote::ToTokens; - +use std::collections::HashMap;  use validator::Validator; diff --git a/validator_derive/src/quoting.rs b/validator_derive/src/quoting.rs index 272226e..20ca7a7 100644 --- a/validator_derive/src/quoting.rs +++ b/validator_derive/src/quoting.rs @@ -4,7 +4,7 @@ use proc_macro2::{self, Span};  use lit::option_u64_to_tokens;  use validation::{FieldValidation, SchemaValidation}; -use asserts::NUMBER_TYPES; +use asserts::{COW_TYPE, NUMBER_TYPES};  /// Pass around all the information needed for creating a validation @@ -31,6 +31,8 @@ impl FieldQuoter {          if self._type.starts_with("Option<") {              quote!(#ident) +        } else if COW_TYPE.is_match(&self._type.as_ref()) { +            quote!(self.#ident.as_ref())          } else if self._type.starts_with("&") || NUMBER_TYPES.contains(&self._type.as_ref()) {              quote!(self.#ident)          } else { diff --git a/validator_derive/tests/compile-fail/length/wrong_type.rs b/validator_derive/tests/compile-fail/length/wrong_type.rs index a9d53d4..3dd1fb0 100644 --- a/validator_derive/tests/compile-fail/length/wrong_type.rs +++ b/validator_derive/tests/compile-fail/length/wrong_type.rs @@ -4,7 +4,7 @@ use validator::Validate;  #[derive(Validate)]  //~^ ERROR: proc-macro derive panicked -//~^^ HELP: Validator `length` can only be used on types `String`, `&str` or `Vec` but found `usize` +//~^^ HELP: Validator `length` can only be used on types `String`, `&str`, Cow<'_,str> or `Vec` but found `usize`  struct Test {      #[validate(length())]      s: usize, | 
