1use proc_macro::TokenStream;
10
11#[deprecated(since = "0.3.3", note = "Use the axum-macros crate instead")]
17#[proc_macro_attribute]
18pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
19 #[cfg(not(debug_assertions))]
20 return input;
21
22 #[cfg(debug_assertions)]
23 return debug_handler::expand(_attr, input);
24}
25
26#[cfg(debug_assertions)]
27mod debug_handler {
28 use proc_macro2::TokenStream;
29 use quote::{format_ident, quote, quote_spanned};
30 use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Token, Type};
31
32 pub(crate) fn expand(
33 attr: proc_macro::TokenStream,
34 input: proc_macro::TokenStream,
35 ) -> proc_macro::TokenStream {
36 match try_expand(attr.into(), input.into()) {
37 Ok(tokens) => tokens.into(),
38 Err(err) => err.into_compile_error().into(),
39 }
40 }
41
42 pub(crate) fn try_expand(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
43 let attr = syn::parse2::<Attrs>(attr)?;
44 let item_fn = syn::parse2::<ItemFn>(input.clone())?;
45
46 check_extractor_count(&item_fn)?;
47
48 let check_inputs_impls_from_request =
49 check_inputs_impls_from_request(&item_fn, &attr.body_ty);
50 let check_output_impls_into_response = check_output_impls_into_response(&item_fn);
51 let check_future_send = check_future_send(&item_fn);
52
53 let tokens = quote! {
54 #input
55 #check_inputs_impls_from_request
56 #check_output_impls_into_response
57 #check_future_send
58 };
59
60 Ok(tokens)
61 }
62
63 struct Attrs {
64 body_ty: Type,
65 }
66
67 impl Parse for Attrs {
68 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
69 let mut body_ty = None;
70
71 while !input.is_empty() {
72 let ident = input.parse::<syn::Ident>()?;
73 if ident == "body" {
74 input.parse::<Token![=]>()?;
75 body_ty = Some(input.parse()?);
76 } else {
77 return Err(syn::Error::new_spanned(ident, "unknown argument"));
78 }
79
80 let _ = input.parse::<Token![,]>();
81 }
82
83 let body_ty = body_ty.unwrap_or_else(|| syn::parse_quote!(axum::body::Body));
84
85 Ok(Self { body_ty })
86 }
87 }
88
89 fn check_extractor_count(item_fn: &ItemFn) -> syn::Result<()> {
90 let max_extractors = 16;
91 if item_fn.sig.inputs.len() <= max_extractors {
92 Ok(())
93 } else {
94 Err(syn::Error::new_spanned(
95 &item_fn.sig.inputs,
96 format!(
97 "Handlers cannot take more than {} arguments. Use `(a, b): (ExtractorA, ExtractorA)` to further nest extractors",
98 max_extractors,
99 )
100 ))
101 }
102 }
103
104 fn check_inputs_impls_from_request(item_fn: &ItemFn, body_ty: &Type) -> TokenStream {
105 if !item_fn.sig.generics.params.is_empty() {
106 return syn::Error::new_spanned(
107 &item_fn.sig.generics,
108 "`#[axum_debug::debug_handler]` doesn't support generic functions",
109 )
110 .into_compile_error();
111 }
112
113 item_fn
114 .sig
115 .inputs
116 .iter()
117 .enumerate()
118 .map(|(idx, arg)| {
119 let (span, ty) = match arg {
120 FnArg::Receiver(receiver) => {
121 if receiver.reference.is_some() {
122 return syn::Error::new_spanned(
123 receiver,
124 "Handlers must only take owned values",
125 )
126 .into_compile_error();
127 }
128
129 let span = receiver.span();
130 (span, syn::parse_quote!(Self))
131 }
132 FnArg::Typed(typed) => {
133 let ty = &typed.ty;
134 let span = ty.span();
135 (span, ty.clone())
136 }
137 };
138
139 let name = format_ident!(
140 "__axum_debug_check_{}_{}_from_request",
141 item_fn.sig.ident,
142 idx
143 );
144 quote_spanned! {span=>
145 #[allow(warnings)]
146 fn #name()
147 where
148 #ty: ::axum::extract::FromRequest<#body_ty> + Send,
149 {}
150 }
151 })
152 .collect::<TokenStream>()
153 }
154
155 fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream {
156 let ty = match &item_fn.sig.output {
157 syn::ReturnType::Default => return quote! {},
158 syn::ReturnType::Type(_, ty) => ty,
159 };
160 let span = ty.span();
161
162 let declare_inputs = item_fn
163 .sig
164 .inputs
165 .iter()
166 .filter_map(|arg| match arg {
167 FnArg::Receiver(_) => None,
168 FnArg::Typed(pat_ty) => {
169 let pat = &pat_ty.pat;
170 let ty = &pat_ty.ty;
171 Some(quote! {
172 let #pat: #ty = panic!();
173 })
174 }
175 })
176 .collect::<TokenStream>();
177
178 let block = &item_fn.block;
179
180 let make_value_name = format_ident!(
181 "__axum_debug_check_{}_into_response_make_value",
182 item_fn.sig.ident
183 );
184
185 let make = if item_fn.sig.asyncness.is_some() {
186 quote_spanned! {span=>
187 #[allow(warnings)]
188 async fn #make_value_name() -> #ty {
189 #declare_inputs
190 #block
191 }
192 }
193 } else {
194 quote_spanned! {span=>
195 #[allow(warnings)]
196 fn #make_value_name() -> #ty {
197 #declare_inputs
198 #block
199 }
200 }
201 };
202
203 let name = format_ident!("__axum_debug_check_{}_into_response", item_fn.sig.ident);
204
205 if let Some(receiver) = self_receiver(item_fn) {
206 quote_spanned! {span=>
207 #make
208
209 #[allow(warnings)]
210 async fn #name() {
211 let value = #receiver #make_value_name().await;
212 fn check<T>(_: T)
213 where T: ::axum::response::IntoResponse
214 {}
215 check(value);
216 }
217 }
218 } else {
219 quote_spanned! {span=>
220 #[allow(warnings)]
221 async fn #name() {
222 #make
223
224 let value = #make_value_name().await;
225
226 fn check<T>(_: T)
227 where T: ::axum::response::IntoResponse
228 {}
229
230 check(value);
231 }
232 }
233 }
234 }
235
236 fn check_future_send(item_fn: &ItemFn) -> TokenStream {
237 if item_fn.sig.asyncness.is_none() {
238 match &item_fn.sig.output {
239 syn::ReturnType::Default => {
240 return syn::Error::new_spanned(
241 &item_fn.sig.fn_token,
242 "Handlers must be `async fn`s",
243 )
244 .into_compile_error();
245 }
246 syn::ReturnType::Type(_, ty) => ty,
247 };
248 }
249
250 let span = item_fn.span();
251
252 let handler_name = &item_fn.sig.ident;
253
254 let args = item_fn.sig.inputs.iter().map(|_| {
255 quote_spanned! {span=> panic!() }
256 });
257
258 let name = format_ident!("__axum_debug_check_{}_future", item_fn.sig.ident);
259
260 if let Some(receiver) = self_receiver(item_fn) {
261 quote_spanned! {span=>
262 #[allow(warnings)]
263 fn #name() {
264 let future = #receiver #handler_name(#(#args),*);
265 fn check<T>(_: T)
266 where T: ::std::future::Future + Send
267 {}
268 check(future);
269 }
270 }
271 } else {
272 quote_spanned! {span=>
273 #[allow(warnings)]
274 fn #name() {
275 #item_fn
276
277 let future = #handler_name(#(#args),*);
278 fn check<T>(_: T)
279 where T: ::std::future::Future + Send
280 {}
281 check(future);
282 }
283 }
284 }
285 }
286
287 fn self_receiver(item_fn: &ItemFn) -> Option<TokenStream> {
288 let takes_self = item_fn
289 .sig
290 .inputs
291 .iter()
292 .any(|arg| matches!(arg, syn::FnArg::Receiver(_)));
293 if takes_self {
294 return Some(quote! { Self:: });
295 }
296
297 if let syn::ReturnType::Type(_, ty) = &item_fn.sig.output {
298 if let syn::Type::Path(path) = &**ty {
299 let segments = &path.path.segments;
300 if segments.len() == 1 {
301 if let Some(last) = segments.last() {
302 match &last.arguments {
303 syn::PathArguments::None if last.ident == "Self" => {
304 return Some(quote! { Self:: });
305 }
306 _ => {}
307 }
308 }
309 }
310 }
311 }
312
313 None
314 }
315}