diff options
| author | Vincent Prouillet | 2016-12-29 13:13:42 +0900 | 
|---|---|---|
| committer | Vincent Prouillet | 2016-12-29 13:16:41 +0900 | 
| commit | a0f2b4c0d4820bfdcc13c7abdfdc7802d9f6d493 (patch) | |
| tree | 75b5bc7b008ac374ac0a437de580d5eda1c67a52 | |
| parent | ce9ee1ce4ac73ba7989c88c67b16243cbcec2b98 (diff) | |
| download | validator-a0f2b4c0d4820bfdcc13c7abdfdc7802d9f6d493.tar.bz2 | |
Added must_match
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | validator/src/lib.rs | 2 | ||||
| -rw-r--r-- | validator/src/must_match.rs | 26 | ||||
| -rw-r--r-- | validator/src/types.rs | 2 | ||||
| -rw-r--r-- | validator_derive/src/lib.rs | 98 | ||||
| -rw-r--r-- | validator_derive/tests/compile-fail/must_match/field_doesnt_exist.rs | 15 | ||||
| -rw-r--r-- | validator_derive/tests/compile-fail/must_match/field_type_doesnt_match.rs | 16 | ||||
| -rw-r--r-- | validator_derive/tests/run-pass/must_match.rs | 18 | ||||
| -rw-r--r-- | validator_derive/tests/test_derive.rs | 27 | 
9 files changed, 189 insertions, 25 deletions
| @@ -103,6 +103,16 @@ Examples:  #[validate(range(min = 1.1, max = 10.8))]  ``` +### must_match +Tests whether the 2 fields are equal. `must_match` takes 1 string argument. It will error if the field +mentioned is missing or has a different type than the field the attribute is on. + +Examples: + +```rust +#[validate(must_match = "password2"))] +``` +  ### custom  Calls one of your function to do a custom validation.   The field will be given as parameter and it should return a `Option<String>` representing the error code, diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 181fc54..3c52661 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -10,6 +10,7 @@ mod email;  mod length;  mod range;  mod urls; +mod must_match;  pub use types::{Errors, Validate, Validator}; @@ -18,3 +19,4 @@ pub use email::{validate_email};  pub use length::{HasLen, validate_length};  pub use range::{validate_range};  pub use urls::{validate_url}; +pub use must_match::{validate_must_match}; diff --git a/validator/src/must_match.rs b/validator/src/must_match.rs new file mode 100644 index 0000000..35fbee6 --- /dev/null +++ b/validator/src/must_match.rs @@ -0,0 +1,26 @@ +/// Validates that the 2 given fields match. +/// Both fields are optionals +pub fn validate_must_match<T: Eq>(a: T, b: T) -> bool { +    a == b +} + +#[cfg(test)] +mod tests { +    use super::{validate_must_match}; + +    #[test] +    fn test_validate_must_match_strings_valid() { +        assert!(validate_must_match("hey".to_string(), "hey".to_string())) +    } + +    #[test] +    fn test_validate_must_match_numbers() { +        assert!(validate_must_match(2, 2)) +    } + +    #[test] +    fn test_validate_must_match_numbers_false() { +        assert_eq!(false, validate_must_match(2, 3)); +    } + +} diff --git a/validator/src/types.rs b/validator/src/types.rs index 8265c63..67e8245 100644 --- a/validator/src/types.rs +++ b/validator/src/types.rs @@ -12,6 +12,8 @@ pub trait Validate {  pub enum Validator {      // String is the path to the function      Custom(String), +    // String is the name of the field to match +    MustMatch(String),      // value is a &str      Email,      // value is a &str diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs index 0e580b0..cdc244d 100644 --- a/validator_derive/src/lib.rs +++ b/validator_derive/src/lib.rs @@ -1,18 +1,19 @@  #![feature(proc_macro, proc_macro_lib)]  #![recursion_limit = "128"] +  #[macro_use] extern crate quote;  extern crate proc_macro;  extern crate syn;  extern crate validator; +use std::collections::HashMap;  use proc_macro::TokenStream;  use quote::ToTokens;  use validator::{Validator}; -static LENGTH_TYPES: [&'static str; 2] = ["String", "Vec"];  static RANGE_TYPES: [&'static str; 12] = [      "usize", "u8", "u16", "u32", "u64", "isize", "i8", "i16", "i32", "i64", "f32", "f64"  ]; @@ -42,13 +43,15 @@ fn expand_validation(ast: &syn::MacroInput) -> quote::Tokens {      let mut validations = vec![]; +    let field_types = find_fields_type(&fields); +      for field in fields {          let field_ident = match field.ident {              Some(ref i) => i,              None => unreachable!()          }; -        let (name, validators) = find_validators_for_field(field); +        let (name, validators) = find_validators_for_field(field, &field_types);          for validator in &validators {              validations.push(match validator {                  &Validator::Length {min, max, equal} =>  { @@ -93,6 +96,14 @@ fn expand_validation(ast: &syn::MacroInput) -> quote::Tokens {                          }                      )                  }, +                &Validator::MustMatch(ref f) => { +                    let other_ident = syn::Ident::new(f.clone()); +                    quote!( +                        if !::validator::validate_must_match(&self.#field_ident, &self.#other_ident) { +                            errors.entry(#name.to_string()).or_insert_with(|| vec![]).push("no_match".to_string()); +                        } +                    ) +                },                  &Validator::Custom(ref f) => {                      let fn_ident = syn::Ident::new(f.clone());                      quote!( @@ -127,8 +138,34 @@ fn expand_validation(ast: &syn::MacroInput) -> quote::Tokens {      impl_ast  } +// Find all the types (as string) for each field of the struct +// Needed for the `must_match` filter +fn find_fields_type(fields: &Vec<syn::Field>) -> HashMap<String, String> { +    let mut types = HashMap::new(); + +    for field in fields { +        let field_name = match field.ident { +            Some(ref s) => s.to_string(), +            None => unreachable!(), +        }; + +        let field_type = match field.ty { +            syn::Ty::Path(_, ref p) => { +                let mut tokens = quote::Tokens::new(); +                p.to_tokens(&mut tokens); +                tokens.to_string().replace(' ', "") + +            }, +            _ => panic!("Type `{:?}` of field `{}` not supported", field.ty, field_name) +        }; +        types.insert(field_name, field_type); +    } + +    types +} +  /// Find everything we need to know about a Field. -fn find_validators_for_field(field: &syn::Field) -> (String, Vec<Validator>) { +fn find_validators_for_field(field: &syn::Field, field_types: &HashMap<String, String>) -> (String, Vec<Validator>) {      let mut field_name = match field.ident {          Some(ref s) => s.to_string(),          None => unreachable!(), @@ -137,16 +174,10 @@ fn find_validators_for_field(field: &syn::Field) -> (String, Vec<Validator>) {      let error = |msg: &str| -> ! {          panic!("Invalid attribute #[validate] on field `{}`: {}", field.ident.clone().unwrap().to_string(), msg);      }; - -    let field_type = match field.ty { -        syn::Ty::Path(_, ref p) => { -            p.segments[0].ident.to_string() - -        }, -        _ => error(&format!("Type `{:?}` not supported", field.ty)) -    }; +    let field_type = field_types.get(&field_name).unwrap();      let mut validators = vec![]; +    let mut has_validate = false;      let find_struct_validator = |name: String, meta_items: &Vec<syn::NestedMetaItem>| -> Validator {          match name.as_ref() { @@ -237,6 +268,10 @@ fn find_validators_for_field(field: &syn::Field) -> (String, Vec<Validator>) {              continue;          } +        if attr.name() == "validate" { +            has_validate = true; +        } +          match attr.value {              syn::MetaItem::List(_, ref meta_items) => {                  if attr.name() == "serde" { @@ -269,20 +304,37 @@ fn find_validators_for_field(field: &syn::Field) -> (String, Vec<Validator>) {                              },                              // custom                              syn::MetaItem::NameValue(ref name, ref val) => { -                                if name == "custom" { -                                    match lit_to_string(val) { -                                        Some(s) => validators.push(Validator::Custom(s)), -                                        None => error("invalid argument for `custom` validator: only strings are allowed"), -                                    }; -                                } else { -                                    panic!("unexpected name value validator: {:?}", name); -                                } +                                match name.to_string().as_ref() { +                                    "custom" => { +                                        match lit_to_string(val) { +                                            Some(s) => validators.push(Validator::Custom(s)), +                                            None => error("invalid argument for `custom` validator: only strings are allowed"), +                                        }; +                                    }, +                                    "must_match" => { +                                        match lit_to_string(val) { +                                            Some(s) => { +                                                if let Some(t2) = field_types.get(&s) { +                                                    if field_type == t2 { +                                                        validators.push(Validator::MustMatch(s)); +                                                    } else { +                                                        error("invalid argument for `must_match` validator: types of field can't match"); +                                                    } +                                                } else { +                                                    error("invalid argument for `must_match` validator: field doesn't exist in struct"); +                                                } +                                            }, +                                            None => error("invalid argument for `must_match` validator: only strings are allowed"), +                                        }; +                                    }, +                                    _ => panic!("unexpected name value validator: {:?}", name), +                                };                              },                              // validators with args: length for example                              syn::MetaItem::List(ref name, ref meta_items) => {                                  // Some sanity checking first                                  if name == "length" { -                                    if !LENGTH_TYPES.contains(&field_type.as_ref()) { +                                    if field_type != "String" && !field_type.starts_with("Vec<") {                                          error(&format!(                                              "Validator `length` can only be used on types `String` or `Vec` but found `{}`",                                              field_type @@ -318,7 +370,7 @@ fn find_validators_for_field(field: &syn::Field) -> (String, Vec<Validator>) {          }      } -    if validators.is_empty() { +    if has_validate && validators.is_empty() {          error("it needs at least one validator");      } @@ -359,10 +411,6 @@ fn find_original_field_name(meta_items: &Vec<syn::NestedMetaItem>) -> Option<Str      original_name  } -// -//fn quote_length_validator(min: Option<u64>, max: Option<u64>, equal: Option<u64>) { -// -//}  fn lit_to_string(lit: &syn::Lit) -> Option<String> {      match *lit { diff --git a/validator_derive/tests/compile-fail/must_match/field_doesnt_exist.rs b/validator_derive/tests/compile-fail/must_match/field_doesnt_exist.rs new file mode 100644 index 0000000..03828e2 --- /dev/null +++ b/validator_derive/tests/compile-fail/must_match/field_doesnt_exist.rs @@ -0,0 +1,15 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +//~^ ERROR: custom derive attribute panicked +//~^^ HELP: Invalid attribute #[validate] on field `password`: invalid argument for `must_match` validator: field doesn't exist in struct +struct Test { +    #[validate(must_match = "password2")] +    password: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/must_match/field_type_doesnt_match.rs b/validator_derive/tests/compile-fail/must_match/field_type_doesnt_match.rs new file mode 100644 index 0000000..61c0847 --- /dev/null +++ b/validator_derive/tests/compile-fail/must_match/field_type_doesnt_match.rs @@ -0,0 +1,16 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +//~^ ERROR: custom derive attribute panicked +//~^^ HELP: Invalid attribute #[validate] on field `password`: invalid argument for `must_match` validator: types of field can't match +struct Test { +    #[validate(must_match = "password2")] +    password: String, +    password2: i32, +} + +fn main() {} diff --git a/validator_derive/tests/run-pass/must_match.rs b/validator_derive/tests/run-pass/must_match.rs new file mode 100644 index 0000000..0d2d917 --- /dev/null +++ b/validator_derive/tests/run-pass/must_match.rs @@ -0,0 +1,18 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +struct Test { +    #[validate(must_match = "s2")] +    s: String, +    s2: String, + +    #[validate(must_match = "s4")] +    s3: usize, +    s4: usize, +} + +fn main() {} diff --git a/validator_derive/tests/test_derive.rs b/validator_derive/tests/test_derive.rs index 1e310b1..7cc63eb 100644 --- a/validator_derive/tests/test_derive.rs +++ b/validator_derive/tests/test_derive.rs @@ -21,6 +21,14 @@ struct SignupData {      age: u32,  } +#[derive(Debug, Validate)] +struct PasswordData { +    #[validate(must_match = "password2")] +    password: String, +    password2: String, +} + +  fn validate_unique_username(username: &str) -> Option<String> {      if username == "xXxShad0wxXx" {          return Some("terrible_username".to_string()); @@ -134,3 +142,22 @@ fn test_custom_validation_error() {      assert!(errs.contains_key("firstName"));      assert_eq!(errs["firstName"], vec!["terrible_username".to_string()]);  } + +#[test] +fn test_must_match_can_work() { +    let data = PasswordData { +        password: "passw0rd".to_string(), +        password2: "passw0rd".to_string(), +    }; +    assert!(data.validate().is_ok()) +} + + +#[test] +fn test_must_match_can_fail() { +    let data = PasswordData { +        password: "passw0rd".to_string(), +        password2: "password".to_string(), +    }; +    assert!(data.validate().is_err()) +} | 
