Skip to main content

actix_multipart_derive/
lib.rs

1//! Multipart form derive macro for Actix Web.
2//!
3//! See [`actix_multipart::form::MultipartForm`] for usage examples.
4//!
5//! [`actix_multipart::form::MultipartForm`]: https://docs.rs/actix-multipart/latest/actix_multipart/form/struct.MultipartForm.html
6
7#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
8#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10#![allow(clippy::disallowed_names)] // false positives in some macro expansions
11
12use std::collections::HashSet;
13
14use bytesize::ByteSize;
15use darling::{FromDeriveInput, FromField, FromMeta};
16use proc_macro::TokenStream;
17use proc_macro2::Ident;
18use quote::quote;
19use syn::{parse_macro_input, Type};
20
21#[derive(Default, FromMeta)]
22enum DuplicateField {
23    #[default]
24    Ignore,
25    Deny,
26    Replace,
27}
28
29#[derive(FromDeriveInput, Default)]
30#[darling(attributes(multipart), default)]
31struct MultipartFormAttrs {
32    deny_unknown_fields: bool,
33    duplicate_field: DuplicateField,
34}
35
36#[allow(clippy::disallowed_names)] // false positive in macro expansion
37#[derive(FromField, Default)]
38#[darling(attributes(multipart), default)]
39struct FieldAttrs {
40    rename: Option<String>,
41    limit: Option<String>,
42}
43
44struct ParsedField<'t> {
45    serialization_name: String,
46    rust_name: &'t Ident,
47    limit: Option<usize>,
48    ty: &'t Type,
49}
50
51/// Implements `MultipartCollect` for a struct so that it can be used with the `MultipartForm`
52/// extractor.
53///
54/// See [`actix_multipart::form::MultipartForm`] for supported fields and attributes.
55///
56/// [`actix_multipart::form::MultipartForm`]: https://docs.rs/actix-multipart/latest/actix_multipart/form/struct.MultipartForm.html
57#[proc_macro_derive(MultipartForm, attributes(multipart))]
58pub fn impl_multipart_form(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
59    let input: syn::DeriveInput = parse_macro_input!(input);
60
61    let name = &input.ident;
62
63    let data_struct = match &input.data {
64        syn::Data::Struct(data_struct) => data_struct,
65        _ => {
66            return compile_err(syn::Error::new(
67                input.ident.span(),
68                "`MultipartForm` can only be derived for structs",
69            ))
70        }
71    };
72
73    let fields = match &data_struct.fields {
74        syn::Fields::Named(fields_named) => fields_named,
75        _ => {
76            return compile_err(syn::Error::new(
77                input.ident.span(),
78                "`MultipartForm` can only be derived for a struct with named fields",
79            ))
80        }
81    };
82
83    let attrs = match MultipartFormAttrs::from_derive_input(&input) {
84        Ok(attrs) => attrs,
85        Err(err) => return err.write_errors().into(),
86    };
87
88    // Parse the field attributes
89    let parsed = match fields
90        .named
91        .iter()
92        .map(|field| {
93            let rust_name = field.ident.as_ref().unwrap();
94            let attrs = FieldAttrs::from_field(field).map_err(|err| err.write_errors())?;
95            let serialization_name = attrs.rename.unwrap_or_else(|| rust_name.to_string());
96
97            let limit = match attrs.limit.map(|limit| match limit.parse::<ByteSize>() {
98                Ok(ByteSize(size)) => Ok(usize::try_from(size).unwrap()),
99                Err(err) => Err(syn::Error::new(
100                    field.ident.as_ref().unwrap().span(),
101                    format!("Could not parse size limit `{}`: {}", limit, err),
102                )),
103            }) {
104                Some(Err(err)) => return Err(compile_err(err)),
105                limit => limit.map(Result::unwrap),
106            };
107
108            Ok(ParsedField {
109                serialization_name,
110                rust_name,
111                limit,
112                ty: &field.ty,
113            })
114        })
115        .collect::<Result<Vec<_>, TokenStream>>()
116    {
117        Ok(attrs) => attrs,
118        Err(err) => return err,
119    };
120
121    // Check that field names are unique
122    let mut set = HashSet::new();
123    for field in &parsed {
124        if !set.insert(field.serialization_name.clone()) {
125            return compile_err(syn::Error::new(
126                field.rust_name.span(),
127                format!("Multiple fields named: `{}`", field.serialization_name),
128            ));
129        }
130    }
131
132    // Return value when a field name is not supported by the form
133    let unknown_field_result = if attrs.deny_unknown_fields {
134        quote!(::std::result::Result::Err(
135            ::actix_multipart::MultipartError::UnknownField(field.name().unwrap().to_string())
136        ))
137    } else {
138        quote!(::actix_multipart::form::discard_field(field, limits).await)
139    };
140
141    // Value for duplicate action
142    let duplicate_field = match attrs.duplicate_field {
143        DuplicateField::Ignore => quote!(::actix_multipart::form::DuplicateField::Ignore),
144        DuplicateField::Deny => quote!(::actix_multipart::form::DuplicateField::Deny),
145        DuplicateField::Replace => quote!(::actix_multipart::form::DuplicateField::Replace),
146    };
147
148    // limit() implementation
149    let mut limit_impl = quote!();
150    for field in &parsed {
151        let name = &field.serialization_name;
152        if let Some(value) = field.limit {
153            limit_impl.extend(quote!(
154                #name => ::std::option::Option::Some(#value),
155            ));
156        }
157    }
158
159    // handle_field() implementation
160    let mut handle_field_impl = quote!();
161    for field in &parsed {
162        let name = &field.serialization_name;
163        let ty = &field.ty;
164
165        handle_field_impl.extend(quote!(
166            #name => ::std::boxed::Box::pin(
167                <#ty as ::actix_multipart::form::FieldGroupReader>::handle_field(req, field, limits, state, #duplicate_field)
168            ),
169        ));
170    }
171
172    // from_state() implementation
173    let mut from_state_impl = quote!();
174    for field in &parsed {
175        let name = &field.serialization_name;
176        let rust_name = &field.rust_name;
177        let ty = &field.ty;
178        from_state_impl.extend(quote!(
179            #rust_name: <#ty as ::actix_multipart::form::FieldGroupReader>::from_state(#name, &mut state)?,
180        ));
181    }
182
183    let gen = quote! {
184        impl ::actix_multipart::form::MultipartCollect for #name {
185            fn limit(field_name: &str) -> ::std::option::Option<usize> {
186                match field_name {
187                    #limit_impl
188                    _ => None,
189                }
190            }
191
192            fn handle_field<'t>(
193                req: &'t ::actix_web::HttpRequest,
194                field: ::actix_multipart::Field,
195                limits: &'t mut ::actix_multipart::form::Limits,
196                state: &'t mut ::actix_multipart::form::State,
197            ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<(), ::actix_multipart::MultipartError>> + 't>> {
198                match field.name().unwrap() {
199                    #handle_field_impl
200                    _ => return ::std::boxed::Box::pin(async move { #unknown_field_result }),
201                }
202            }
203
204            fn from_state(mut state: ::actix_multipart::form::State) -> ::std::result::Result<Self, ::actix_multipart::MultipartError> {
205                Ok(Self {
206                    #from_state_impl
207                })
208            }
209
210        }
211    };
212    gen.into()
213}
214
215/// Transform a syn error into a token stream for returning.
216fn compile_err(err: syn::Error) -> TokenStream {
217    TokenStream::from(err.to_compile_error())
218}