Skip to content

Commit f15e0c7

Browse files
feat: support unnamed and unit structs
1 parent 7aced3d commit f15e0c7

File tree

7 files changed

+138
-25
lines changed

7 files changed

+138
-25
lines changed

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
use proc_macro2::TokenStream;
22
use quote::quote;
3-
use syn::{Field, Ident, Result};
3+
use syn::{Field, Result};
44

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

77
pub struct ValidateField {
8-
ident: Ident,
8+
expr: TokenStream,
99
// TODO: Consider using a trait for validations.
1010
email: Option<Email>,
1111
length: Option<Length>,
1212
}
1313

1414
impl ValidateField {
15-
pub fn parse(ident: Ident, field: &Field) -> Result<Self> {
15+
pub fn parse(expr: TokenStream, field: &Field) -> Result<Self> {
1616
let mut result = Self {
17-
ident,
17+
expr,
1818
email: None,
1919
length: None,
2020
};
@@ -53,11 +53,8 @@ impl ValidateField {
5353
}
5454

5555
pub fn sync_validations(&self) -> Vec<TokenStream> {
56-
let email = self.email.as_ref().map(|email| email.tokens(&self.ident));
57-
let length = self
58-
.length
59-
.as_ref()
60-
.map(|length| length.tokens(&self.ident));
56+
let email = self.email.as_ref().map(|email| email.tokens(&self.expr));
57+
let length = self.length.as_ref().map(|length| length.tokens(&self.expr));
6158

6259
[email, length].into_iter().flatten().collect()
6360
}

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

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashMap;
22

33
use convert_case::{Case, Casing};
4-
use proc_macro2::TokenStream;
4+
use proc_macro2::{Literal, TokenStream};
55
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
66
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Ident, Result};
77

@@ -54,10 +54,11 @@ impl ValidateNamedStruct {
5454
continue;
5555
};
5656

57-
result.fields.insert(
58-
field_ident.clone(),
59-
ValidateField::parse(field_ident.clone(), field)?,
60-
);
57+
let expr = quote!(self.#field_ident);
58+
59+
result
60+
.fields
61+
.insert(field_ident.clone(), ValidateField::parse(expr, field)?);
6162
}
6263

6364
Ok(result)
@@ -150,23 +151,108 @@ pub struct ValidateUnnamedStruct {
150151
#[allow(unused)]
151152
ident: Ident,
152153
#[allow(unused)]
154+
error_ident: Ident,
155+
#[allow(unused)]
153156
fields: Vec<ValidateField>,
154157
}
155158

156159
impl ValidateUnnamedStruct {
157-
fn parse(input: &DeriveInput, _data: &DataStruct, _fields: &FieldsUnnamed) -> Result<Self> {
158-
let result = Self {
160+
fn parse(input: &DeriveInput, _data: &DataStruct, fields: &FieldsUnnamed) -> Result<Self> {
161+
let mut result = Self {
159162
ident: input.ident.clone(),
163+
error_ident: format_ident!("{}ValidationError", input.ident),
160164
fields: Vec::default(),
161165
};
162166

167+
for (index, field) in fields.unnamed.iter().enumerate() {
168+
let index = Literal::usize_unsuffixed(index);
169+
let expr = quote!(self.#index);
170+
171+
result.fields.push(ValidateField::parse(expr, field)?);
172+
}
173+
163174
Ok(result)
164175
}
165176
}
166177

167178
impl ToTokens for ValidateUnnamedStruct {
168-
fn to_tokens(&self, _tokens: &mut TokenStream) {
169-
// TODO
179+
fn to_tokens(&self, tokens: &mut TokenStream) {
180+
let ident = &self.ident;
181+
let error_ident = &self.error_ident;
182+
let mut error_field_idents = vec![];
183+
let mut error_field_types = vec![];
184+
let mut sync_validations = vec![];
185+
let mut async_validations = vec![];
186+
187+
for (index, field) in self.fields.iter().enumerate() {
188+
let field_error_ident = format_ident!("F{index}");
189+
190+
error_field_idents.push(field_error_ident.clone());
191+
error_field_types.push(field.error_type());
192+
193+
for validation in field.sync_validations() {
194+
sync_validations.push(quote! {
195+
if let Err(err) = #validation {
196+
errors.push(#error_ident::#field_error_ident(err));
197+
}
198+
});
199+
}
200+
201+
for validation in field.async_validations() {
202+
async_validations.push(quote! {
203+
if let Err(err) = #validation {
204+
errors.push(#error_ident::#field_error_ident(err));
205+
}
206+
});
207+
}
208+
}
209+
210+
tokens.append_all(quote! {
211+
use fortifier::*;
212+
213+
#[derive(Debug)]
214+
enum #error_ident {
215+
#( #error_field_idents(#error_field_types) ),*
216+
}
217+
218+
impl ::std::fmt::Display for #error_ident {
219+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
220+
write!(f, "{self:#?}")
221+
}
222+
}
223+
224+
impl ::std::error::Error for #error_ident {}
225+
226+
impl Validate for #ident {
227+
type Error = #error_ident;
228+
229+
fn validate_sync(&self) -> Result<(), ValidationErrors<Self::Error>> {
230+
let mut errors = vec![];
231+
232+
#(#sync_validations)*
233+
234+
if !errors.is_empty() {
235+
Err(errors.into())
236+
} else {
237+
Ok(())
238+
}
239+
}
240+
241+
fn validate_async(&self) -> ::std::pin::Pin<Box<impl Future<Output = Result<(), ValidationErrors<Self::Error>>>>> {
242+
Box::pin(async {
243+
let mut errors = vec![];
244+
245+
#(#async_validations)*
246+
247+
if !errors.is_empty() {
248+
Err(errors.into())
249+
} else {
250+
Ok(())
251+
}
252+
})
253+
}
254+
}
255+
})
170256
}
171257
}
172258

@@ -187,8 +273,10 @@ impl ToTokens for ValidateUnitStruct {
187273
let ident = &self.ident;
188274

189275
tokens.append_all(quote! {
276+
use fortifier::ValidationErrors;
277+
190278
impl Validate for #ident {
191-
type Error = Infallible;
279+
type Error = ::std::convert::Infallible;
192280

193281
fn validate_sync(&self) -> Result<(), ValidationErrors<Self::Error>> {
194282
Ok(())
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use proc_macro2::TokenStream;
22
use quote::quote;
3-
use syn::{Ident, Result, meta::ParseNestedMeta};
3+
use syn::{Result, meta::ParseNestedMeta};
44

55
#[derive(Default)]
66
pub struct Email {}
@@ -10,9 +10,9 @@ impl Email {
1010
Ok(Email::default())
1111
}
1212

13-
pub fn tokens(&self, ident: &Ident) -> TokenStream {
13+
pub fn tokens(&self, expr: &TokenStream) -> TokenStream {
1414
quote! {
15-
self.#ident.validate_email()
15+
#expr.validate_email()
1616
}
1717
}
1818
}

packages/fortifier-macros/src/validations/length.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use proc_macro2::TokenStream;
22
use quote::quote;
3-
use syn::{Expr, Ident, Result, meta::ParseNestedMeta};
3+
use syn::{Expr, Result, meta::ParseNestedMeta};
44

55
#[derive(Default)]
66
pub struct Length {
@@ -37,7 +37,7 @@ impl Length {
3737
Ok(result)
3838
}
3939

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

5757
quote! {
58-
self.#ident.validate_length(#equal, #min, #max)
58+
#expr.validate_length(#equal, #min, #max)
5959
}
6060
}
6161
}

packages/fortifier-macros/tests/derive/basic_pass.rs renamed to packages/fortifier-macros/tests/derive/struct_named_pass.rs

File renamed without changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use std::error::Error;
2+
3+
use fortifier::Validate;
4+
5+
#[derive(Validate)]
6+
struct CreateUser;
7+
8+
fn main() -> Result<(), Box<dyn Error>> {
9+
let data = CreateUser;
10+
11+
data.validate_sync()?;
12+
13+
Ok(())
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use std::error::Error;
2+
3+
use fortifier::Validate;
4+
5+
#[derive(Validate)]
6+
struct CreateUser(#[validate(length(min = 1, max = 256))] String);
7+
8+
fn main() -> Result<(), Box<dyn Error>> {
9+
let data = CreateUser("John Doe".to_owned());
10+
11+
data.validate_sync()?;
12+
13+
Ok(())
14+
}

0 commit comments

Comments
 (0)