Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/fortifier-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Fortifier macros.

mod validate;
mod validation;
mod validations;

use proc_macro::TokenStream;
Expand Down
49 changes: 23 additions & 26 deletions packages/fortifier-macros/src/validate/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Email>,
length: Option<Length>,
url: Option<Url>,
validations: Vec<Box<dyn Validation>>,
}

impl ValidateField {
pub fn parse(expr: TokenStream, field: &Field) -> Result<Self> {
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 {
Expand All @@ -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<usize>)
} 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<TokenStream> {
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<TokenStream> {
vec![]
self.validations
.iter()
.filter(|validation| validation.is_async())
.map(|validation| validation.tokens(&self.expr))
.collect()
}
}
14 changes: 14 additions & 0 deletions packages/fortifier-macros/src/validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use proc_macro2::TokenStream;
use syn::{Result, meta::ParseNestedMeta};

pub trait Validation {
fn parse(_meta: &ParseNestedMeta<'_>) -> Result<Self>
where
Self: Sized;

fn is_async(&self) -> bool;

fn error_type(&self) -> TokenStream;

fn tokens(&self, expr: &TokenStream) -> TokenStream;
}
16 changes: 13 additions & 3 deletions packages/fortifier-macros/src/validations/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Email> {
impl Validation for Email {
fn parse(_meta: &ParseNestedMeta<'_>) -> Result<Self> {
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()
}
Expand Down
16 changes: 13 additions & 3 deletions packages/fortifier-macros/src/validations/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ 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<Expr>,
pub min: Option<Expr>,
pub max: Option<Expr>,
}

impl Length {
pub fn parse(meta: &ParseNestedMeta<'_>) -> Result<Length> {
impl Validation for Length {
fn parse(meta: &ParseNestedMeta<'_>) -> Result<Self> {
let mut result = Length::default();

meta.parse_nested_meta(|meta| {
Expand All @@ -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<usize>)
}

fn tokens(&self, expr: &TokenStream) -> TokenStream {
let equal = if let Some(equal) = &self.equal {
quote!(Some(#equal))
} else {
Expand Down
16 changes: 13 additions & 3 deletions packages/fortifier-macros/src/validations/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Url> {
impl Validation for Url {
fn parse(_meta: &ParseNestedMeta<'_>) -> Result<Self> {
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()
}
Expand Down