diff options
Diffstat (limited to 'validator_derive/src')
| -rw-r--r-- | validator_derive/src/lib.rs | 17 | ||||
| -rw-r--r-- | validator_derive/src/quoting.rs | 70 |
2 files changed, 67 insertions, 20 deletions
diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs index b5a76c1..2a23ba4 100644 --- a/validator_derive/src/lib.rs +++ b/validator_derive/src/lib.rs @@ -33,9 +33,7 @@ use quoting::{FieldQuoter, quote_field_validation, quote_schema_validation}; #[proc_macro_derive(Validate, attributes(validate))] pub fn derive_validation(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); - - let expanded = impl_validate(&ast); - expanded.into() + impl_validate(&ast).into() } @@ -52,6 +50,7 @@ fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { }; let mut validations = vec![]; + let mut nested_validations = vec![]; let field_types = find_fields_type(&fields); @@ -62,7 +61,7 @@ fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let field_quoter = FieldQuoter::new(field_ident, name, field_type); for validation in &field_validations { - validations.push(quote_field_validation(&field_quoter, validation)); + quote_field_validation(&field_quoter, validation, &mut validations, &mut nested_validations); } } @@ -82,11 +81,14 @@ fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { #schema_validation - if errors.is_empty() { + let mut result = if errors.is_empty() { ::std::result::Result::Ok(()) } else { ::std::result::Result::Err(errors) - } + }; + + #(#nested_validations)* + result } } ); @@ -352,6 +354,9 @@ fn find_validators_for_field(field: &syn::Field, field_types: &HashMap<String, S }; } }, + Some(syn::Meta::Word(_)) => { + validators.push(FieldValidation::new(Validator::Nested)) + }, _ => unreachable!("Got something other than a list of attributes while checking field `{}`", field_ident), } } diff --git a/validator_derive/src/quoting.rs b/validator_derive/src/quoting.rs index 20ca7a7..b47db6a 100644 --- a/validator_derive/src/quoting.rs +++ b/validator_derive/src/quoting.rs @@ -40,13 +40,25 @@ impl FieldQuoter { } } + pub fn quote_validator_field(&self) -> proc_macro2::TokenStream { + let ident = &self.ident; + + if self._type.starts_with("Option<") || self._type.starts_with("Vec<") { + quote!(#ident) + } else if COW_TYPE.is_match(&self._type.as_ref()) { + quote!(self.#ident.as_ref()) + } else { + quote!(self.#ident) + } + } + pub fn get_optional_validator_param(&self) -> proc_macro2::TokenStream { let ident = &self.ident; if self._type.starts_with("Option<&") || self._type.starts_with("Option<Option<&") || NUMBER_TYPES.contains(&self._type.as_ref()) { - quote!(#ident) + quote!(#ident) } else { - quote!(ref #ident) + quote!(ref #ident) } } @@ -71,6 +83,27 @@ impl FieldQuoter { tokens } + + + /// Wrap the quoted output of a validation with a for loop if + /// the field type is a vector + pub fn wrap_if_vector(&self, tokens: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let field_ident = &self.ident; + let field_name = &self.name; + if self._type.starts_with("Vec<") { + return quote!( + if !::validator::ValidationErrors::has_error(&result, #field_name) { + let results: Vec<_> = self.#field_ident.iter().map(|#field_ident| { + let mut result = ::std::result::Result::Ok(()); + #tokens + result + }).collect(); + result = ::validator::ValidationErrors::merge_all(result, #field_name, results); + }) + } + + tokens + } } /// Quote an actual end-user error creation automatically @@ -326,24 +359,33 @@ pub fn quote_regex_validation(field_quoter: &FieldQuoter, validation: &FieldVali unreachable!(); } -pub fn quote_field_validation(field_quoter: &FieldQuoter, validation: &FieldValidation) -> proc_macro2::TokenStream { +pub fn quote_nested_validation(field_quoter: &FieldQuoter) -> proc_macro2::TokenStream { + let field_name = &field_quoter.name; + let validator_field = field_quoter.quote_validator_field(); + let quoted = quote!(result = ::validator::ValidationErrors::merge(result, #field_name, #validator_field.validate());); + field_quoter.wrap_if_option(field_quoter.wrap_if_vector(quoted)) +} + +pub fn quote_field_validation(field_quoter: &FieldQuoter, validation: &FieldValidation, + validations: &mut Vec<proc_macro2::TokenStream>, + nested_validations: &mut Vec<proc_macro2::TokenStream>) { match validation.validator { - Validator::Length {..} => quote_length_validation(&field_quoter, validation), - Validator::Range {..} => quote_range_validation(&field_quoter, validation), - Validator::Email => quote_email_validation(&field_quoter, validation), - Validator::Url => quote_url_validation(&field_quoter, validation), - Validator::MustMatch(_) => quote_must_match_validation(&field_quoter, validation), - Validator::Custom(_) => quote_custom_validation(&field_quoter, validation), - Validator::Contains(_) => quote_contains_validation(&field_quoter, validation), - Validator::Regex(_) => quote_regex_validation(&field_quoter, validation), + Validator::Length {..} => validations.push(quote_length_validation(&field_quoter, validation)), + Validator::Range {..} => validations.push(quote_range_validation(&field_quoter, validation)), + Validator::Email => validations.push(quote_email_validation(&field_quoter, validation)), + Validator::Url => validations.push(quote_url_validation(&field_quoter, validation)), + Validator::MustMatch(_) => validations.push(quote_must_match_validation(&field_quoter, validation)), + Validator::Custom(_) => validations.push(quote_custom_validation(&field_quoter, validation)), + Validator::Contains(_) => validations.push(quote_contains_validation(&field_quoter, validation)), + Validator::Regex(_) => validations.push(quote_regex_validation(&field_quoter, validation)), #[cfg(feature = "card")] - Validator::CreditCard => quote_credit_card_validation(&field_quoter, validation), + Validator::CreditCard => validations.push(quote_credit_card_validation(&field_quoter, validation)), #[cfg(feature = "phone")] - Validator::Phone => quote_phone_validation(&field_quoter, validation), + Validator::Phone => validations.push(quote_phone_validation(&field_quoter, validation)), + Validator::Nested => nested_validations.push(quote_nested_validation(&field_quoter)), } } - pub fn quote_schema_validation(validation: Option<SchemaValidation>) -> proc_macro2::TokenStream { if let Some(v) = validation { let fn_ident = syn::Ident::new(&v.function, Span::call_site()); |
