actix_web_lab_derive/
lib.rs1use quote::{format_ident, quote};
4use syn::{parse_macro_input, punctuated::Punctuated, token::Comma, DeriveInput, Ident};
5
6#[proc_macro_derive(FromRequest, attributes(from_request))]
35pub fn derive_from_request(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
36 let input = parse_macro_input!(input as DeriveInput);
37
38 let name = input.ident;
39
40 let data = match input.data {
41 syn::Data::Struct(data) => data,
42 syn::Data::Enum(_) | syn::Data::Union(_) => {
43 return quote! {
44 compile_error!("Deriving FromRequest is only supported on structs for now.");
45 }
46 .into();
47 }
48 };
49
50 let fields = match data.fields {
51 syn::Fields::Named(fields) => fields.named,
52 syn::Fields::Unnamed(_) | syn::Fields::Unit => {
53 return quote! {
54 compile_error!("Deriving FromRequest is only supported on structs with named fields for now.");
55 }
56 .into();
57 }
58 };
59
60 let field_names_joined = fields
61 .iter()
62 .map(|f| f.ident.clone().unwrap())
63 .collect::<Punctuated<_, Comma>>();
64
65 let fut_fields = fields.iter().filter(|field| {
67 field.attrs.is_empty()
68 || field
69 .attrs
70 .iter()
71 .any(|attr| attr.parse_args::<Ident>().is_err())
72 });
73
74 let field_fut_names_joined = fut_fields
75 .clone()
76 .map(|f| format_ident!("{}_fut", f.ident.clone().unwrap()))
77 .collect::<Punctuated<_, Comma>>();
78
79 let field_post_fut_names_joined = fut_fields
80 .clone()
81 .map(|f| f.ident.clone().unwrap())
82 .collect::<Punctuated<_, Comma>>();
83
84 let field_futs = fut_fields.clone().map(|field| {
85 let syn::Field { ident, ty, .. } = field;
86
87 let varname = format_ident!("{}_fut", ident.clone().unwrap());
88
89 quote! {
90 let #varname = <#ty>::from_request(&req, pl).map_err(Into::into);
91 }
92 });
93
94 let fields_copied_from_app_data = fields
95 .iter()
96 .filter(|field| {
97 field.attrs.iter().any(|attr| {
98 attr.parse_args::<Ident>()
99 .map_or(false, |ident| ident == "copy_from_app_data")
100 })
101 })
102 .map(|field| {
103 let syn::Field { ident, ty, .. } = field;
104
105 let varname = ident.clone().unwrap();
106
107 quote! {
108 let #varname = if let Some(st) = req.app_data::<#ty>().copied() {
109 st
110 } else {
111 ::actix_web_lab::__reexports::tracing::debug!(
112 "Failed to extract `{}` for `{}` handler. For this extractor to work \
113 correctly, pass the data to `App::app_data()`. Ensure that types align in \
114 both the set and retrieve calls.",
115 ::std::any::type_name::<#ty>(),
116 req.match_name().unwrap_or_else(|| req.path())
117 );
118
119 return ::std::boxed::Box::pin(async move {
120 ::std::result::Result::Err(
121 ::actix_web_lab::__reexports::actix_web::error::ErrorInternalServerError(
122 "Requested application data is not configured correctly. \
123 View/enable debug logs for more details.",
124 ))
125 })
126 };
127 }
128 });
129
130 let output = quote! {
131 impl ::actix_web::FromRequest for #name {
132 type Error = ::actix_web::Error;
133 type Future = ::std::pin::Pin<::std::boxed::Box<
134 dyn ::std::future::Future<Output = ::std::result::Result<Self, Self::Error>>
135 >>;
136
137 fn from_request(req: &::actix_web::HttpRequest, pl: &mut ::actix_web::dev::Payload) -> Self::Future {
138 use ::actix_web_lab::__reexports::actix_web::FromRequest as _;
139 use ::actix_web_lab::__reexports::futures_util::{FutureExt as _, TryFutureExt as _};
140 use ::actix_web_lab::__reexports::tokio::try_join;
141
142 #(#fields_copied_from_app_data)*
143
144 #(#field_futs)*
145
146 ::std::boxed::Box::pin(
147 async move { try_join!( #field_fut_names_joined ) }
148 .map_ok(move |( #field_post_fut_names_joined )| Self { #field_names_joined })
149 )
150 }
151 }
152 };
153
154 proc_macro::TokenStream::from(output)
155}