validator_derive-0.20.0/.cargo_vcs_info.json0000644000000001560000000000100144400ustar { "git": { "sha1": "32e49fbd17eb4ca3d51e4fb5ac0180ea925fe605" }, "path_in_vcs": "validator_derive" }validator_derive-0.20.0/Cargo.toml0000644000000025410000000000100124360ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.81" name = "validator_derive" version = "0.20.0" authors = ["Vincent Prouillet actions status Crates.io version docs.rs docs Download Macros 1.1 custom derive to simplify struct validation inspired by [marshmallow](http://marshmallow.readthedocs.io/en/latest/) and [Django validators](https://docs.djangoproject.com/en/1.10/ref/validators/). Installation: ```toml [dependencies] validator = { version = "0.19", features = ["derive"] } ``` A short example: ```rust use serde::Deserialize; // A trait that the Validate derive will impl use validator::{Validate, ValidationError}; #[derive(Debug, Validate, Deserialize)] struct SignupData { #[validate(email)] mail: String, #[validate(url)] site: String, #[validate(length(min = 1), custom(function = "validate_unique_username"))] #[serde(rename = "firstName")] first_name: String, #[validate(range(min = 18, max = 20))] age: u32, #[validate(range(exclusive_min = 0.0, max = 100.0))] height: f32, } fn validate_unique_username(username: &str) -> Result<(), ValidationError> { if username == "xXxShad0wxXx" { // the value of the username will automatically be added later return Err(ValidationError::new("terrible_username")); } Ok(()) } match signup_data.validate() { Ok(_) => (), Err(e) => return e; }; ``` A validation on an `Option<_>` field will be executed on the contained type if the option is `Some`. The `validate()` method returns a `Result<(), ValidationErrors>`. In the case of an invalid result, the `ValidationErrors` instance includes a map of errors keyed against the struct's field names. Errors may be represented in three ways, as described by the `ValidationErrorsKind` enum: ```rust #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(untagged)] pub enum ValidationErrorsKind { Struct(Box), List(BTreeMap>), Field(Vec), } ``` In the simple example above, any errors would be of the `Field(Vec)` type, where a single `ValidationError` has the following structure: ```rust #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ValidationError { pub code: Cow<'static, str>, pub message: Option>, pub params: HashMap, Value>, } ``` The value of the field will automatically be added to the params with a key of `value`. The other two `ValidationErrorsKind` types represent errors discovered in nested (vectors of) structs, as described in this example: ```rust use serde::Deserialize; // A trait that the Validate derive will impl use validator::Validate; #[derive(Debug, Validate, Deserialize)] struct SignupData { #[validate(nested)] contact_details: ContactDetails, #[validate(nested)] preferences: Vec, #[validate(required)] allow_cookies: Option, } #[derive(Debug, Validate, Deserialize)] struct ContactDetails { #[validate(email)] mail: String, } #[derive(Debug, Validate, Deserialize)] struct Preference { #[validate(length(min = 4))] name: String, value: bool, } match signup_data.validate() { Ok(_) => (), Err(e) => return e; }; ``` Here, the `ContactDetails` and `Preference` structs are nested within the parent `SignupData` struct. Because these child types also derive `Validate`, the fields where they appear can be tagged for inclusion in the parent struct's validation method. Any errors found in a single nested struct (the `contact_details` field in this example) would be returned as a `Struct(Box)` type in the parent's `ValidationErrors` result. Any errors found in a vector of nested structs (the `preferences` field in this example) would be returned as a `List(BTreeMap>)` type in the parent's `ValidationErrors` result, where the map is keyed on the index of invalid vector entries. ## Usage You will need to import the `Validate` trait. The `validator` crate can also be used without the custom derive as it exposes all the validation functions and types. ## Validators The crate comes with some built-in validators and you can have several validators for a given field. ### email Tests whether the String is a valid email according to the HTML5 regex, which means it will mark some esoteric emails as invalid that won't be valid in a `email` input as well. This validator doesn't take any arguments: `#[validate(email)]`. ### url Tests whether the String is a valid URL. This validator doesn't take any arguments: `#[validate(url)]`; ### length Tests whether a String or a Vec match the length requirement given. `length` has 3 integer arguments: - min - max - equal Using `equal` excludes the `min` or `max` and will result in a compilation error if they are found. At least one argument is required with a maximum of 2 (having `min` and `max` at the same time). Examples: ```rust const MIN_CONST: u64 = 1; const MAX_CONST: u64 = 10; #[validate(length(min = 1, max = 10))] #[validate(length(min = 1))] #[validate(length(max = 10))] #[validate(length(equal = 10))] #[validate(length(min = "MIN_CONST", max = "MAX_CONST"))] ``` ### range Tests whether a number is in the given range. `range` takes 1 or 2 arguments, and they can be normal (`min` and `max`) or exclusive (`exclusive_min`, `exclusive_max`, unreachable limits). These can be a number or a value path. Examples: ```rust const MAX_CONSTANT: i32 = 10; const MIN_CONSTANT: i32 = 0; #[validate(range(min = 1))] #[validate(range(min = "MIN_CONSTANT"))] #[validate(range(min = 1, max = 10))] #[validate(range(min = 1.1, max = 10.8))] #[validate(range(max = 10.8))] #[validate(range(min = "MAX_CONSTANT"))] #[validate(range(min = "crate::MAX_CONSTANT"))] #[validate(range(exclusive_min = 0.0, max = 100.0))] #[validate(range(exclusive_max = 10))] // If you get an error saying the literal doesn't fit in i32, specify a number type in the literal directly #[validate(range(max = 1000000000u64))] ``` ### 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(other = "password2"))] ``` ### contains Tests whether the string contains the substring given or if a key is present in a hashmap. `contains` takes 1 string argument. Examples: ```rust #[validate(contains = "gmail")] #[validate(contains(pattern = "gmail"))] ``` ### does_not_contain Pretty much the opposite of contains, provided just for ease-of-use. Tests whether a container does not contain the substring given if it's a string or if a key is NOT present in a hashmap. `does_not_contain` takes 1 string argument. Examples: ```rust #[validate(does_not_contain = "gmail")] #[validate(does_not_contain(pattern = "gmail"))] ``` ### regex Tests whether the string matches the regex given. `regex` takes 1 string argument: the path to a static Regex instance. Examples: ```rust use once_cell::sync::Lazy; static RE_TWO_CHARS: Lazy = Lazy::new(|| { Regex::new(r"[a-z]{2}$").unwrap() }); #[validate(regex(path = *RE_TWO_CHARS)] ``` ### credit\_card Test whether the string is a valid credit card number. Examples: ```rust #[validate(credit_card)] ``` ### custom Calls one of your functions to perform a custom validation. The field reference (or value if it can be copied cheaply like numbers) will be given as a parameter to the function, which should return a `Result<(), ValidationError>`. Examples: ```rust #[validate(custom(function = "validate_something"))] #[validate(custom(function = "::utils::validate_something"))] ``` You can also do your own validation by parsing the arguments from the validation function by setting `context` for struct. Applying custom validation using the `use_context` argument is accomplished by setting the `use_context` parameter. Defining the `context` parameter will implement the `ValidateArgs` trait with the corresponding function types like this: ```rust use validator::{Validate, ValidateArgs, ValidationError}; fn validate(value: &str, context: &TestContext) -> Result<(), ValidationError> { [...] } struct TestContext(i64, i64); #[derive(Debug, Validate)] #[validate(context = TestContext)] struct TestStruct { #[validate(custom(function = "validate", use_context))] value: String, } let test_struct: TestStruct = [...]; let test_context: TestContext = [...]; test_struct.validate_with_args(&test_context).is_ok(); ``` It is also possible to pass references by using the lifetime `'v_a` note that this lifetime should only be used for the function parameters like this: ```rust fn validate_value(_: &str, arg: &mut Database) -> Result<(), ValidationError> { [...] } #[derive(Debug, Validate)] // vvvv This is the lifetime for references #[validate(context = "Database<'v_a>", mutable)] struct TestStruct { #[validate(custom(function = "validate_value", use_context))] value: String, } let mut database: Database = [...]; let test_struct: TestStruct = [...]; test_struct.validate_with_args(&mut database).is_ok(); ``` Custom validation with arguments doesn't work on nested validation. See [`validator_derive_tests/tests/custom.rs`](https://github.com/Keats/validator/blob/master/validator_derive_tests/tests/custom.rs) and [`validator_derive_tests/tests/custom_args.rs`](https://github.com/Keats/validator/blob/master/validator_derive_tests/tests/custom_args.rs) for more examples. ### nested Performs validation on a field with a type that also implements the Validate trait (or a vector of such types). Examples: ```rust #[validate(nested)] ``` ### non_control_character Tests whether the String has any utf-8 control characters, fails validation if it does. To use this validator, you must enable the `unic` feature for the `validator` crate. This validator doesn't take any arguments: `#[validate(non_control_character)]`; ### required Tests whether the `Option` field is `Some`; ## Struct level validation Often, some error validation can only be applied when looking at the full struct, here's how it works here: ```rust #[derive(Debug, Validate, Deserialize)] #[validate(schema(function = "validate_category", skip_on_field_errors = false))] struct CategoryData { category: String, name: String, } ``` The function mentioned should return a `Result<(), ValidationError>` and will be called after validation is done for all fields. The `skip_on_field_errors` defaults to `true` if not present and will ensure that the function is not called if an error happened while validating the struct fields. Any error on the struct level validation will appear in the key `__all__` of the hashmap of errors. ## Message and code Each validator can take 2 optional arguments in addition to their own arguments: - `message`: a message to go with the error, for example if you want to do i18n - `code`: each validator has a default error code (for example the `regex` validator code is `regex`) but it can be overridden if necessary, mainly needed for the `custom` validator Note that these arguments can't be applied to nested validation calls with `#[validate]`. For example, the following attributes all work: ```rust // code attribute #[validate(email(code = "code_str"))] #[validate(credit_card(code = "code_str"))] #[validate(length(min = 5, max = 10, code = "code_str"))] #[validate(regex(path = *static_regex, code = "code_str"))] #[validate(custom(function = "custom_fn", code = "code_str"))] #[validate(contains(pattern = "pattern_str", code = "code_str"))] #[validate(does_not_contain(pattern = "pattern_str", code = "code_str"))] #[validate(must_match(other = "match_value", code = "code_str"))] // message attribute #[validate(url(message = "message_str"))] #[validate(length(min = 5, max = 10, message = "message_str"))] #[validate(regex(path = *static_regex, message = "message_str"))] #[validate(custom(function = "custom_fn", message = "message_str"))] #[validate(contains(pattern = "pattern_str", message = "message_str"))] #[validate(does_not_contain(pattern = "pattern_str", message = "message_str"))] #[validate(must_match(other = "match_value", message = "message_str"))] // both attributes #[validate(url(message = "message", code = "code_str"))] #[validate(email(code = "code_str", message = "message"))] #[validate(custom(function = "custom_fn", code = "code_str", message = "message_str"))] ``` ## Features `derive` - This allows for the use of the derive macro. `derive_nightly_features` - This imports both derive as well as proc-macro-error2 nightly features. This allows proc-macro-error2 to emit extra nightly warnings. validator_derive-0.20.0/src/lib.rs000064400000000000000000000325331046102023000151370ustar 00000000000000use darling::ast::Data; use darling::util::{Override, WithOriginal}; use darling::FromDeriveInput; use proc_macro_error2::{abort, proc_macro_error}; use quote::{quote, ToTokens}; use syn::{parse_macro_input, DeriveInput, Field, GenericParam, Path, PathArguments}; use tokens::cards::credit_card_tokens; use tokens::contains::contains_tokens; use tokens::custom::custom_tokens; use tokens::does_not_contain::does_not_contain_tokens; use tokens::email::email_tokens; use tokens::ip::ip_tokens; use tokens::length::length_tokens; use tokens::must_match::must_match_tokens; use tokens::nested::nested_tokens; use tokens::non_control_character::non_control_char_tokens; use tokens::range::range_tokens; use tokens::regex::regex_tokens; use tokens::required::required_tokens; use tokens::schema::schema_tokens; use tokens::url::url_tokens; use types::*; use utils::{quote_use_stmts, CrateName}; mod tokens; mod types; mod utils; impl ToTokens for ValidateField { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let field_name = self.ident.clone().unwrap(); let field_name_str = self.ident.clone().unwrap().to_string(); let type_name = self.ty.to_token_stream().to_string(); let is_number = NUMBER_TYPES.contains(&type_name); let (actual_field, wrapper_closure) = self.if_let_option_wrapper(&field_name, is_number); // Length validation let length = if let Some(length) = self.length.clone() { wrapper_closure(length_tokens(&self.crate_name, length, &actual_field, &field_name_str)) } else { quote!() }; // Email validation let email = if let Some(email) = self.email.clone() { wrapper_closure(email_tokens( &self.crate_name, match email { Override::Inherit => Email::default(), Override::Explicit(e) => e, }, &actual_field, &field_name_str, )) } else { quote!() }; // Credit card validation let card = if let Some(credit_card) = self.credit_card.clone() { wrapper_closure(credit_card_tokens( &self.crate_name, match credit_card { Override::Inherit => Card::default(), Override::Explicit(c) => c, }, &actual_field, &field_name_str, )) } else { quote!() }; // Url validation let url = if let Some(url) = self.url.clone() { wrapper_closure(url_tokens( &self.crate_name, match url { Override::Inherit => Url::default(), Override::Explicit(u) => u, }, &actual_field, &field_name_str, )) } else { quote!() }; // Ip address validation let ip = if let Some(ip) = self.ip.clone() { wrapper_closure(ip_tokens( &self.crate_name, match ip { Override::Inherit => Ip::default(), Override::Explicit(i) => i, }, &actual_field, &field_name_str, )) } else { quote!() }; // Non control character validation let ncc = if let Some(ncc) = self.non_control_character.clone() { wrapper_closure(non_control_char_tokens( &self.crate_name, match ncc { Override::Inherit => NonControlCharacter::default(), Override::Explicit(n) => n, }, &actual_field, &field_name_str, )) } else { quote!() }; // Range validation let range = if let Some(range) = self.range.clone() { wrapper_closure(range_tokens(&self.crate_name, range, &actual_field, &field_name_str)) } else { quote!() }; // Required validation let required = if let Some(required) = self.required.clone() { required_tokens( &self.crate_name, match required { Override::Inherit => Required::default(), Override::Explicit(r) => r, }, &field_name, &field_name_str, ) } else { quote!() }; // Contains validation let contains = if let Some(contains) = self.contains.clone() { wrapper_closure(contains_tokens( &self.crate_name, contains, &actual_field, &field_name_str, )) } else { quote!() }; // Does not contain validation let does_not_contain = if let Some(does_not_contain) = self.does_not_contain.clone() { wrapper_closure(does_not_contain_tokens( &self.crate_name, does_not_contain, &actual_field, &field_name_str, )) } else { quote!() }; // Must match validation let must_match = if let Some(must_match) = self.must_match.clone() { // TODO: handle option for other wrapper_closure(must_match_tokens( &self.crate_name, must_match, &actual_field, &field_name_str, )) } else { quote!() }; // Regex validation let regex = if let Some(regex) = self.regex.clone() { wrapper_closure(regex_tokens(&self.crate_name, regex, &actual_field, &field_name_str)) } else { quote!() }; // Custom validation let mut custom = quote!(); // We try to be smart when passing arguments let is_cow = type_name.contains("Cow <"); let custom_actual_field = if is_cow { quote!(#actual_field.as_ref()) } else if is_number || type_name.starts_with("&") { quote!(#actual_field) } else { quote!(&#actual_field) }; for c in &self.custom { let tokens = custom_tokens(c.clone(), &custom_actual_field, &field_name_str); custom = quote!( #custom #tokens ); } if !self.custom.is_empty() { custom = wrapper_closure(custom); } let nested = if let Some(n) = self.nested { if n { wrapper_closure(nested_tokens(&actual_field, &field_name_str)) } else { quote!() } } else { quote!() }; tokens.extend(quote! { #length #email #card #url #ip #ncc #range #required #contains #does_not_contain #must_match #regex #custom #nested }); } } // The main struct we get from parsing the attributes // The "supports(struct_named)" attribute guarantees only named structs to work with this macro #[derive(Debug, FromDeriveInput)] #[darling(attributes(validate), supports(struct_named))] #[darling(and_then = "ValidationData::validate")] struct ValidationData { ident: syn::Ident, generics: syn::Generics, data: Data<(), WithOriginal>, #[darling(multiple)] schema: Vec, context: Option, mutable: Option, nest_all_fields: Option, /// The name of the crate to use for the generated code, /// defaults to `validator`. #[darling(rename = "crate", default)] crate_name: CrateName, } impl ValidationData { fn validate(self) -> darling::Result { if let Some(context) = &self.context { // Check if context lifetime is not `'v_a` for segment in &context.segments { match &segment.arguments { PathArguments::AngleBracketed(args) => { for arg in &args.args { match arg { syn::GenericArgument::Lifetime(lt) => { if lt.ident != "v_a" { abort! { lt.ident, "Invalid argument reference"; note = "The lifetime `'{}` is not supported.", lt.ident; help = "Please use the validator lifetime `'v_a`"; } } } _ => (), } } } _ => (), } } } match &self.data { Data::Struct(fields) => { let original_fields: Vec<&Field> = fields.fields.iter().map(|f| &f.original).collect(); for f in &fields.fields { f.parsed.validate(&self.ident, &original_fields, &f.original); } } _ => (), } Ok(self) } } #[proc_macro_error] #[proc_macro_derive(Validate, attributes(validate))] pub fn derive_validation(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: DeriveInput = parse_macro_input!(input); // parse the input to the ValidationData struct defined above let validation_data = match ValidationData::from_derive_input(&input) { Ok(data) => data, Err(e) => return e.write_errors().into(), }; let crate_name = validation_data.crate_name; let custom_context = if let Some(context) = &validation_data.context { if let Some(mutable) = validation_data.mutable { if mutable { quote!(&'v_a mut #context) } else { quote!(&'v_a #context) } } else { quote!(&'v_a #context) } } else { quote!(()) }; // get all the fields to quote them below let mut validation_fields: Vec = validation_data .data .take_struct() .unwrap() .fields .into_iter() .map(|f| f.parsed) // skip fields with #[validate(skip)] attribute .filter(|f| if let Some(s) = f.skip { !s } else { true }) .map(|f| ValidateField { crate_name: crate_name.clone(), ..f }) .collect(); if let Some(nest_all_fields) = validation_data.nest_all_fields { if nest_all_fields { validation_fields = validation_fields .iter_mut() .map(|f| { f.nested = Some(true); f.to_owned() }) .collect(); } } // generate `use` statements for all used validator traits let use_statements = quote_use_stmts(&crate_name, &validation_fields); // Schema validation let schema = validation_data.schema.iter().fold(quote!(), |acc, s| { let st = schema_tokens(s.clone()); let acc = quote! { #acc #st }; acc }); let ident = validation_data.ident; let (imp, ty, whr) = validation_data.generics.split_for_impl(); let struct_generics_quote = validation_data.generics.params.iter().fold(quote!(), |mut q, g| { if let GenericParam::Type(t) = g { // Default types are not allowed in trait impl if t.default.is_some() { let mut t2 = t.clone(); t2.default = None; let g2 = GenericParam::Type(t2); q.extend(quote!(#g2, )); } else { q.extend(quote!(#g, )); } } else { q.extend(quote!(#g, )); } q }); let imp_args = if struct_generics_quote.is_empty() { quote!(<'v_a>) } else { quote!(<'v_a, #struct_generics_quote>) }; let argless_validation = if validation_data.context.is_none() { quote! { impl #imp #crate_name::Validate for #ident #ty #whr { fn validate(&self) -> ::std::result::Result<(), #crate_name::ValidationErrors> { use #crate_name::ValidateArgs; self.validate_with_args(()) } } } } else { quote!() }; quote!( #argless_validation impl #imp_args #crate_name::ValidateArgs<'v_a> for #ident #ty #whr { type Args = #custom_context; fn validate_with_args(&self, args: Self::Args) -> ::std::result::Result<(), #crate_name::ValidationErrors> { #use_statements let mut errors = #crate_name::ValidationErrors::new(); #(#validation_fields)* #schema if errors.is_empty() { ::std::result::Result::Ok(()) } else { ::std::result::Result::Err(errors) } } } ) .into() } validator_derive-0.20.0/src/tokens/cards.rs000064400000000000000000000012111046102023000167550ustar 00000000000000use quote::quote; use crate::types::Card; use crate::utils::{quote_code, quote_message, CrateName}; pub fn credit_card_tokens( crate_name: &CrateName, credit_card: Card, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let message = quote_message(credit_card.message); let code = quote_code(crate_name, credit_card.code, "credit_card"); quote! { if !#field_name.validate_credit_card() { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/contains.rs000064400000000000000000000014631046102023000175100ustar 00000000000000use quote::quote; use crate::types::Contains; use crate::utils::{quote_code, quote_message, CrateName}; pub fn contains_tokens( crate_name: &CrateName, contains: Contains, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let p = contains.pattern; let (needle, needle_err) = (quote!(#p), quote!(err.add_param(::std::borrow::Cow::from("needle"), &#p);)); let message = quote_message(contains.message); let code = quote_code(crate_name, contains.code, "contains"); quote! { if !#field_name.validate_contains(#needle) { #code #message #needle_err err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/custom.rs000064400000000000000000000020351046102023000172000ustar 00000000000000use quote::quote; use crate::types::Custom; use crate::utils::quote_message; pub fn custom_tokens( custom: Custom, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let fn_call = custom.function.unwrap(); let args = if let Some(arg) = custom.use_context { if arg { quote!(#field_name, args) } else { quote!(#field_name) } } else { quote!(#field_name) }; let message = quote_message(custom.message); let code = if let Some(c) = custom.code { quote!( err.code = ::std::borrow::Cow::from(#c); ) } else { quote!() }; quote! { match #fn_call(#args) { ::std::result::Result::Ok(()) => {} ::std::result::Result::Err(mut err) => { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } } validator_derive-0.20.0/src/tokens/does_not_contain.rs000064400000000000000000000015701046102023000212160ustar 00000000000000use quote::quote; use crate::types::DoesNotContain; use crate::utils::{quote_code, quote_message, CrateName}; pub fn does_not_contain_tokens( crate_name: &CrateName, does_not_contain: DoesNotContain, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let p = does_not_contain.pattern; let (needle, needle_err) = (quote!(#p), quote!(err.add_param(::std::borrow::Cow::from("needle"), &#p);)); let message = quote_message(does_not_contain.message); let code = quote_code(crate_name, does_not_contain.code, "does_not_contain"); quote! { if !#field_name.validate_does_not_contain(#needle) { #code #message #needle_err err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/email.rs000064400000000000000000000011471046102023000167600ustar 00000000000000use quote::quote; use crate::types::Email; use crate::utils::{quote_code, quote_message, CrateName}; pub fn email_tokens( crate_name: &CrateName, email: Email, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let message = quote_message(email.message); let code = quote_code(crate_name, email.code, "email"); quote! { if !#field_name.validate_email() { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/ip.rs000064400000000000000000000023111046102023000162730ustar 00000000000000use quote::quote; use crate::types::Ip; use crate::utils::{quote_code, quote_message, CrateName}; pub fn ip_tokens( crate_name: &CrateName, ip: Ip, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let message = quote_message(ip.message); let code = quote_code(crate_name, ip.code, "ip"); let version = match (ip.v4, ip.v6) { (Some(v4), Some(v6)) => match (v4, v6) { (true, false) => quote!(validate_ipv4()), (false, true) => quote!(validate_ipv6()), _ => quote!(validate_ip()), }, (Some(v4), None) => { if v4 { quote!(validate_ipv4()) } else { quote!(validate_ip()) } } (None, Some(v6)) => { if v6 { quote!(validate_ipv6()) } else { quote!(validate_ip()) } } _ => quote!(validate_ip()), }; quote! { if !#field_name.#version { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/length.rs000064400000000000000000000024431046102023000171520ustar 00000000000000use quote::quote; use crate::types::Length; use crate::utils::{quote_code, quote_message, CrateName}; pub fn length_tokens( crate_name: &CrateName, length: Length, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let (min, min_err) = if let Some(v) = length.min.as_ref() { (quote!(Some(#v)), quote!(err.add_param(::std::borrow::Cow::from("min"), &#v);)) } else { (quote!(None), quote!()) }; let (max, max_err) = if let Some(v) = length.max { (quote!(Some(#v)), quote!(err.add_param(::std::borrow::Cow::from("max"), &#v);)) } else { (quote!(None), quote!()) }; let (equal, equal_err) = if let Some(v) = length.equal { (quote!(Some(#v)), quote!(err.add_param(::std::borrow::Cow::from("equal"), &#v);)) } else { (quote!(None), quote!()) }; let message = quote_message(length.message); let code = quote_code(crate_name, length.code, "length"); quote! { if !#field_name.validate_length(#min, #max, #equal) { #code #message #min_err #max_err #equal_err err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/mod.rs000064400000000000000000000004061046102023000164450ustar 00000000000000pub mod cards; pub mod contains; pub mod custom; pub mod does_not_contain; pub mod email; pub mod ip; pub mod length; pub mod must_match; pub mod nested; pub mod non_control_character; pub mod range; pub mod regex; pub mod required; pub mod schema; pub mod url; validator_derive-0.20.0/src/tokens/must_match.rs000064400000000000000000000015261046102023000200360ustar 00000000000000use quote::quote; use crate::types::MustMatch; use crate::utils::{quote_code, quote_message, CrateName}; pub fn must_match_tokens( crate_name: &CrateName, must_match: MustMatch, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let o = must_match.other; let (other, other_err) = (quote!(self.#o), quote!(err.add_param(::std::borrow::Cow::from("other"), &self.#o);)); let message = quote_message(must_match.message); let code = quote_code(crate_name, must_match.code, "must_match"); quote! { if !#crate_name::validate_must_match(&#field_name, &#other) { #code #message #other_err err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/nested.rs000064400000000000000000000005721046102023000171540ustar 00000000000000use quote::quote; pub fn nested_tokens( field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { quote! { if let std::collections::hash_map::Entry::Vacant(entry) = errors.0.entry(::std::borrow::Cow::Borrowed(#field_name_str)) { errors.merge_self(#field_name_str, (&#field_name).validate()); } } } validator_derive-0.20.0/src/tokens/non_control_character.rs000064400000000000000000000013171046102023000222360ustar 00000000000000use quote::quote; use crate::types::NonControlCharacter; use crate::utils::{quote_code, quote_message, CrateName}; pub fn non_control_char_tokens( crate_name: &CrateName, non_control_char: NonControlCharacter, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let message = quote_message(non_control_char.message); let code = quote_code(crate_name, non_control_char.code, "non_control_character"); quote! { if !#field_name.validate_non_control_character() { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/range.rs000064400000000000000000000030451046102023000167640ustar 00000000000000use quote::quote; use crate::types::Range; use crate::utils::{quote_code, quote_message, CrateName}; pub fn range_tokens( crate_name: &CrateName, range: Range, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let (min, min_err) = if let Some(m) = range.min { (quote!(Some(#m)), quote!(err.add_param(::std::borrow::Cow::from("min"), &#m);)) } else { (quote!(None), quote!()) }; let (max, max_err) = if let Some(m) = range.max { (quote!(Some(#m)), quote!(err.add_param(::std::borrow::Cow::from("max"), &#m);)) } else { (quote!(None), quote!()) }; let (ex_min, ex_min_err) = if let Some(m) = range.exclusive_min { (quote!(Some(#m)), quote!(err.add_param(::std::borrow::Cow::from("exclusive_min"), &#m);)) } else { (quote!(None), quote!()) }; let (ex_max, ex_max_err) = if let Some(m) = range.exclusive_max { (quote!(Some(#m)), quote!(err.add_param(::std::borrow::Cow::from("exclusive_max"), &#m);)) } else { (quote!(None), quote!()) }; let message = quote_message(range.message); let code = quote_code(crate_name, range.code, "range"); quote! { if !#field_name.validate_range(#min, #max, #ex_min, #ex_max) { #code #message #min_err #max_err #ex_min_err #ex_max_err err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/regex.rs000064400000000000000000000012111046102023000167730ustar 00000000000000use quote::quote; use crate::types::Regex; use crate::utils::{quote_code, quote_message, CrateName}; pub fn regex_tokens( crate_name: &CrateName, regex: Regex, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let path = regex.path; let message = quote_message(regex.message); let code = quote_code(crate_name, regex.code, "regex"); quote! { if !&#field_name.validate_regex(&#path) { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/required.rs000064400000000000000000000012061046102023000175050ustar 00000000000000use quote::quote; use syn::Ident; use crate::types::Required; use crate::utils::{quote_code, quote_message, CrateName}; pub fn required_tokens( crate_name: &CrateName, required: Required, field_name: &Ident, field_name_str: &str, ) -> proc_macro2::TokenStream { let message = quote_message(required.message); let code = quote_code(crate_name, required.code, "required"); quote! { if !self.#field_name.validate_required() { #code #message err.add_param(::std::borrow::Cow::from("value"), &self.#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/tokens/schema.rs000064400000000000000000000023211046102023000171240ustar 00000000000000use quote::quote; use crate::types::Schema; use crate::utils::quote_message; pub fn schema_tokens(schema: Schema) -> proc_macro2::TokenStream { let fn_call = schema.function; let args = if let Some(args) = schema.use_context { if args { quote!(&self, args) } else { quote!(&self) } } else { quote!(&self) }; let skip_on_errors = schema.skip_on_field_errors.unwrap_or(true); let message = quote_message(schema.message); let code = if let Some(c) = schema.code { quote!( err.code = ::std::borrow::Cow::from(#c); ) } else { quote!() }; let fn_call = quote! { match #fn_call(#args) { ::std::result::Result::Ok(()) => {} ::std::result::Result::Err(mut err) => { #code #message errors.add("__all__", err); } } }; if skip_on_errors { quote! { if errors.is_empty() || ((errors.field_errors().len() == 1) && errors.field_errors().contains_key("__all__")) { #fn_call } } } else { quote! { #fn_call } } } validator_derive-0.20.0/src/tokens/url.rs000064400000000000000000000011271046102023000164710ustar 00000000000000use quote::quote; use crate::types::Url; use crate::utils::{quote_code, quote_message, CrateName}; pub fn url_tokens( crate_name: &CrateName, url: Url, field_name: &proc_macro2::TokenStream, field_name_str: &str, ) -> proc_macro2::TokenStream { let message = quote_message(url.message); let code = quote_code(crate_name, url.code, "url"); quote! { if !#field_name.validate_url() { #code #message err.add_param(::std::borrow::Cow::from("value"), &#field_name); errors.add(#field_name_str, err); } } } validator_derive-0.20.0/src/types.rs000064400000000000000000000252511046102023000155340ustar 00000000000000use once_cell::sync::Lazy; use darling::util::Override; use darling::{FromField, FromMeta}; use proc_macro_error2::abort; use quote::quote; use syn::spanned::Spanned; use syn::{Expr, Field, Ident, Path}; use crate::utils::{get_attr, CrateName}; static OPTIONS_TYPE: [&str; 3] = ["Option|", "std|option|Option|", "core|option|Option|"]; pub(crate) static NUMBER_TYPES: Lazy> = Lazy::new(|| { let number_types = [ quote!(usize), quote!(u8), quote!(u16), quote!(u32), quote!(u64), quote!(u128), quote!(isize), quote!(i8), quote!(i16), quote!(i32), quote!(i64), quote!(i128), quote!(f32), quote!(f64), ]; let mut tys = Vec::with_capacity(number_types.len() * 3); for ty in number_types { tys.push(ty.to_string()); tys.push(quote!(Option<#ty>).to_string()); tys.push(quote!(Option >).to_string()); } tys }); // This struct holds all the validation information on a field // The "ident" and "ty" fields are populated by `darling` // The others are our attributes for example: // #[validate(email(message = "asdfg"))] // ^^^^^ // #[derive(Debug, FromField, Clone)] #[darling(attributes(validate))] pub struct ValidateField { pub ident: Option, pub ty: syn::Type, // pub attrs: Vec, pub credit_card: Option>, pub contains: Option, pub does_not_contain: Option, pub email: Option>, pub ip: Option>, pub length: Option, pub must_match: Option, pub non_control_character: Option>, pub range: Option, pub required: Option>, pub url: Option>, pub regex: Option, #[darling(multiple)] pub custom: Vec, pub skip: Option, pub nested: Option, /// Placeholder for the crate name, filled in by the [`ValidationData`](crate::ValidationData) value. #[darling(skip)] pub crate_name: CrateName, } impl ValidateField { pub fn validate(&self, struct_ident: &Ident, all_fields: &[&Field], current_field: &Field) { let field_name = self.ident.clone().expect("Field is not a named field").to_string(); let field_attrs = ¤t_field.attrs; for attr in field_attrs { if attr.path().is_ident("validate") && matches!(attr.meta, syn::Meta::Path(_)) { abort!( current_field.span(), "You need to set at least one validator on field `{}`", field_name; note = "If you want nested validation, use `#[validate(nested)]`" ) } } for c in &self.custom { // If function is not a path if let Err(e) = &c.function { abort!( e.span(), "Invalid attribute #[validate(custom(...))] on field `{}`:", field_name; note = "Invalid argument for `custom` validator, only paths are allowed"; help = "Try formating the argument like `path::to::function` or `\"path::to::function\"`" ); } } if let Some(length) = &self.length { // If length has both `equal` and `min` or `max` argument if length.equal.is_some() && (length.min.is_some() || length.max.is_some()) { abort! { length.equal.clone().unwrap().span(), "Invalid attribute #[validate(length(...))] on field `{}`:", field_name; note = "Both `equal` and `min` or `max` have been set"; help = "Exclusively use either the `equal` or `min` and `max` attributes" } } // Check if validator has no arguments if length.equal.is_none() && length.min.is_none() && length.max.is_none() { abort!( get_attr(field_attrs, "length").unwrap(), "Invalid attribute #[validate(length(...))] on field `{}`:", field_name; note = "Validator `length` requires at least 1 argument"; help = "Add the argument `equal`, `min` or `max`" ) } } if let Some(must_match) = &self.must_match { let other_field = must_match .other .get_ident() .expect("Cannot get ident from `other` field value") .to_string(); // Check if the other field exists if !all_fields.iter().any(|f| f.ident.clone().unwrap() == other_field) { abort!( must_match.other.span(), "Invalid attribute for #[validate(must_match(...))] on field `{}`:", field_name; note = "The `other` field doesn't exist in the struct `{}`", struct_ident; help = "Add the field `{}` to the struct", other_field ) } } if let Some(range) = &self.range { // Check if validator has no arguments if range.min.is_none() && range.max.is_none() && range.exclusive_min.is_none() && range.exclusive_max.is_none() { abort!( get_attr(field_attrs, "range").unwrap(), "Invalid attribute #[validate(range(...))] on field `{}`:", field_name; note = "Validator `range` requires at least 1 argument"; help = "Add the argument `min` or `max`, `exclusive_min` or `exclusive_max`" ) } } } /// How many Option u8 { fn find_option(mut count: u8, ty: &syn::Type) -> u8 { if let syn::Type::Path(p) = ty { let idents_of_path = p.path.segments.iter().into_iter().fold(String::new(), |mut acc, v| { acc.push_str(&v.ident.to_string()); acc.push('|'); acc }); if OPTIONS_TYPE.contains(&idents_of_path.as_str()) { count += 1; if let Some(p) = p.path.segments.first() { if let syn::PathArguments::AngleBracketed(ref params) = p.arguments { if let syn::GenericArgument::Type(ref ty) = params.args.first().unwrap() { count = find_option(count, ty); } } } } } count } find_option(0, &self.ty) } pub fn if_let_option_wrapper( &self, field_name: &Ident, is_number_type: bool, ) -> (proc_macro2::TokenStream, Box proc_macro2::TokenStream>) { let number_options = self.number_options(); let field_name = field_name.clone(); let actual_field = if number_options > 0 { quote!(#field_name) } else { quote!(self.#field_name) }; let binding_pattern = if is_number_type { quote!(#field_name) } else { quote!(ref #field_name) }; match number_options { 0 => (actual_field.clone(), Box::new(move |tokens| tokens)), 1 => ( actual_field.clone(), Box::new(move |tokens| { quote!( if let Some(#binding_pattern) = self.#field_name { #tokens } ) }), ), 2 => ( actual_field.clone(), Box::new(move |tokens| { quote!( if let Some(Some(#binding_pattern)) = self.#field_name { #tokens } ) }), ), _ => abort!( field_name.span(), "Validation on values nested in more than 2 Option are not supported" ), } } } // Structs to hold the validation information and to provide attributes // The name of a field here corresponds to an attribute like // #[validate(card(message = "something's wrong", code = "1234"))] // ^^^^^^^ ^^^^ // #[derive(Debug, Clone, FromMeta, Default)] pub struct Card { pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct Contains { pub pattern: String, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct DoesNotContain { pub pattern: String, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta, Default)] pub struct Email { pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta, Default)] pub struct Ip { pub v4: Option, pub v6: Option, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct Length { pub min: Option, pub max: Option, pub equal: Option, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct MustMatch { pub other: Path, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta, Default)] pub struct NonControlCharacter { pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct Range { pub min: Option, pub max: Option, pub exclusive_min: Option, pub exclusive_max: Option, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta, Default)] pub struct Required { pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta, Default)] pub struct Url { pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct Regex { pub path: Expr, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct Custom { pub function: darling::Result, pub use_context: Option, pub message: Option, pub code: Option, } #[derive(Debug, Clone, FromMeta)] pub struct Schema { pub function: Path, pub use_context: Option, pub skip_on_field_errors: Option, pub message: Option, pub code: Option, } validator_derive-0.20.0/src/utils.rs000064400000000000000000000077141046102023000155340ustar 00000000000000use quote::{quote, ToTokens}; use syn::{Attribute, Path}; use crate::ValidateField; #[derive(Debug, Clone)] pub struct CrateName { inner: Path, } impl ToTokens for CrateName { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { self.inner.to_tokens(tokens); } } impl darling::FromMeta for CrateName { fn from_string(value: &str) -> darling::Result { Path::from_string(value).map(|inner| CrateName { inner }) } fn from_value(value: &syn::Lit) -> darling::Result { Path::from_value(value).map(|inner| CrateName { inner }) } fn from_expr(value: &syn::Expr) -> darling::Result { Path::from_expr(value).map(|inner| CrateName { inner }) } } impl Default for CrateName { fn default() -> Self { CrateName { inner: syn::parse_str("::validator").expect("invalid valid crate name") } } } pub fn quote_message(message: Option) -> proc_macro2::TokenStream { if let Some(m) = message { quote!( err.message = Some(::std::borrow::Cow::from(#m)); ) } else { quote!() } } pub fn quote_code( crate_name: &CrateName, code: Option, default: &str, ) -> proc_macro2::TokenStream { if let Some(c) = code { quote!( let mut err = #crate_name::ValidationError::new(#c); ) } else { quote!( let mut err = #crate_name::ValidationError::new(#default); ) } } pub fn quote_use_stmts( crate_name: &CrateName, fields: &Vec, ) -> proc_macro2::TokenStream { let mut length = quote!(); let mut email = quote!(); let mut card = quote!(); let mut url = quote!(); let mut ip = quote!(); let mut ncc = quote!(); let mut range = quote!(); let mut required = quote!(); let mut contains = quote!(); let mut does_not_contain = quote!(); let mut regex = quote!(); for f in fields { if f.length.is_some() { length = quote!( use #crate_name::ValidateLength; ); } if f.email.is_some() { email = quote!( use #crate_name::ValidateEmail; ); } if f.credit_card.is_some() { card = quote!( use #crate_name::ValidateCreditCard; ); } if f.url.is_some() { url = quote!( use #crate_name::ValidateUrl; ); } if f.ip.is_some() { ip = quote!( use #crate_name::ValidateIp; ); } if f.non_control_character.is_some() { ncc = quote!( use #crate_name::ValidateNonControlCharacter; ); } if f.range.is_some() { range = quote!( use #crate_name::ValidateRange; ); } if f.required.is_some() { required = quote!( use #crate_name::ValidateRequired; ); } if f.contains.is_some() { contains = quote!( use #crate_name::ValidateContains; ); } if f.does_not_contain.is_some() { does_not_contain = quote!( use #crate_name::ValidateDoesNotContain; ); } if f.regex.is_some() { regex = quote!( use #crate_name::ValidateRegex; ); } } quote!( #length #email #card #url #ip #ncc #range #required #contains #does_not_contain #regex ) } pub fn get_attr<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attribute> { attrs.iter().find(|a| match &a.meta { syn::Meta::List(list) => list.tokens.clone().into_iter().any(|t| match t { proc_macro2::TokenTree::Ident(i) => i == name, _ => false, }), _ => false, }) }