diff --git a/packages/fortifier-macros/src/lib.rs b/packages/fortifier-macros/src/lib.rs index 263faec..54a9008 100644 --- a/packages/fortifier-macros/src/lib.rs +++ b/packages/fortifier-macros/src/lib.rs @@ -3,6 +3,7 @@ //! Fortifier macros. mod validate; +mod validation; mod validations; use proc_macro::TokenStream; diff --git a/packages/fortifier-macros/src/validate/field.rs b/packages/fortifier-macros/src/validate/field.rs index c6a794b..e722a69 100644 --- a/packages/fortifier-macros/src/validate/field.rs +++ b/packages/fortifier-macros/src/validate/field.rs @@ -2,38 +2,36 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Field, Result}; -use crate::validations::{Email, Length, Url}; +use crate::{ + validation::Validation, + validations::{Email, Length, Url}, +}; pub struct ValidateField { expr: TokenStream, - // TODO: Consider using a trait for validations. - email: Option, - length: Option, - url: Option, + validations: Vec>, } impl ValidateField { pub fn parse(expr: TokenStream, field: &Field) -> Result { let mut result = Self { expr, - email: None, - length: None, - url: None, + validations: vec![], }; for attr in &field.attrs { if attr.path().is_ident("validate") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("email") { - result.email = Some(Email::parse(&meta)?); + result.validations.push(Box::new(Email::parse(&meta)?)); Ok(()) } else if meta.path.is_ident("length") { - result.length = Some(Length::parse(&meta)?); + result.validations.push(Box::new(Length::parse(&meta)?)); Ok(()) } else if meta.path.is_ident("url") { - result.url = Some(Url::parse(&meta)?); + result.validations.push(Box::new(Url::parse(&meta)?)); Ok(()) } else { @@ -49,26 +47,25 @@ impl ValidateField { pub fn error_type(&self) -> TokenStream { // TODO: Merge error types - if self.email.is_some() { - quote!(EmailError) - } else if self.length.is_some() { - quote!(LengthError) - } else if self.url.is_some() { - quote!(UrlError) - } else { - quote!(()) - } + self.validations + .first() + .map(|validation| validation.error_type()) + .unwrap_or_else(|| quote!(())) } pub fn sync_validations(&self) -> Vec { - let email = self.email.as_ref().map(|email| email.tokens(&self.expr)); - let length = self.length.as_ref().map(|length| length.tokens(&self.expr)); - let url = self.url.as_ref().map(|url| url.tokens(&self.expr)); - - [email, length, url].into_iter().flatten().collect() + self.validations + .iter() + .filter(|validation| !validation.is_async()) + .map(|validation| validation.tokens(&self.expr)) + .collect() } pub fn async_validations(&self) -> Vec { - vec![] + self.validations + .iter() + .filter(|validation| validation.is_async()) + .map(|validation| validation.tokens(&self.expr)) + .collect() } } diff --git a/packages/fortifier-macros/src/validation.rs b/packages/fortifier-macros/src/validation.rs new file mode 100644 index 0000000..b52c705 --- /dev/null +++ b/packages/fortifier-macros/src/validation.rs @@ -0,0 +1,14 @@ +use proc_macro2::TokenStream; +use syn::{Result, meta::ParseNestedMeta}; + +pub trait Validation { + fn parse(_meta: &ParseNestedMeta<'_>) -> Result + where + Self: Sized; + + fn is_async(&self) -> bool; + + fn error_type(&self) -> TokenStream; + + fn tokens(&self, expr: &TokenStream) -> TokenStream; +} diff --git a/packages/fortifier-macros/src/validations/email.rs b/packages/fortifier-macros/src/validations/email.rs index 47c476d..fd95f67 100644 --- a/packages/fortifier-macros/src/validations/email.rs +++ b/packages/fortifier-macros/src/validations/email.rs @@ -2,15 +2,25 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Result, meta::ParseNestedMeta}; +use crate::validation::Validation; + #[derive(Default)] pub struct Email {} -impl Email { - pub fn parse(_meta: &ParseNestedMeta<'_>) -> Result { +impl Validation for Email { + fn parse(_meta: &ParseNestedMeta<'_>) -> Result { Ok(Email::default()) } - pub fn tokens(&self, expr: &TokenStream) -> TokenStream { + fn is_async(&self) -> bool { + false + } + + fn error_type(&self) -> TokenStream { + quote!(EmailError) + } + + fn tokens(&self, expr: &TokenStream) -> TokenStream { quote! { #expr.validate_email() } diff --git a/packages/fortifier-macros/src/validations/length.rs b/packages/fortifier-macros/src/validations/length.rs index 5799e68..6883db9 100644 --- a/packages/fortifier-macros/src/validations/length.rs +++ b/packages/fortifier-macros/src/validations/length.rs @@ -2,6 +2,8 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Expr, Result, meta::ParseNestedMeta}; +use crate::validation::Validation; + #[derive(Default)] pub struct Length { pub equal: Option, @@ -9,8 +11,8 @@ pub struct Length { pub max: Option, } -impl Length { - pub fn parse(meta: &ParseNestedMeta<'_>) -> Result { +impl Validation for Length { + fn parse(meta: &ParseNestedMeta<'_>) -> Result { let mut result = Length::default(); meta.parse_nested_meta(|meta| { @@ -37,7 +39,15 @@ impl Length { Ok(result) } - pub fn tokens(&self, expr: &TokenStream) -> TokenStream { + fn is_async(&self) -> bool { + false + } + + fn error_type(&self) -> TokenStream { + quote!(LengthError) + } + + fn tokens(&self, expr: &TokenStream) -> TokenStream { let equal = if let Some(equal) = &self.equal { quote!(Some(#equal)) } else { diff --git a/packages/fortifier-macros/src/validations/url.rs b/packages/fortifier-macros/src/validations/url.rs index 290b84e..093cbbe 100644 --- a/packages/fortifier-macros/src/validations/url.rs +++ b/packages/fortifier-macros/src/validations/url.rs @@ -2,15 +2,25 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Result, meta::ParseNestedMeta}; +use crate::validation::Validation; + #[derive(Default)] pub struct Url {} -impl Url { - pub fn parse(_meta: &ParseNestedMeta<'_>) -> Result { +impl Validation for Url { + fn parse(_meta: &ParseNestedMeta<'_>) -> Result { Ok(Url::default()) } - pub fn tokens(&self, expr: &TokenStream) -> TokenStream { + fn is_async(&self) -> bool { + false + } + + fn error_type(&self) -> TokenStream { + quote!(UrlError) + } + + fn tokens(&self, expr: &TokenStream) -> TokenStream { quote! { #expr.validate_url() }