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