Skip to main content

web_core_validator_derive/
lib.rs

1use darling::FromDeriveInput;
2use darling::ast::Data;
3use darling::util::{Override, WithOriginal};
4use proc_macro_error2::{abort, proc_macro_error};
5use quote::{ToTokens, quote};
6use syn::{DeriveInput, Field, GenericParam, Path, PathArguments, parse_macro_input};
7
8use tokens::cards::credit_card_tokens;
9use tokens::contains::contains_tokens;
10use tokens::custom::custom_tokens;
11use tokens::does_not_contain::does_not_contain_tokens;
12use tokens::email::email_tokens;
13use tokens::ip::ip_tokens;
14use tokens::length::length_tokens;
15use tokens::must_match::must_match_tokens;
16use tokens::nested::nested_tokens;
17use tokens::non_control_character::non_control_char_tokens;
18use tokens::range::range_tokens;
19use tokens::regex::regex_tokens;
20use tokens::required::required_tokens;
21use tokens::schema::schema_tokens;
22use tokens::url::url_tokens;
23use types::*;
24use utils::{CrateName, quote_use_stmts};
25
26mod tokens;
27mod types;
28mod utils;
29
30impl ToTokens for ValidateField {
31    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
32        let field_name = self.ident.clone().unwrap();
33        let field_name_str = self.ident.clone().unwrap().to_string();
34
35        let type_name = self.ty.to_token_stream().to_string();
36        let is_number = NUMBER_TYPES.contains(&type_name);
37
38        let (actual_field, wrapper_closure) = self.if_let_option_wrapper(&field_name, is_number);
39
40        // Length validation
41        let length = if let Some(length) = self.length.clone() {
42            wrapper_closure(length_tokens(
43                &self.crate_name,
44                length,
45                &actual_field,
46                &field_name_str,
47            ))
48        } else {
49            quote!()
50        };
51
52        // Email validation
53        let email = if let Some(email) = self.email.clone() {
54            wrapper_closure(email_tokens(
55                &self.crate_name,
56                match email {
57                    Override::Inherit => Email::default(),
58                    Override::Explicit(e) => e,
59                },
60                &actual_field,
61                &field_name_str,
62            ))
63        } else {
64            quote!()
65        };
66
67        // Credit card validation
68        let card = if let Some(credit_card) = self.credit_card.clone() {
69            wrapper_closure(credit_card_tokens(
70                &self.crate_name,
71                match credit_card {
72                    Override::Inherit => Card::default(),
73                    Override::Explicit(c) => c,
74                },
75                &actual_field,
76                &field_name_str,
77            ))
78        } else {
79            quote!()
80        };
81
82        // Url validation
83        let url = if let Some(url) = self.url.clone() {
84            wrapper_closure(url_tokens(
85                &self.crate_name,
86                match url {
87                    Override::Inherit => Url::default(),
88                    Override::Explicit(u) => u,
89                },
90                &actual_field,
91                &field_name_str,
92            ))
93        } else {
94            quote!()
95        };
96
97        // Ip address validation
98        let ip = if let Some(ip) = self.ip.clone() {
99            wrapper_closure(ip_tokens(
100                &self.crate_name,
101                match ip {
102                    Override::Inherit => Ip::default(),
103                    Override::Explicit(i) => i,
104                },
105                &actual_field,
106                &field_name_str,
107            ))
108        } else {
109            quote!()
110        };
111
112        // Non control character validation
113        let ncc = if let Some(ncc) = self.non_control_character.clone() {
114            wrapper_closure(non_control_char_tokens(
115                &self.crate_name,
116                match ncc {
117                    Override::Inherit => NonControlCharacter::default(),
118                    Override::Explicit(n) => n,
119                },
120                &actual_field,
121                &field_name_str,
122            ))
123        } else {
124            quote!()
125        };
126
127        // Range validation
128        let range = if let Some(range) = self.range.clone() {
129            wrapper_closure(range_tokens(
130                &self.crate_name,
131                range,
132                &actual_field,
133                &field_name_str,
134            ))
135        } else {
136            quote!()
137        };
138
139        // Required validation
140        let required = if let Some(required) = self.required.clone() {
141            required_tokens(
142                &self.crate_name,
143                match required {
144                    Override::Inherit => Required::default(),
145                    Override::Explicit(r) => r,
146                },
147                &field_name,
148                &field_name_str,
149            )
150        } else {
151            quote!()
152        };
153
154        // Contains validation
155        let contains = if let Some(contains) = self.contains.clone() {
156            wrapper_closure(contains_tokens(
157                &self.crate_name,
158                contains,
159                &actual_field,
160                &field_name_str,
161            ))
162        } else {
163            quote!()
164        };
165
166        // Does not contain validation
167        let does_not_contain = if let Some(does_not_contain) = self.does_not_contain.clone() {
168            wrapper_closure(does_not_contain_tokens(
169                &self.crate_name,
170                does_not_contain,
171                &actual_field,
172                &field_name_str,
173            ))
174        } else {
175            quote!()
176        };
177
178        // Must match validation
179        let must_match = if let Some(must_match) = self.must_match.clone() {
180            // TODO: handle option for other
181            wrapper_closure(must_match_tokens(
182                &self.crate_name,
183                must_match,
184                &actual_field,
185                &field_name_str,
186            ))
187        } else {
188            quote!()
189        };
190
191        // Regex validation
192        let regex = if let Some(regex) = self.regex.clone() {
193            wrapper_closure(regex_tokens(
194                &self.crate_name,
195                regex,
196                &actual_field,
197                &field_name_str,
198            ))
199        } else {
200            quote!()
201        };
202
203        // Custom validation
204        let mut custom = quote!();
205        // We try to be smart when passing arguments
206        let is_cow = type_name.contains("Cow <");
207        let custom_actual_field = if is_cow {
208            quote!(#actual_field.as_ref())
209        } else if is_number || type_name.starts_with("&") {
210            quote!(#actual_field)
211        } else {
212            quote!(&#actual_field)
213        };
214
215        for c in &self.custom {
216            let tokens = custom_tokens(c.clone(), &custom_actual_field, &field_name_str);
217            custom = quote!(
218                #custom
219
220                #tokens
221            );
222        }
223        if !self.custom.is_empty() {
224            custom = wrapper_closure(custom);
225        }
226
227        let nested = if let Some(n) = &self.nested {
228            wrapper_closure(nested_tokens(
229                &actual_field,
230                &field_name_str,
231                n.use_context(),
232                self.is_option(),
233                self.is_vec(),
234            ))
235        } else {
236            quote!()
237        };
238
239        tokens.extend(quote! {
240            #length
241            #email
242            #card
243            #url
244            #ip
245            #ncc
246            #range
247            #required
248            #contains
249            #does_not_contain
250            #must_match
251            #regex
252            #custom
253            #nested
254        });
255    }
256}
257
258// The main struct we get from parsing the attributes
259// The "supports(struct_named)" attribute guarantees only named structs to work with this macro
260#[derive(Debug, FromDeriveInput)]
261#[darling(attributes(validate), supports(struct_named))]
262#[darling(and_then = "ValidationData::validate")]
263struct ValidationData {
264    ident: syn::Ident,
265    generics: syn::Generics,
266    data: Data<(), WithOriginal<ValidateField, syn::Field>>,
267    #[darling(multiple)]
268    schema: Vec<Schema>,
269    context: Option<Path>,
270    nest_all_fields: Option<bool>,
271    /// The name of the crate to use for the generated code,
272    /// defaults to `validator`.
273    #[darling(rename = "crate", default)]
274    crate_name: CrateName,
275}
276
277impl ValidationData {
278    fn validate(self) -> darling::Result<Self> {
279        if let Some(context) = &self.context {
280            // Check if context lifetime is not `'v_a`
281            for segment in &context.segments {
282                if let PathArguments::AngleBracketed(args) = &segment.arguments {
283                    for arg in &args.args {
284                        if let syn::GenericArgument::Lifetime(lt) = arg {
285                            if lt.ident != "v_a" {
286                                abort! {
287                                    lt.ident, "Invalid argument reference";
288                                    note = "The lifetime `'{}` is not supported.", lt.ident;
289                                    help = "Please use the validator lifetime `'v_a`";
290                                }
291                            }
292                        }
293                    }
294                }
295            }
296        }
297
298        if let Data::Struct(fields) = &self.data {
299            let original_fields: Vec<&Field> = fields.fields.iter().map(|f| &f.original).collect();
300            for f in &fields.fields {
301                f.parsed.validate(
302                    &self.ident,
303                    &original_fields,
304                    &f.original,
305                    self.context.is_some(),
306                );
307            }
308        }
309
310        Ok(self)
311    }
312}
313
314#[proc_macro_error]
315#[proc_macro_derive(Validate, attributes(validate))]
316pub fn derive_validation(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
317    let input: DeriveInput = parse_macro_input!(input);
318
319    // parse the input to the ValidationData struct defined above
320    let validation_data = match ValidationData::from_derive_input(&input) {
321        Ok(data) => data,
322        Err(e) => return e.write_errors().into(),
323    };
324
325    let crate_name = validation_data.crate_name;
326
327    let custom_context = if let Some(context) = &validation_data.context {
328        quote!(#context)
329    } else {
330        quote!(())
331    };
332
333    // get all the fields to quote them below
334    let mut validation_fields: Vec<ValidateField> = validation_data
335        .data
336        .take_struct()
337        .unwrap()
338        .fields
339        .into_iter()
340        .map(|f| f.parsed)
341        // skip fields with #[validate(skip)] attribute
342        .filter(|f| if let Some(s) = f.skip { !s } else { true })
343        .map(|f| ValidateField {
344            crate_name: crate_name.clone(),
345            ..f
346        })
347        .collect();
348
349    if let Some(nest_all_fields) = validation_data.nest_all_fields {
350        if nest_all_fields {
351            validation_fields = validation_fields
352                .iter_mut()
353                .map(|f| {
354                    f.nested = Some(Nested { use_context: None });
355                    f.to_owned()
356                })
357                .collect();
358        }
359    }
360
361    // generate `use` statements for all used validator traits
362    let use_statements = quote_use_stmts(&crate_name, &validation_fields);
363
364    // Schema validation
365    let schema = validation_data.schema.iter().fold(quote!(), |acc, s| {
366        let st = schema_tokens(s.clone());
367        let acc = quote! {
368            #acc
369            #st
370        };
371        acc
372    });
373
374    let ident = validation_data.ident;
375    let (imp, ty, whr) = validation_data.generics.split_for_impl();
376
377    let struct_generics_quote =
378        validation_data
379            .generics
380            .params
381            .iter()
382            .fold(quote!(), |mut q, g| {
383                if let GenericParam::Type(t) = g {
384                    // Default types are not allowed in trait impl
385                    if t.default.is_some() {
386                        let mut t2 = t.clone();
387                        t2.default = None;
388                        let g2 = GenericParam::Type(t2);
389                        q.extend(quote!(#g2, ));
390                    } else {
391                        q.extend(quote!(#g, ));
392                    }
393                } else {
394                    q.extend(quote!(#g, ));
395                }
396                q
397            });
398
399    let imp_args = if struct_generics_quote.is_empty() {
400        quote!(<'v_a>)
401    } else {
402        quote!(<'v_a, #struct_generics_quote>)
403    };
404
405    let argless_validation = if validation_data.context.is_none() {
406        quote! {
407            impl #imp #crate_name::Validate for #ident #ty #whr {
408                async fn validate(&self) -> ::std::result::Result<(), #crate_name::ValidationErrors> {
409                    use #crate_name::ValidateArgs;
410                    self.validate_with_args(&mut ()).await
411                }
412            }
413        }
414    } else {
415        quote!()
416    };
417
418    // Generate HasContext impl when context is specified
419    let has_context_impl = if let Some(context) = &validation_data.context {
420        quote! {
421            impl #imp #crate_name::HasContext<#context> for #ident #ty #whr {}
422        }
423    } else {
424        quote!()
425    };
426
427    quote!(
428        #argless_validation
429
430        #has_context_impl
431
432        impl #imp_args #crate_name::ValidateArgs<'v_a> for #ident #ty #whr {
433            type Args = #custom_context;
434
435            async fn validate_with_args(&self, args: &mut Self::Args)
436            -> ::std::result::Result<(), #crate_name::ValidationErrors>
437             {
438                #use_statements
439
440                let mut errors = #crate_name::ValidationErrors::new();
441
442                #(#validation_fields)*
443
444                #schema
445
446                if errors.is_empty() {
447                    ::std::result::Result::Ok(())
448                } else {
449                    ::std::result::Result::Err(errors)
450                }
451            }
452        }
453    )
454    .into()
455}