web_core_validator_derive/
lib.rs1use 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 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 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 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 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 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 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 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 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 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 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 let must_match = if let Some(must_match) = self.must_match.clone() {
180 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 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 let mut custom = quote!();
205 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#[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 #[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 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 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 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 .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 let use_statements = quote_use_stmts(&crate_name, &validation_fields);
363
364 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 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 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}