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
92 changes: 75 additions & 17 deletions packages/fortifier-macros/src/validate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod attributes;
mod data;
mod r#enum;
mod field;
mod fields;
Expand All @@ -7,33 +8,90 @@ mod r#type;
mod r#union;

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{Data, DeriveInput, Result};
use quote::{ToTokens, TokenStreamExt, quote};
use syn::{DeriveInput, Generics, Ident, Result, Type, TypeTuple, punctuated::Punctuated};

use crate::validate::{r#enum::ValidateEnum, r#struct::ValidateStruct, union::ValidateUnion};
use crate::{validate::data::ValidateData, validation::Execution};

pub enum Validate<'a> {
Struct(ValidateStruct<'a>),
Enum(ValidateEnum<'a>),
Union(ValidateUnion),
pub struct Validate<'a> {
ident: &'a Ident,
generics: &'a Generics,
context_type: Option<Type>,
data: ValidateData<'a>,
}

impl<'a> Validate<'a> {
pub fn parse(input: &'a DeriveInput) -> Result<Self> {
Ok(match &input.data {
Data::Struct(data) => Self::Struct(ValidateStruct::parse(input, data)?),
Data::Enum(data) => Self::Enum(ValidateEnum::parse(input, data)?),
Data::Union(data) => Self::Union(ValidateUnion::parse(input, data)?),
})
let mut result = Validate {
ident: &input.ident,
generics: &input.generics,
context_type: None,
data: ValidateData::parse(input)?,
};

for attribute in &input.attrs {
if !attribute.path().is_ident("validate") {
continue;
}

attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("context") {
result.context_type = Some(meta.value()?.parse()?);

Ok(())
} else {
Err(meta.error("unknown parameter"))
}
})?;
}

Ok(result)
}
}

impl<'a> ToTokens for Validate<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Validate::Struct(r#struct) => r#struct.to_tokens(tokens),
Validate::Enum(r#enum) => r#enum.to_tokens(tokens),
Validate::Union(r#union) => r#union.to_tokens(tokens),
}
let ident = &self.ident;
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();

let context_type = match &self.context_type {
Some(context_type) => context_type,
None => &Type::Tuple(TypeTuple {
paren_token: Default::default(),
elems: Punctuated::new(),
}),
};
let (error_type, error_definition) = self.data.error_type();
let sync_validations = self.data.validations(Execution::Sync);
let async_validations = self.data.validations(Execution::Async);

let no_context_impl = self.context_type.is_none().then(|| {
quote! {
#[automatically_derived]
impl #impl_generics ::fortifier::Validate for #ident #type_generics #where_clause {}
}
});

tokens.append_all(quote! {
#error_definition

#[automatically_derived]
impl #impl_generics ::fortifier::ValidateWithContext for #ident #type_generics #where_clause {
type Context = #context_type;
type Error = #error_type;

fn validate_sync_with_context(&self, context: &Self::Context) -> Result<(), ::fortifier::ValidationErrors<Self::Error>> {
#sync_validations
}

fn validate_async_with_context(&self, context: &Self::Context) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ::fortifier::ValidationErrors<Self::Error>>>>> {
Box::pin(async move {
#async_validations
})
}
}

#no_context_impl
})
}
}
39 changes: 39 additions & 0 deletions packages/fortifier-macros/src/validate/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use proc_macro2::TokenStream;
use syn::{Data, DeriveInput, Result};

use crate::{
validate::{r#enum::ValidateEnum, r#struct::ValidateStruct, union::ValidateUnion},
validation::Execution,
};

pub enum ValidateData<'a> {
Struct(ValidateStruct<'a>),
Enum(ValidateEnum<'a>),
Union(ValidateUnion),
}

impl<'a> ValidateData<'a> {
pub fn parse(input: &'a DeriveInput) -> Result<Self> {
Ok(match &input.data {
Data::Struct(data) => Self::Struct(ValidateStruct::parse(input, data)?),
Data::Enum(data) => Self::Enum(ValidateEnum::parse(input, data)?),
Data::Union(data) => Self::Union(ValidateUnion::parse(input, data)?),
})
}

pub fn error_type(&self) -> (TokenStream, TokenStream) {
match self {
ValidateData::Struct(r#struct) => r#struct.error_type(),
ValidateData::Enum(r#enum) => r#enum.error_type(),
ValidateData::Union(r#union) => r#union.error_type(),
}
}

pub fn validations(&self, execution: Execution) -> TokenStream {
match self {
ValidateData::Struct(r#struct) => r#struct.validations(execution),
ValidateData::Enum(r#enum) => r#enum.validations(execution),
ValidateData::Union(r#union) => r#union.validations(execution),
}
}
}
62 changes: 16 additions & 46 deletions packages/fortifier-macros/src/validate/enum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
use syn::{DataEnum, DeriveInput, Generics, Ident, Result, Variant, Visibility};
use quote::{ToTokens, format_ident, quote};
use syn::{DataEnum, DeriveInput, Ident, Result, Variant, Visibility};

use crate::{
validate::{
Expand All @@ -15,7 +15,6 @@ pub struct ValidateEnum<'a> {
visibility: &'a Visibility,
ident: &'a Ident,
error_ident: Ident,
generics: &'a Generics,
variants: Vec<ValidateEnumVariant<'a>>,
}

Expand All @@ -25,7 +24,6 @@ impl<'a> ValidateEnum<'a> {
visibility: &input.vis,
ident: &input.ident,
error_ident: format_ident!("{}ValidationError", input.ident),
generics: &input.generics,
variants: Vec::with_capacity(data.variants.len()),
};

Expand All @@ -41,7 +39,7 @@ impl<'a> ValidateEnum<'a> {
Ok(result)
}

fn error_type(&self) -> (&Ident, TokenStream) {
pub fn error_type(&self) -> (TokenStream, TokenStream) {
let visibility = &self.visibility;
let error_ident = &self.error_ident;

Expand All @@ -51,14 +49,14 @@ impl<'a> ValidateEnum<'a> {
.iter()
.map(|variant| &variant.ident)
.collect::<Vec<_>>();
let error_variant_types = self
let (error_variant_types, variant_error_types): (Vec<_>, Vec<_>) = self
.variants
.iter()
.map(|variant| variant.error_type().0)
.collect::<Vec<_>>();
.map(|variant| variant.error_type())
.unzip();

(
error_ident,
error_ident.to_token_stream(),
quote! {
#[allow(dead_code)]
#[derive(Debug, PartialEq)]
Expand All @@ -76,51 +74,23 @@ impl<'a> ValidateEnum<'a> {

#[automatically_derived]
impl ::std::error::Error for #error_ident {}

#( #variant_error_types )*
},
)
}
}

impl<'a> ToTokens for ValidateEnum<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ident = &self.ident;
let (impl_generics, type_generics, where_clause) = &self.generics.split_for_impl();

let (error_ident, error_type) = self.error_type();
let variant_error_types = self.variants.iter().map(|variant| variant.error_type().1);
let sync_variant_match_arms = self
.variants
.iter()
.map(|variant| variant.match_arm(Execution::Sync));
let async_variant_match_arms = self
pub fn validations(&self, execution: Execution) -> TokenStream {
let variant_match_arms = self
.variants
.iter()
.map(|variant| variant.match_arm(Execution::Async));

tokens.append_all(quote! {
#error_type
.map(|variant| variant.match_arm(execution));

#( #variant_error_types )*

#[automatically_derived]
impl #impl_generics ::fortifier::Validate for #ident #type_generics #where_clause {
type Error = #error_ident;

fn validate_sync(&self) -> Result<(), ::fortifier::ValidationErrors<Self::Error>> {
match &self {
#( #sync_variant_match_arms ),*
}
}

fn validate_async(&self) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ::fortifier::ValidationErrors<Self::Error>>>>> {
Box::pin(async move {
match &self {
#( #async_variant_match_arms ),*
}
})
}
quote! {
match &self {
#( #variant_match_arms ),*
}
})
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions packages/fortifier-macros/src/validate/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use syn::{Field, Ident, Result, Visibility};
use crate::{
validate::{attributes::enum_attributes, r#type::should_validate_type},
validation::{Execution, Validation},
validations::{Custom, Email, Length, Regex, Url},
validations::{Custom, Email, Length, Nested, Regex, Url},
};

pub enum LiteralOrIdent {
Expand Down Expand Up @@ -96,8 +96,11 @@ impl<'a> ValidateField<'a> {
}
}

if !skip && should_validate_type(&field.ty) {
// TODO: Use enum/struct generics to determine if a generic field type supports nested validation.
// TODO: Remove the validations empty check after resolving the issue above.
if !skip && result.validations.is_empty() && should_validate_type(&field.ty) {
// TODO: Nested validation
result.validations.push(Box::new(Nested::new()));
}

Ok(result)
Expand Down
48 changes: 7 additions & 41 deletions packages/fortifier-macros/src/validate/struct.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,30 @@
use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt, quote};
use syn::{DataStruct, DeriveInput, Generics, Ident, Result};
use syn::{DataStruct, DeriveInput, Result};

use crate::{
validate::{field::ValidateFieldPrefix, fields::ValidateFields},
validation::Execution,
};

pub struct ValidateStruct<'a> {
ident: &'a Ident,
generics: &'a Generics,
fields: ValidateFields<'a>,
}

impl<'a> ValidateStruct<'a> {
pub fn parse(input: &'a DeriveInput, data: &'a DataStruct) -> Result<Self> {
Ok(ValidateStruct {
ident: &input.ident,
generics: &input.generics,
fields: ValidateFields::parse(&input.vis, input.ident.clone(), &data.fields)?,
})
}
}

impl<'a> ToTokens for ValidateStruct<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ident = &self.ident;
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
pub fn error_type(&self) -> (TokenStream, TokenStream) {
self.fields.error_type()
}

pub fn validations(&self, execution: Execution) -> TokenStream {
let error_wrapper = |tokens| tokens;

let (error_ident, error_type) = self.fields.error_type();
let sync_validations = self.fields.validations(
Execution::Sync,
ValidateFieldPrefix::SelfKeyword,
&error_wrapper,
);
let async_validations = self.fields.validations(
Execution::Async,
ValidateFieldPrefix::SelfKeyword,
&error_wrapper,
);

tokens.append_all(quote! {
#error_type

#[automatically_derived]
impl #impl_generics ::fortifier::Validate for #ident #type_generics #where_clause {
type Error = #error_ident;

fn validate_sync(&self) -> Result<(), ::fortifier::ValidationErrors<Self::Error>> {
#sync_validations
}

fn validate_async(&self) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ::fortifier::ValidationErrors<Self::Error>>>>> {
Box::pin(async {
#async_validations
})
}
}
})
self.fields
.validations(execution, ValidateFieldPrefix::SelfKeyword, &error_wrapper)
}
}
17 changes: 10 additions & 7 deletions packages/fortifier-macros/src/validate/union.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{DataUnion, DeriveInput, Result};

use crate::validation::Execution;

pub struct ValidateUnion {}

impl ValidateUnion {
pub fn parse(_input: &DeriveInput, _data: &DataUnion) -> Result<Self> {
todo!("union")
pub fn parse(input: &DeriveInput, _data: &DataUnion) -> Result<Self> {
Err(syn::Error::new_spanned(input, "union is not supported"))
}

pub fn error_type(&self) -> (TokenStream, TokenStream) {
todo!()
}
}

impl ToTokens for ValidateUnion {
fn to_tokens(&self, _tokens: &mut TokenStream) {
// TODO
pub fn validations(&self, _execution: Execution) -> TokenStream {
todo!()
}
}
Loading