aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Sparks2018-07-27 10:02:04 +0100
committerVincent Prouillet2018-07-27 11:02:04 +0200
commitf4b11eaecb8fcc4798eb3a932a68c47ed802d96d (patch)
tree2ba683f8512308bfd10f3293f304bbee975ee8df
parent889e7e7e7fad2a2156a46d8382558c6cfbf59628 (diff)
downloadvalidator-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.rs13
-rw-r--r--validator/src/validation/cards.rs23
-rw-r--r--validator/src/validation/contains.rs17
-rw-r--r--validator/src/validation/email.rs22
-rw-r--r--validator/src/validation/ip.rs59
-rw-r--r--validator/src/validation/length.rs12
-rw-r--r--validator/src/validation/must_match.rs10
-rw-r--r--validator/src/validation/phone.rs22
-rw-r--r--validator/src/validation/urls.rs21
-rw-r--r--validator_derive/Cargo.toml4
-rw-r--r--validator_derive/src/asserts.rs11
-rw-r--r--validator_derive/src/lib.rs13
-rw-r--r--validator_derive/src/quoting.rs4
-rw-r--r--validator_derive/tests/compile-fail/length/wrong_type.rs2
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,