diff options
| author | Vincent Prouillet | 2016-12-27 14:31:17 +0900 |
|---|---|---|
| committer | Vincent Prouillet | 2016-12-28 11:49:43 +0900 |
| commit | c5c76fac8726d53988092d71c818d38f12e8348e (patch) | |
| tree | fffc8d3c5f369da2b935d118a2118d2fcca905a5 /validator_derive | |
| download | validator-c5c76fac8726d53988092d71c818d38f12e8348e.tar.bz2 | |
Initial commit
Diffstat (limited to 'validator_derive')
21 files changed, 873 insertions, 0 deletions
diff --git a/validator_derive/Cargo.toml b/validator_derive/Cargo.toml new file mode 100644 index 0000000..76c5e7e --- /dev/null +++ b/validator_derive/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "validator_derive" +version = "0.1.0" +authors = ["Vincent Prouillet <vincent@wearewizards.io>"] +license = "MIT" +description = "Macros 1.1 implementation of #[derive(Validate)]" +homepage = "https://github.com/Keats/validator" +repository = "https://github.com/Keats/validator" +keywords = ["validation", "api", "validator"] + +[lib] +proc-macro = true + +[dependencies] +syn = "0.10" +quote = "0.3" + +[dev-dependencies] +serde = "0.8" +serde_derive = "0.8" +serde_json = "0.8" +compiletest_rs = "*" + +[dependencies.validator] +path = "../validator" diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs new file mode 100644 index 0000000..0e580b0 --- /dev/null +++ b/validator_derive/src/lib.rs @@ -0,0 +1,410 @@ +#![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 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" +]; + + +#[proc_macro_derive(Validate, attributes(validate))] +pub fn derive_validation(input: TokenStream) -> TokenStream { + let source = input.to_string(); + // Parse the string representation to an AST + let ast = syn::parse_macro_input(&source).unwrap(); + + let expanded = expand_validation(&ast); + expanded.parse().unwrap() +} + + +fn expand_validation(ast: &syn::MacroInput) -> quote::Tokens { + let fields = match ast.body { + syn::Body::Struct(syn::VariantData::Struct(ref fields)) => { + if fields.iter().any(|field| field.ident.is_none()) { + panic!("struct has unnamed fields"); + } + fields + }, + _ => panic!("#[derive(Validate)] can only be used with structs"), + }; + + let mut validations = vec![]; + + for field in fields { + let field_ident = match field.ident { + Some(ref i) => i, + None => unreachable!() + }; + + let (name, validators) = find_validators_for_field(field); + for validator in &validators { + validations.push(match validator { + &Validator::Length {min, max, equal} => { + // Can't interpolate None + let min_tokens = option_u64_to_tokens(min); + let max_tokens = option_u64_to_tokens(max); + let equal_tokens = option_u64_to_tokens(equal); + quote!( + if !::validator::validate_length( + ::validator::Validator::Length { + min: #min_tokens, + max: #max_tokens, + equal: #equal_tokens + }, + &self.#field_ident + ) { + errors.entry(#name.to_string()).or_insert_with(|| vec![]).push("length".to_string()); + } + ) + }, + &Validator::Range {min, max} => { + quote!( + if !::validator::validate_range( + ::validator::Validator::Range {min: #min, max: #max}, + self.#field_ident as f64 + ) { + errors.entry(#name.to_string()).or_insert_with(|| vec![]).push("range".to_string()); + } + ) + }, + &Validator::Email => { + quote!( + if !::validator::validate_email(&self.#field_ident) { + errors.entry(#name.to_string()).or_insert_with(|| vec![]).push("email".to_string()); + } + ) + } + &Validator::Url => { + quote!( + if !::validator::validate_url(&self.#field_ident) { + errors.entry(#name.to_string()).or_insert_with(|| vec![]).push("url".to_string()); + } + ) + }, + &Validator::Custom(ref f) => { + let fn_ident = syn::Ident::new(f.clone()); + quote!( + match #fn_ident(&self.#field_ident) { + ::std::option::Option::Some(s) => errors.entry(#name.to_string()).or_insert_with(|| vec![]).push(s), + ::std::option::Option::None => (), + }; + ) + }, + }); + } + } + + let ident = &ast.ident; + let impl_ast = quote!( + impl Validate for #ident { + fn validate(&self) -> ::std::result::Result<(), ::validator::Errors> { + use std::collections::HashMap; + let mut errors = HashMap::new(); + + #(#validations)* + + if errors.is_empty() { + ::std::result::Result::Ok(()) + } else { + ::std::result::Result::Err(errors) + } + } + } + ); + // println!("{}", impl_ast.to_string()); + impl_ast +} + +/// Find everything we need to know about a Field. +fn find_validators_for_field(field: &syn::Field) -> (String, Vec<Validator>) { + let mut field_name = match field.ident { + Some(ref s) => s.to_string(), + None => unreachable!(), + }; + + 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 mut validators = vec![]; + + let find_struct_validator = |name: String, meta_items: &Vec<syn::NestedMetaItem>| -> Validator { + match name.as_ref() { + "length" => { + let mut min = None; + let mut max = None; + let mut equal = None; + + for meta_item in meta_items { + match *meta_item { + syn::NestedMetaItem::MetaItem(ref item) => match *item { + syn::MetaItem::NameValue(ref name, ref val) => { + match name.to_string().as_ref() { + "min" => { + min = match lit_to_int(val) { + Some(s) => Some(s), + None => error("invalid argument type for `min` of `length` validator: only integers are allowed"), + }; + }, + "max" => { + max = match lit_to_int(val) { + Some(s) => Some(s), + None => error("invalid argument type for `max` of `length` validator: only integers are allowed"), + }; + }, + "equal" => { + equal = match lit_to_int(val) { + Some(s) => Some(s), + None => error("invalid argument type for `equal` of `length` validator: only integers are allowed"), + }; + }, + _ => error(&format!( + "unknown argument `{}` for validator `length` (it only has `min`, `max`, `equal`)", + name.to_string() + )) + } + }, + _ => panic!("unexpected item {:?} while parsing `length` validator", item) + }, + _=> unreachable!() + } + } + if equal.is_some() && (min.is_some() || max.is_some()) { + error("both `equal` and `min` or `max` have been set in `length` validator: probably a mistake"); + } + Validator::Length { min: min, max: max, equal: equal } + }, + "range" => { + let mut min = 0.0; + let mut max = 0.0; + for meta_item in meta_items { + match *meta_item { + syn::NestedMetaItem::MetaItem(ref item) => match *item { + syn::MetaItem::NameValue(ref name, ref val) => { + match name.to_string().as_ref() { + "min" => { + min = match lit_to_float(val) { + Some(s) => s, + None => error("invalid argument type for `min` of `range` validator: only integers are allowed") + }; + }, + "max" => { + max = match lit_to_float(val) { + Some(s) => s, + None => error("invalid argument type for `max` of `range` validator: only integers are allowed") + }; + }, + _ => error(&format!( + "unknown argument `{}` for validator `range` (it only has `min`, `max`)", + name.to_string() + )) + } + }, + _ => panic!("unexpected item {:?} while parsing `range` validator", item) + }, + _=> unreachable!() + } + } + + Validator::Range { min: min, max: max} + } + _ => panic!("unexpected list validator: {:?}", name) + } + }; + + for attr in &field.attrs { + if attr.name() != "validate" && attr.name() != "serde" { + continue; + } + + match attr.value { + syn::MetaItem::List(_, ref meta_items) => { + if attr.name() == "serde" { + match find_original_field_name(meta_items) { + Some(s) => { field_name = s }, + None => () + }; + continue; + } + + // only validation from there on + for meta_item in meta_items { + match *meta_item { + syn::NestedMetaItem::MetaItem(ref item) => match *item { + // email, url + syn::MetaItem::Word(ref name) => match name.to_string().as_ref() { + "email" => { + if field_type != "String" { + panic!("`email` validator can only be used on String"); + } + validators.push(Validator::Email); + }, + "url" => { + if field_type != "String" { + panic!("`url` validator can only be used on String"); + } + validators.push(Validator::Url); + }, + _ => panic!("Unexpected word validator: {}", name) + }, + // 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); + } + }, + // 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()) { + error(&format!( + "Validator `length` can only be used on types `String` or `Vec` but found `{}`", + field_type + )); + } + + if meta_items.len() == 0 { + error("Validator `length` requires at least 1 argument out of `min`, `max` and `equal`"); + } + } + + if name == "range" { + if !RANGE_TYPES.contains(&field_type.as_ref()) { + error(&format!( + "Validator `range` can only be used on number types but found `{}`", + field_type + )); + } + + if meta_items.len() != 2 { + error("Validator `range` requires 2 arguments: `min` and `max`"); + } + } + + validators.push(find_struct_validator(name.to_string(), meta_items)); + }, + }, + _ => unreachable!("Found a non MetaItem while looking for validators") + }; + } + }, + _ => unreachable!("Got something other than a list of attributes while checking field `{}`", field_name), + } + } + + if validators.is_empty() { + error("it needs at least one validator"); + } + + (field_name, validators) +} + +/// Serde can be used to rename fields on deserialization but most of the times +/// we want the error on the original field. +/// +/// For example a JS frontend might send camelCase fields and Rust converts them to snake_case +/// but we want to send the errors back to the frontend with the original name +fn find_original_field_name(meta_items: &Vec<syn::NestedMetaItem>) -> Option<String> { + let mut original_name = None; + + for meta_item in meta_items { + match *meta_item { + syn::NestedMetaItem::MetaItem(ref item) => match *item { + syn::MetaItem::Word(_) => continue, + syn::MetaItem::NameValue(ref name, ref val) => { + if name == "rename" { + original_name = Some(lit_to_string(val).unwrap()); + } + + }, + // length + syn::MetaItem::List(_, ref meta_items) => { + return find_original_field_name(meta_items); + } + }, + _ => unreachable!() + }; + + if original_name.is_some() { + return original_name; + } + } + + 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 { + syn::Lit::Str(ref s, _) => Some(s.to_string()), + _ => None, + } +} + +fn lit_to_int(lit: &syn::Lit) -> Option<u64> { + match *lit { + syn::Lit::Int(ref s, _) => Some(*s), + _ => None, + } +} + +fn lit_to_float(lit: &syn::Lit) -> Option<f64> { + match *lit { + syn::Lit::Float(ref s, _) => Some(s.parse::<f64>().unwrap()), + syn::Lit::Int(ref s, _) => Some(*s as f64), + _ => None, + } +} + +fn option_u64_to_tokens(opt: Option<u64>) -> quote::Tokens { + let mut tokens = quote::Tokens::new(); + tokens.append("::"); + tokens.append("std"); + tokens.append("::"); + tokens.append("option"); + tokens.append("::"); + tokens.append("Option"); + tokens.append("::"); + match opt { + Some(ref t) => { + tokens.append("Some"); + tokens.append("("); + t.to_tokens(&mut tokens); + tokens.append(")"); + } + None => { + tokens.append("None"); + } + } + tokens +} diff --git a/validator_derive/tests/compile-fail/custom_not_string.rs b/validator_derive/tests/compile-fail/custom_not_string.rs new file mode 100644 index 0000000..e576c5c --- /dev/null +++ b/validator_derive/tests/compile-fail/custom_not_string.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 `s`: invalid argument for `custom` validator: only strings are allowed +struct Test { + #[validate(custom = 2)] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/length/equal_and_min_max_set.rs b/validator_derive/tests/compile-fail/length/equal_and_min_max_set.rs new file mode 100644 index 0000000..cc6654c --- /dev/null +++ b/validator_derive/tests/compile-fail/length/equal_and_min_max_set.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 `s`: both `equal` and `min` or `max` have been set in `length` validator: probably a mistake +struct Test { + #[validate(length(min = 1, equal = 2))] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/length/no_args.rs b/validator_derive/tests/compile-fail/length/no_args.rs new file mode 100644 index 0000000..35a44df --- /dev/null +++ b/validator_derive/tests/compile-fail/length/no_args.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 `s`: Validator `length` requires at least 1 argument out of `min`, `max` and `equal` +struct Test { + #[validate(length())] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/length/unknown_arg.rs b/validator_derive/tests/compile-fail/length/unknown_arg.rs new file mode 100644 index 0000000..d8e6185 --- /dev/null +++ b/validator_derive/tests/compile-fail/length/unknown_arg.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 `s`: unknown argument `eq` for validator `length` (it only has `min`, `max`, `equal`) +struct Test { + #[validate(length(eq = 2))] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/length/wrong_arg_type.rs b/validator_derive/tests/compile-fail/length/wrong_arg_type.rs new file mode 100644 index 0000000..4d3f602 --- /dev/null +++ b/validator_derive/tests/compile-fail/length/wrong_arg_type.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 `s`: invalid argument type for `min` of `length` validator: only integers are allowed +struct Test { + #[validate(length(min = "2"))] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/length/wrong_type.rs b/validator_derive/tests/compile-fail/length/wrong_type.rs new file mode 100644 index 0000000..e8a28ca --- /dev/null +++ b/validator_derive/tests/compile-fail/length/wrong_type.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 `s`: Validator `length` can only be used on types `String` or `Vec` but found `usize` +struct Test { + #[validate(length())] + s: usize, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/no_validations.rs b/validator_derive/tests/compile-fail/no_validations.rs new file mode 100644 index 0000000..0a65bdd --- /dev/null +++ b/validator_derive/tests/compile-fail/no_validations.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 `s`: it needs at least one validator +struct Test { + #[validate()] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/range/missing_arg.rs b/validator_derive/tests/compile-fail/range/missing_arg.rs new file mode 100644 index 0000000..94913cf --- /dev/null +++ b/validator_derive/tests/compile-fail/range/missing_arg.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 `s`: Validator `range` requires 2 arguments: `min` and `max` +struct Test { + #[validate(range(min = 2.0))] + s: i32, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/range/no_args.rs b/validator_derive/tests/compile-fail/range/no_args.rs new file mode 100644 index 0000000..0532cc0 --- /dev/null +++ b/validator_derive/tests/compile-fail/range/no_args.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 `s`: Validator `range` requires 2 arguments: `min` and `max` +struct Test { + #[validate(range())] + s: i32, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/range/unknown_arg.rs b/validator_derive/tests/compile-fail/range/unknown_arg.rs new file mode 100644 index 0000000..7d2ac57 --- /dev/null +++ b/validator_derive/tests/compile-fail/range/unknown_arg.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 `s`: unknown argument `mi` for validator `range` (it only has `min`, `max`) +struct Test { + #[validate(range(mi = 2, max = 3))] + s: i32, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/range/wrong_arg_type.rs b/validator_derive/tests/compile-fail/range/wrong_arg_type.rs new file mode 100644 index 0000000..a62a5ff --- /dev/null +++ b/validator_derive/tests/compile-fail/range/wrong_arg_type.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 `s`: invalid argument type for `min` of `range` validator: only integers are allowed +struct Test { + #[validate(range(min = "2", max = 3))] + s: i32, +} + +fn main() {} diff --git a/validator_derive/tests/compile-fail/range/wrong_type.rs b/validator_derive/tests/compile-fail/range/wrong_type.rs new file mode 100644 index 0000000..e341364 --- /dev/null +++ b/validator_derive/tests/compile-fail/range/wrong_type.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 `s`: Validator `range` can only be used on number types but found `String` +struct Test { + #[validate(range(min = 10.0, max = 12.0))] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/compile_test.rs b/validator_derive/tests/compile_test.rs new file mode 100644 index 0000000..b6fe8e5 --- /dev/null +++ b/validator_derive/tests/compile_test.rs @@ -0,0 +1,24 @@ +extern crate compiletest_rs as compiletest; + +use std::path::PathBuf; + +fn run_mode(mode: &'static str) { + let mut config = compiletest::default_config(); + let cfg_mode = mode.parse().expect("Invalid mode"); + + config.target_rustcflags = Some("-L target/debug/ -L target/debug/deps/".to_string()); + config.mode = cfg_mode; + config.src_base = PathBuf::from(format!("tests/{}", mode)); + + compiletest::run_tests(&config); +} + +#[test] +fn test_compile_fail() { + run_mode("compile-fail"); +} + +#[test] +fn test_run_pass() { + run_mode("run-pass"); +} diff --git a/validator_derive/tests/run-pass/custom.rs b/validator_derive/tests/run-pass/custom.rs new file mode 100644 index 0000000..205198e --- /dev/null +++ b/validator_derive/tests/run-pass/custom.rs @@ -0,0 +1,17 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +struct Test { + #[validate(custom = "validate_something")] + s: String, +} + +fn validate_something(s: &str) -> Option<String> { + Some(s.to_string()) +} + +fn main() {} diff --git a/validator_derive/tests/run-pass/email.rs b/validator_derive/tests/run-pass/email.rs new file mode 100644 index 0000000..edfc357 --- /dev/null +++ b/validator_derive/tests/run-pass/email.rs @@ -0,0 +1,13 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +struct Test { + #[validate(email)] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/run-pass/length.rs b/validator_derive/tests/run-pass/length.rs new file mode 100644 index 0000000..01b85ea --- /dev/null +++ b/validator_derive/tests/run-pass/length.rs @@ -0,0 +1,28 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +struct Test { + #[validate(length(min = 1))] + s: String, + #[validate(length(min = 1, max = 2))] + s2: String, + #[validate(length(equal = 1))] + s3: String, + #[validate(length(max = 1))] + s4: String, + + #[validate(length(min = 1))] + s5: Vec<String>, + #[validate(length(min = 1, max = 2))] + s6: Vec<String>, + #[validate(length(equal = 1))] + s7: Vec<String>, + #[validate(length(max = 1))] + s8: Vec<String>, +} + +fn main() {} diff --git a/validator_derive/tests/run-pass/range.rs b/validator_derive/tests/run-pass/range.rs new file mode 100644 index 0000000..8f3a047 --- /dev/null +++ b/validator_derive/tests/run-pass/range.rs @@ -0,0 +1,27 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +struct Test { + #[validate(range(min = 1, max = 2.2))] + s: isize, + #[validate(range(min = 1, max = 2))] + s2: usize, + #[validate(range(min = 18, max = 22))] + s3: i32, + #[validate(range(min = 18, max = 22))] + s4: i64, + #[validate(range(min = 18, max = 22))] + s5: u32, + #[validate(range(min = 18, max = 22))] + s6: u64, + #[validate(range(min = 18.1, max = 22))] + s7: i8, + #[validate(range(min = 18.0, max = 22))] + s8: u8, +} + +fn main() {} diff --git a/validator_derive/tests/run-pass/url.rs b/validator_derive/tests/run-pass/url.rs new file mode 100644 index 0000000..910676a --- /dev/null +++ b/validator_derive/tests/run-pass/url.rs @@ -0,0 +1,13 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +use validator::Validate; + +#[derive(Validate)] +struct Test { + #[validate(url)] + s: String, +} + +fn main() {} diff --git a/validator_derive/tests/test_derive.rs b/validator_derive/tests/test_derive.rs new file mode 100644 index 0000000..1e310b1 --- /dev/null +++ b/validator_derive/tests/test_derive.rs @@ -0,0 +1,136 @@ +#![feature(proc_macro, attr_literals)] + +#[macro_use] extern crate validator_derive; +extern crate validator; +#[macro_use] extern crate serde_derive; +extern crate serde_json; + +use validator::Validate; + + +#[derive(Debug, Validate, Deserialize)] +struct SignupData { + #[validate(email)] + mail: String, + #[validate(url)] + site: String, + #[validate(length(min = 1), custom = "validate_unique_username")] + #[serde(rename = "firstName")] + first_name: String, + #[validate(range(min = 18, max = 20))] + age: u32, +} + +fn validate_unique_username(username: &str) -> Option<String> { + if username == "xXxShad0wxXx" { + return Some("terrible_username".to_string()); + } + + None +} + +#[test] +fn test_can_validate_ok() { + let signup = SignupData { + mail: "bob@bob.com".to_string(), + site: "http://hello.com".to_string(), + first_name: "Bob".to_string(), + age: 18, + }; + + assert!(signup.validate().is_ok()); +} + +#[test] +fn test_bad_email_fails_validation() { + let signup = SignupData { + mail: "bob".to_string(), + site: "http://hello.com".to_string(), + first_name: "Bob".to_string(), + age: 18, + }; + let res = signup.validate(); + assert!(res.is_err()); + let errs = res.unwrap_err(); + assert!(errs.contains_key("mail")); + assert_eq!(errs["mail"], vec!["email".to_string()]); +} + +#[test] +fn test_bad_url_fails_validation() { + let signup = SignupData { + mail: "bob@bob.com".to_string(), + site: "//hello.com".to_string(), + first_name: "Bob".to_string(), + age: 18, + }; + let res = signup.validate(); + assert!(res.is_err()); + let errs = res.unwrap_err(); + assert!(errs.contains_key("site")); + assert_eq!(errs["site"], vec!["url".to_string()]); +} + +#[test] +fn test_bad_length_fails_validation_and_points_to_original_name() { + let signup = SignupData { + mail: "bob@bob.com".to_string(), + site: "http://hello.com".to_string(), + first_name: "".to_string(), + age: 18, + }; + let res = signup.validate(); + assert!(res.is_err()); + let errs = res.unwrap_err(); + println!("{:?}", errs); + assert!(errs.contains_key("firstName")); + assert_eq!(errs["firstName"], vec!["length".to_string()]); +} + + +#[test] +fn test_bad_range_fails_validation() { + let signup = SignupData { + mail: "bob@bob.com".to_string(), + site: "https://hello.com".to_string(), + first_name: "Bob".to_string(), + age: 1, + }; + let res = signup.validate(); + assert!(res.is_err()); + let errs = res.unwrap_err(); + assert!(errs.contains_key("age")); + assert_eq!(errs["age"], vec!["range".to_string()]); +} + +#[test] +fn test_can_have_multiple_errors() { + let signup = SignupData { + mail: "bob@bob.com".to_string(), + site: "https://hello.com".to_string(), + first_name: "".to_string(), + age: 1, + }; + let res = signup.validate(); + assert!(res.is_err()); + let errs = res.unwrap_err(); + assert!(errs.contains_key("age")); + assert!(errs.contains_key("firstName")); + assert_eq!(errs["age"], vec!["range".to_string()]); + assert_eq!(errs["firstName"], vec!["length".to_string()]); +} + +#[test] +fn test_custom_validation_error() { + let signup = SignupData { + mail: "bob@bob.com".to_string(), + site: "https://hello.com".to_string(), + first_name: "xXxShad0wxXx".to_string(), + age: 18, + }; + let res = signup.validate(); + assert!(res.is_err()); + let errs = res.unwrap_err(); + assert!(errs.contains_key("firstName")); + assert_eq!(errs["firstName"], vec!["terrible_username".to_string()]); +} |
