diff options
| -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, |
