Skip to content

Commit d756fd6

Browse files
feat: add context (#25)
1 parent 44a4ff2 commit d756fd6

10 files changed

Lines changed: 258 additions & 121 deletions

File tree

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod attributes;
2+
mod data;
23
mod r#enum;
34
mod field;
45
mod fields;
@@ -7,33 +8,90 @@ mod r#type;
78
mod r#union;
89

910
use proc_macro2::TokenStream;
10-
use quote::ToTokens;
11-
use syn::{Data, DeriveInput, Result};
11+
use quote::{ToTokens, TokenStreamExt, quote};
12+
use syn::{DeriveInput, Generics, Ident, Result, Type, TypeTuple, punctuated::Punctuated};
1213

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

15-
pub enum Validate<'a> {
16-
Struct(ValidateStruct<'a>),
17-
Enum(ValidateEnum<'a>),
18-
Union(ValidateUnion),
16+
pub struct Validate<'a> {
17+
ident: &'a Ident,
18+
generics: &'a Generics,
19+
context_type: Option<Type>,
20+
data: ValidateData<'a>,
1921
}
2022

2123
impl<'a> Validate<'a> {
2224
pub fn parse(input: &'a DeriveInput) -> Result<Self> {
23-
Ok(match &input.data {
24-
Data::Struct(data) => Self::Struct(ValidateStruct::parse(input, data)?),
25-
Data::Enum(data) => Self::Enum(ValidateEnum::parse(input, data)?),
26-
Data::Union(data) => Self::Union(ValidateUnion::parse(input, data)?),
27-
})
25+
let mut result = Validate {
26+
ident: &input.ident,
27+
generics: &input.generics,
28+
context_type: None,
29+
data: ValidateData::parse(input)?,
30+
};
31+
32+
for attribute in &input.attrs {
33+
if !attribute.path().is_ident("validate") {
34+
continue;
35+
}
36+
37+
attribute.parse_nested_meta(|meta| {
38+
if meta.path.is_ident("context") {
39+
result.context_type = Some(meta.value()?.parse()?);
40+
41+
Ok(())
42+
} else {
43+
Err(meta.error("unknown parameter"))
44+
}
45+
})?;
46+
}
47+
48+
Ok(result)
2849
}
2950
}
3051

3152
impl<'a> ToTokens for Validate<'a> {
3253
fn to_tokens(&self, tokens: &mut TokenStream) {
33-
match self {
34-
Validate::Struct(r#struct) => r#struct.to_tokens(tokens),
35-
Validate::Enum(r#enum) => r#enum.to_tokens(tokens),
36-
Validate::Union(r#union) => r#union.to_tokens(tokens),
37-
}
54+
let ident = &self.ident;
55+
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
56+
57+
let context_type = match &self.context_type {
58+
Some(context_type) => context_type,
59+
None => &Type::Tuple(TypeTuple {
60+
paren_token: Default::default(),
61+
elems: Punctuated::new(),
62+
}),
63+
};
64+
let (error_type, error_definition) = self.data.error_type();
65+
let sync_validations = self.data.validations(Execution::Sync);
66+
let async_validations = self.data.validations(Execution::Async);
67+
68+
let no_context_impl = self.context_type.is_none().then(|| {
69+
quote! {
70+
#[automatically_derived]
71+
impl #impl_generics ::fortifier::Validate for #ident #type_generics #where_clause {}
72+
}
73+
});
74+
75+
tokens.append_all(quote! {
76+
#error_definition
77+
78+
#[automatically_derived]
79+
impl #impl_generics ::fortifier::ValidateWithContext for #ident #type_generics #where_clause {
80+
type Context = #context_type;
81+
type Error = #error_type;
82+
83+
fn validate_sync_with_context(&self, context: &Self::Context) -> Result<(), ::fortifier::ValidationErrors<Self::Error>> {
84+
#sync_validations
85+
}
86+
87+
fn validate_async_with_context(&self, context: &Self::Context) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ::fortifier::ValidationErrors<Self::Error>>>>> {
88+
Box::pin(async move {
89+
#async_validations
90+
})
91+
}
92+
}
93+
94+
#no_context_impl
95+
})
3896
}
3997
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use proc_macro2::TokenStream;
2+
use syn::{Data, DeriveInput, Result};
3+
4+
use crate::{
5+
validate::{r#enum::ValidateEnum, r#struct::ValidateStruct, union::ValidateUnion},
6+
validation::Execution,
7+
};
8+
9+
pub enum ValidateData<'a> {
10+
Struct(ValidateStruct<'a>),
11+
Enum(ValidateEnum<'a>),
12+
Union(ValidateUnion),
13+
}
14+
15+
impl<'a> ValidateData<'a> {
16+
pub fn parse(input: &'a DeriveInput) -> Result<Self> {
17+
Ok(match &input.data {
18+
Data::Struct(data) => Self::Struct(ValidateStruct::parse(input, data)?),
19+
Data::Enum(data) => Self::Enum(ValidateEnum::parse(input, data)?),
20+
Data::Union(data) => Self::Union(ValidateUnion::parse(input, data)?),
21+
})
22+
}
23+
24+
pub fn error_type(&self) -> (TokenStream, TokenStream) {
25+
match self {
26+
ValidateData::Struct(r#struct) => r#struct.error_type(),
27+
ValidateData::Enum(r#enum) => r#enum.error_type(),
28+
ValidateData::Union(r#union) => r#union.error_type(),
29+
}
30+
}
31+
32+
pub fn validations(&self, execution: Execution) -> TokenStream {
33+
match self {
34+
ValidateData::Struct(r#struct) => r#struct.validations(execution),
35+
ValidateData::Enum(r#enum) => r#enum.validations(execution),
36+
ValidateData::Union(r#union) => r#union.validations(execution),
37+
}
38+
}
39+
}

packages/fortifier-macros/src/validate/enum.rs

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use proc_macro2::TokenStream;
2-
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
3-
use syn::{DataEnum, DeriveInput, Generics, Ident, Result, Variant, Visibility};
2+
use quote::{ToTokens, format_ident, quote};
3+
use syn::{DataEnum, DeriveInput, Ident, Result, Variant, Visibility};
44

55
use crate::{
66
validate::{
@@ -15,7 +15,6 @@ pub struct ValidateEnum<'a> {
1515
visibility: &'a Visibility,
1616
ident: &'a Ident,
1717
error_ident: Ident,
18-
generics: &'a Generics,
1918
variants: Vec<ValidateEnumVariant<'a>>,
2019
}
2120

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

@@ -41,7 +39,7 @@ impl<'a> ValidateEnum<'a> {
4139
Ok(result)
4240
}
4341

44-
fn error_type(&self) -> (&Ident, TokenStream) {
42+
pub fn error_type(&self) -> (TokenStream, TokenStream) {
4543
let visibility = &self.visibility;
4644
let error_ident = &self.error_ident;
4745

@@ -51,14 +49,14 @@ impl<'a> ValidateEnum<'a> {
5149
.iter()
5250
.map(|variant| &variant.ident)
5351
.collect::<Vec<_>>();
54-
let error_variant_types = self
52+
let (error_variant_types, variant_error_types): (Vec<_>, Vec<_>) = self
5553
.variants
5654
.iter()
57-
.map(|variant| variant.error_type().0)
58-
.collect::<Vec<_>>();
55+
.map(|variant| variant.error_type())
56+
.unzip();
5957

6058
(
61-
error_ident,
59+
error_ident.to_token_stream(),
6260
quote! {
6361
#[allow(dead_code)]
6462
#[derive(Debug, PartialEq)]
@@ -76,51 +74,23 @@ impl<'a> ValidateEnum<'a> {
7674

7775
#[automatically_derived]
7876
impl ::std::error::Error for #error_ident {}
77+
78+
#( #variant_error_types )*
7979
},
8080
)
8181
}
82-
}
83-
84-
impl<'a> ToTokens for ValidateEnum<'a> {
85-
fn to_tokens(&self, tokens: &mut TokenStream) {
86-
let ident = &self.ident;
87-
let (impl_generics, type_generics, where_clause) = &self.generics.split_for_impl();
8882

89-
let (error_ident, error_type) = self.error_type();
90-
let variant_error_types = self.variants.iter().map(|variant| variant.error_type().1);
91-
let sync_variant_match_arms = self
92-
.variants
93-
.iter()
94-
.map(|variant| variant.match_arm(Execution::Sync));
95-
let async_variant_match_arms = self
83+
pub fn validations(&self, execution: Execution) -> TokenStream {
84+
let variant_match_arms = self
9685
.variants
9786
.iter()
98-
.map(|variant| variant.match_arm(Execution::Async));
99-
100-
tokens.append_all(quote! {
101-
#error_type
87+
.map(|variant| variant.match_arm(execution));
10288

103-
#( #variant_error_types )*
104-
105-
#[automatically_derived]
106-
impl #impl_generics ::fortifier::Validate for #ident #type_generics #where_clause {
107-
type Error = #error_ident;
108-
109-
fn validate_sync(&self) -> Result<(), ::fortifier::ValidationErrors<Self::Error>> {
110-
match &self {
111-
#( #sync_variant_match_arms ),*
112-
}
113-
}
114-
115-
fn validate_async(&self) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ::fortifier::ValidationErrors<Self::Error>>>>> {
116-
Box::pin(async move {
117-
match &self {
118-
#( #async_variant_match_arms ),*
119-
}
120-
})
121-
}
89+
quote! {
90+
match &self {
91+
#( #variant_match_arms ),*
12292
}
123-
})
93+
}
12494
}
12595
}
12696

packages/fortifier-macros/src/validate/field.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use syn::{Field, Ident, Result, Visibility};
66
use crate::{
77
validate::{attributes::enum_attributes, r#type::should_validate_type},
88
validation::{Execution, Validation},
9-
validations::{Custom, Email, Length, Regex, Url},
9+
validations::{Custom, Email, Length, Nested, Regex, Url},
1010
};
1111

1212
pub enum LiteralOrIdent {
@@ -96,8 +96,11 @@ impl<'a> ValidateField<'a> {
9696
}
9797
}
9898

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

103106
Ok(result)
Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,30 @@
11
use proc_macro2::TokenStream;
2-
use quote::{ToTokens, TokenStreamExt, quote};
3-
use syn::{DataStruct, DeriveInput, Generics, Ident, Result};
2+
use syn::{DataStruct, DeriveInput, Result};
43

54
use crate::{
65
validate::{field::ValidateFieldPrefix, fields::ValidateFields},
76
validation::Execution,
87
};
98

109
pub struct ValidateStruct<'a> {
11-
ident: &'a Ident,
12-
generics: &'a Generics,
1310
fields: ValidateFields<'a>,
1411
}
1512

1613
impl<'a> ValidateStruct<'a> {
1714
pub fn parse(input: &'a DeriveInput, data: &'a DataStruct) -> Result<Self> {
1815
Ok(ValidateStruct {
19-
ident: &input.ident,
20-
generics: &input.generics,
2116
fields: ValidateFields::parse(&input.vis, input.ident.clone(), &data.fields)?,
2217
})
2318
}
24-
}
2519

26-
impl<'a> ToTokens for ValidateStruct<'a> {
27-
fn to_tokens(&self, tokens: &mut TokenStream) {
28-
let ident = &self.ident;
29-
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
20+
pub fn error_type(&self) -> (TokenStream, TokenStream) {
21+
self.fields.error_type()
22+
}
3023

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

33-
let (error_ident, error_type) = self.fields.error_type();
34-
let sync_validations = self.fields.validations(
35-
Execution::Sync,
36-
ValidateFieldPrefix::SelfKeyword,
37-
&error_wrapper,
38-
);
39-
let async_validations = self.fields.validations(
40-
Execution::Async,
41-
ValidateFieldPrefix::SelfKeyword,
42-
&error_wrapper,
43-
);
44-
45-
tokens.append_all(quote! {
46-
#error_type
47-
48-
#[automatically_derived]
49-
impl #impl_generics ::fortifier::Validate for #ident #type_generics #where_clause {
50-
type Error = #error_ident;
51-
52-
fn validate_sync(&self) -> Result<(), ::fortifier::ValidationErrors<Self::Error>> {
53-
#sync_validations
54-
}
55-
56-
fn validate_async(&self) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ::fortifier::ValidationErrors<Self::Error>>>>> {
57-
Box::pin(async {
58-
#async_validations
59-
})
60-
}
61-
}
62-
})
27+
self.fields
28+
.validations(execution, ValidateFieldPrefix::SelfKeyword, &error_wrapper)
6329
}
6430
}
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
use proc_macro2::TokenStream;
2-
use quote::ToTokens;
32
use syn::{DataUnion, DeriveInput, Result};
43

4+
use crate::validation::Execution;
5+
56
pub struct ValidateUnion {}
67

78
impl ValidateUnion {
8-
pub fn parse(_input: &DeriveInput, _data: &DataUnion) -> Result<Self> {
9-
todo!("union")
9+
pub fn parse(input: &DeriveInput, _data: &DataUnion) -> Result<Self> {
10+
Err(syn::Error::new_spanned(input, "union is not supported"))
11+
}
12+
13+
pub fn error_type(&self) -> (TokenStream, TokenStream) {
14+
todo!()
1015
}
11-
}
1216

13-
impl ToTokens for ValidateUnion {
14-
fn to_tokens(&self, _tokens: &mut TokenStream) {
15-
// TODO
17+
pub fn validations(&self, _execution: Execution) -> TokenStream {
18+
todo!()
1619
}
1720
}

0 commit comments

Comments
 (0)