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
15 changes: 6 additions & 9 deletions packages/fortifier-macros/src/validate/field.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, Ident, Result};
use syn::{Field, Result};

use crate::validations::{Email, Length};

pub struct ValidateField {
ident: Ident,
expr: TokenStream,
// TODO: Consider using a trait for validations.
email: Option<Email>,
length: Option<Length>,
}

impl ValidateField {
pub fn parse(ident: Ident, field: &Field) -> Result<Self> {
pub fn parse(expr: TokenStream, field: &Field) -> Result<Self> {
let mut result = Self {
ident,
expr,
email: None,
length: None,
};
Expand Down Expand Up @@ -53,11 +53,8 @@ impl ValidateField {
}

pub fn sync_validations(&self) -> Vec<TokenStream> {
let email = self.email.as_ref().map(|email| email.tokens(&self.ident));
let length = self
.length
.as_ref()
.map(|length| length.tokens(&self.ident));
let email = self.email.as_ref().map(|email| email.tokens(&self.expr));
let length = self.length.as_ref().map(|length| length.tokens(&self.expr));

[email, length].into_iter().flatten().collect()
}
Expand Down
109 changes: 97 additions & 12 deletions packages/fortifier-macros/src/validate/struct.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use proc_macro2::{Literal, TokenStream};
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Ident, Result};

Expand Down Expand Up @@ -54,10 +54,11 @@ impl ValidateNamedStruct {
continue;
};

result.fields.insert(
field_ident.clone(),
ValidateField::parse(field_ident.clone(), field)?,
);
let expr = quote!(self.#field_ident);

result
.fields
.insert(field_ident.clone(), ValidateField::parse(expr, field)?);
}

Ok(result)
Expand Down Expand Up @@ -147,26 +148,108 @@ impl ToTokens for ValidateNamedStruct {
}

pub struct ValidateUnnamedStruct {
#[allow(unused)]
ident: Ident,
#[allow(unused)]
error_ident: Ident,
fields: Vec<ValidateField>,
}

impl ValidateUnnamedStruct {
fn parse(input: &DeriveInput, _data: &DataStruct, _fields: &FieldsUnnamed) -> Result<Self> {
let result = Self {
fn parse(input: &DeriveInput, _data: &DataStruct, fields: &FieldsUnnamed) -> Result<Self> {
let mut result = Self {
ident: input.ident.clone(),
error_ident: format_ident!("{}ValidationError", input.ident),
fields: Vec::default(),
};

for (index, field) in fields.unnamed.iter().enumerate() {
let index = Literal::usize_unsuffixed(index);
let expr = quote!(self.#index);

result.fields.push(ValidateField::parse(expr, field)?);
}

Ok(result)
}
}

impl ToTokens for ValidateUnnamedStruct {
fn to_tokens(&self, _tokens: &mut TokenStream) {
// TODO
fn to_tokens(&self, tokens: &mut TokenStream) {
let ident = &self.ident;
let error_ident = &self.error_ident;
let mut error_field_idents = vec![];
let mut error_field_types = vec![];
let mut sync_validations = vec![];
let mut async_validations = vec![];

for (index, field) in self.fields.iter().enumerate() {
let field_error_ident = format_ident!("F{index}");

error_field_idents.push(field_error_ident.clone());
error_field_types.push(field.error_type());

for validation in field.sync_validations() {
sync_validations.push(quote! {
if let Err(err) = #validation {
errors.push(#error_ident::#field_error_ident(err));
}
});
}

for validation in field.async_validations() {
async_validations.push(quote! {
if let Err(err) = #validation {
errors.push(#error_ident::#field_error_ident(err));
}
});
}
}

tokens.append_all(quote! {
use fortifier::*;

#[derive(Debug)]
enum #error_ident {
#( #error_field_idents(#error_field_types) ),*
}

impl ::std::fmt::Display for #error_ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{self:#?}")
}
}

impl ::std::error::Error for #error_ident {}

impl Validate for #ident {
type Error = #error_ident;

fn validate_sync(&self) -> Result<(), ValidationErrors<Self::Error>> {
let mut errors = vec![];

#(#sync_validations)*

if !errors.is_empty() {
Err(errors.into())
} else {
Ok(())
}
}

fn validate_async(&self) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ValidationErrors<Self::Error>>>>> {
Box::pin(async {
let mut errors = vec![];

#(#async_validations)*

if !errors.is_empty() {
Err(errors.into())
} else {
Ok(())
}
})
}
}
})
}
}

Expand All @@ -187,8 +270,10 @@ impl ToTokens for ValidateUnitStruct {
let ident = &self.ident;

tokens.append_all(quote! {
use fortifier::ValidationErrors;

impl Validate for #ident {
type Error = Infallible;
type Error = ::std::convert::Infallible;

fn validate_sync(&self) -> Result<(), ValidationErrors<Self::Error>> {
Ok(())
Expand Down
6 changes: 3 additions & 3 deletions packages/fortifier-macros/src/validations/email.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, Result, meta::ParseNestedMeta};
use syn::{Result, meta::ParseNestedMeta};

#[derive(Default)]
pub struct Email {}
Expand All @@ -10,9 +10,9 @@ impl Email {
Ok(Email::default())
}

pub fn tokens(&self, ident: &Ident) -> TokenStream {
pub fn tokens(&self, expr: &TokenStream) -> TokenStream {
quote! {
self.#ident.validate_email()
#expr.validate_email()
}
}
}
6 changes: 3 additions & 3 deletions packages/fortifier-macros/src/validations/length.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Expr, Ident, Result, meta::ParseNestedMeta};
use syn::{Expr, Result, meta::ParseNestedMeta};

#[derive(Default)]
pub struct Length {
Expand Down Expand Up @@ -37,7 +37,7 @@ impl Length {
Ok(result)
}

pub fn tokens(&self, ident: &Ident) -> TokenStream {
pub fn tokens(&self, expr: &TokenStream) -> TokenStream {
let equal = if let Some(equal) = &self.equal {
quote!(Some(#equal))
} else {
Expand All @@ -55,7 +55,7 @@ impl Length {
};

quote! {
self.#ident.validate_length(#equal, #min, #max)
#expr.validate_length(#equal, #min, #max)
}
}
}
14 changes: 14 additions & 0 deletions packages/fortifier-macros/tests/derive/struct_unit_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::error::Error;

use fortifier::Validate;

#[derive(Validate)]
struct CreateUser;

fn main() -> Result<(), Box<dyn Error>> {
let data = CreateUser;

data.validate_sync()?;

Ok(())
}
14 changes: 14 additions & 0 deletions packages/fortifier-macros/tests/derive/struct_unnamed_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::error::Error;

use fortifier::Validate;

#[derive(Validate)]
struct CreateUser(#[validate(length(min = 1, max = 256))] String);

fn main() -> Result<(), Box<dyn Error>> {
let data = CreateUser("John Doe".to_owned());

data.validate_sync()?;

Ok(())
}