actix_multipart_extract_derive/
lib.rs

1use parse_size::parse_size;
2use proc_macro::TokenStream;
3use proc_macro2::Span;
4use quote::quote;
5use syn::{
6    parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed, Ident, Lit, Meta, MetaList,
7    MetaNameValue, NestedMeta, Path,
8};
9
10#[proc_macro_derive(MultipartForm, attributes(multipart))]
11pub fn multipart_form(input: TokenStream) -> TokenStream {
12    let ast = parse_macro_input!(input as DeriveInput);
13    let name = ast.ident;
14
15    let fields = if let Data::Struct(syn::DataStruct {
16        fields: Fields::Named(FieldsNamed { ref named, .. }),
17        ..
18    }) = ast.data
19    {
20        named
21    } else {
22        panic!("can only derive on a struct")
23    };
24
25    let field_max_sizes = fields.iter().map(|field| {
26        let Field { attrs, .. } = field;
27
28        for attr in attrs {
29            if let Ok(meta) = attr.parse_meta() {
30                if let Meta::List(MetaList { path, nested, .. }) = meta {
31                    // Check for multipart attribute.
32                    if path.get_ident().unwrap()
33                        != &Ident::new("multipart", proc_macro2::Span::call_site())
34                    {
35                        continue;
36                    }
37
38                    if let Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
39                        path: Path { segments, .. },
40                        lit,
41                        ..
42                    }))) = nested.first()
43                    {
44                        for segment in segments {
45                            if &segment.ident == &Ident::new("max_size", Span::call_site()) {
46                                let lit_string = match lit {
47                                    Lit::Int(l) => l.to_string(),
48                                    Lit::Float(f) => f.to_string(),
49                                    _ => {
50                                        return syn::Error::new(
51                                            lit.span(),
52                                            "must be a number with size suffix",
53                                        )
54                                        .to_compile_error()
55                                    }
56                                };
57
58                                let max_size = match parse_size(lit_string) {
59                                    Ok(v) => v as usize,
60                                    Err(_) => {
61                                        return syn::Error::new(lit.span(), "invalid size")
62                                            .to_compile_error();
63                                    }
64                                };
65
66                                return quote! { Some(#max_size) };
67                            }
68                        }
69                    }
70                }
71            }
72        }
73
74        quote! { None }
75    });
76
77    let field_len = field_max_sizes.len();
78
79    let expanded = quote! {
80        impl actix_multipart_extract::form::MultipartForm for #name {
81            fn max_size(field: &str) -> Option<usize> {
82                // Array of max sizes ordered by field.
83                static max_sizes: [Option<usize>; #field_len] = [#(#field_max_sizes,)*];
84
85                // Serde renamed field names ordered by field.
86                let introspected = actix_multipart_extract::serde_introspect::<Self>();
87
88                match introspected.iter().position(|f| f == &field) {
89                    Some(i) => max_sizes[i],
90                    None => None
91                }
92            }
93        }
94    };
95
96    expanded.into()
97}