axum_debug_macros/
lib.rs

1//! Procedural macros for [`axum-debug`] crate.
2//!
3//! [`axum-debug`]: https://crates.io/crates/axum-debug
4
5#![warn(
6    clippy::all,
7    clippy::dbg_macro,
8    clippy::todo,
9    clippy::mem_forget,
10    rust_2018_idioms,
11    future_incompatible,
12    nonstandard_style,
13    missing_debug_implementations,
14    missing_docs
15)]
16#![deny(unreachable_pub, private_in_public)]
17#![forbid(unsafe_code)]
18
19use proc_macro::TokenStream;
20
21/// Generates better error messages when applied to a handler function.
22///
23/// # Examples
24///
25/// Function is not async:
26///
27/// ```rust,ignore
28/// #[debug_handler]
29/// fn handler() -> &'static str {
30///     "Hello, world"
31/// }
32/// ```
33///
34/// ```text
35/// error: handlers must be async functions
36///   --> main.rs:xx:1
37///    |
38/// xx | fn handler() -> &'static str {
39///    | ^^
40/// ```
41///
42/// Wrong return type:
43///
44/// ```rust,ignore
45/// #[debug_handler]
46/// async fn handler() -> bool {
47///     false
48/// }
49/// ```
50///
51/// ```text
52/// error[E0277]: the trait bound `bool: IntoResponse` is not satisfied
53///   --> main.rs:xx:23
54///    |
55/// xx | async fn handler() -> bool {
56///    |                       ^^^^
57///    |                       |
58///    |                       the trait `IntoResponse` is not implemented for `bool`
59/// ```
60///
61/// Wrong extractor:
62///
63/// ```rust,ignore
64/// #[debug_handler]
65/// async fn handler(a: bool) -> String {
66///     format!("Can I extract a bool? {}", a)
67/// }
68/// ```
69///
70/// ```text
71/// error[E0277]: the trait bound `bool: FromRequest` is not satisfied
72///   --> main.rs:xx:21
73///    |
74/// xx | async fn handler(a: bool) -> String {
75///    |                     ^^^^
76///    |                     |
77///    |                     the trait `FromRequest` is not implemented for `bool`
78/// ```
79///
80/// Too many extractors:
81///
82/// ```rust,ignore
83/// #[debug_handler]
84/// async fn handler(
85///     a: String,
86///     b: String,
87///     c: String,
88///     d: String,
89///     e: String,
90///     f: String,
91///     g: String,
92///     h: String,
93///     i: String,
94///     j: String,
95///     k: String,
96///     l: String,
97///     m: String,
98///     n: String,
99///     o: String,
100///     p: String,
101///     q: String,
102/// ) {}
103/// ```
104///
105/// ```text
106/// error: too many extractors. 16 extractors are allowed
107/// note: you can nest extractors like "a: (Extractor, Extractor), b: (Extractor, Extractor)"
108///   --> main.rs:xx:5
109///    |
110/// xx | /     a: String,
111/// xx | |     b: String,
112/// xx | |     c: String,
113/// xx | |     d: String,
114/// ...  |
115/// xx | |     p: String,
116/// xx | |     q: String,
117///    | |______________^
118/// ```
119///
120/// Future is not [`Send`]:
121///
122/// ```rust,ignore
123/// #[debug_handler]
124/// async fn handler() {
125///     let not_send = std::rc::Rc::new(());
126///
127///     async{}.await;
128/// }
129/// ```
130///
131/// ```text
132/// error: future cannot be sent between threads safely
133///   --> main.rs:xx:10
134///    |
135/// xx | async fn handler() {
136///    |          ^^^^^^^
137///    |          |
138///    |          future returned by `handler` is not `Send`
139/// ```
140///
141/// [`Send`]: Send
142#[proc_macro_attribute]
143pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
144    #[cfg(not(debug_assertions))]
145    return input;
146
147    #[cfg(debug_assertions)]
148    return debug::apply_debug_handler(input);
149}
150
151/// Shortens error message when applied to a [`Router`].
152///
153/// # Example
154///
155/// ```rust,ignore
156/// use axum::{handler::get, Router};
157/// use axum_debug::{debug_handler, debug_router};
158///
159/// #[tokio::main]
160/// async fn main() {
161///     let app = Router::new().route("/", get(handler));
162///
163///     debug_router!(app);
164///
165///     axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
166///         .serve(app.into_make_service())
167///         .await
168///         .unwrap();
169/// }
170///
171/// #[debug_handler]
172/// async fn handler() -> bool {
173///     false
174/// }
175/// ```
176///
177/// [`Router`]: axum::routing::Router
178#[proc_macro]
179pub fn debug_router(_input: TokenStream) -> TokenStream {
180    #[cfg(not(debug_assertions))]
181    return TokenStream::new();
182
183    #[cfg(debug_assertions)]
184    return debug::apply_debug_router(_input);
185}
186
187#[cfg(debug_assertions)]
188mod debug {
189    use proc_macro::TokenStream;
190    use proc_macro2::Span;
191    use quote::{quote, quote_spanned};
192    use syn::{parse_macro_input, FnArg, Ident, ItemFn, ReturnType, Signature};
193
194    pub(crate) fn apply_debug_handler(input: TokenStream) -> TokenStream {
195        let function = parse_macro_input!(input as ItemFn);
196
197        let vis = &function.vis;
198        let sig = &function.sig;
199        let ident = &sig.ident;
200        let span = ident.span();
201        let len = sig.inputs.len();
202        let generics = create_generics(len);
203        let params = sig.inputs.iter().map(|fn_arg| {
204            if let FnArg::Typed(pat_type) = fn_arg {
205                &pat_type.pat
206            } else {
207                panic!("not a handler function");
208            }
209        });
210        let block = &function.block;
211
212        if let Err(error) = async_check(&sig) {
213            return error;
214        }
215
216        if let Err(error) = param_limit_check(&sig) {
217            return error;
218        }
219
220        let check_trait = check_trait_code(&sig, &generics);
221        let check_return = check_return_code(&sig, &generics);
222        let check_params = check_params_code(&sig, &generics);
223
224        let expanded = quote_spanned! {span=>
225            #vis #sig {
226                #check_trait
227                #check_return
228                #(#check_params)*
229
230                #sig #block
231
232                #ident(#(#params),*).await
233            }
234        };
235
236        expanded.into()
237    }
238
239    pub(crate) fn apply_debug_router(input: TokenStream) -> TokenStream {
240        let ident = parse_macro_input!(input as Ident);
241
242        let expanded = quote! {
243            let #ident = axum::Router::boxed(#ident);
244        };
245
246        expanded.into()
247    }
248
249    fn create_generics(len: usize) -> Vec<Ident> {
250        let mut vec = Vec::new();
251        for i in 1..=len {
252            vec.push(Ident::new(&format!("T{}", i), Span::call_site()));
253        }
254        vec
255    }
256
257    fn async_check(sig: &Signature) -> Result<(), TokenStream> {
258        if sig.asyncness.is_none() {
259            let error = syn::Error::new_spanned(sig.fn_token, "handlers must be async functions")
260                .to_compile_error()
261                .into();
262
263            return Err(error);
264        }
265
266        Ok(())
267    }
268
269    fn param_limit_check(sig: &Signature) -> Result<(), TokenStream> {
270        if sig.inputs.len() > 16 {
271            let msg = "too many extractors. 16 extractors are allowed\n\
272                       note: you can nest extractors like \"a: (Extractor, Extractor), b: (Extractor, Extractor)\"";
273
274            let error = syn::Error::new_spanned(&sig.inputs, msg)
275                .to_compile_error()
276                .into();
277
278            return Err(error);
279        }
280
281        Ok(())
282    }
283
284    fn check_trait_code(sig: &Signature, generics: &Vec<Ident>) -> proc_macro2::TokenStream {
285        let ident = &sig.ident;
286        let span = ident.span();
287
288        quote_spanned! {span=>
289            {
290                debug_handler(#ident);
291
292                fn debug_handler<F, Fut, #(#generics),*>(_f: F)
293                where
294                    F: FnOnce(#(#generics),*) -> Fut + Clone + Send + Sync + 'static,
295                    Fut: std::future::Future + Send,
296                {}
297            }
298        }
299    }
300
301    fn check_return_code(sig: &Signature, generics: &Vec<Ident>) -> proc_macro2::TokenStream {
302        let span = match &sig.output {
303            ReturnType::Default => syn::Error::new_spanned(&sig.output, "").span(),
304            ReturnType::Type(_, t) => syn::Error::new_spanned(t, "").span(),
305        };
306        let ident = &sig.ident;
307
308        quote_spanned! {span=>
309            {
310                debug_handler(#ident);
311
312                fn debug_handler<F, Fut, Res, #(#generics),*>(_f: F)
313                where
314                    F: FnOnce(#(#generics),*) -> Fut,
315                    Fut: std::future::Future<Output = Res>,
316                    Res: axum::response::IntoResponse,
317                {}
318            }
319        }
320    }
321
322    fn check_params_code(sig: &Signature, generics: &Vec<Ident>) -> Vec<proc_macro2::TokenStream> {
323        let mut vec = Vec::new();
324
325        let ident = &sig.ident;
326
327        for (i, generic) in generics.iter().enumerate() {
328            let span = match &sig.inputs[i] {
329                FnArg::Typed(pat_type) => syn::Error::new_spanned(&pat_type.ty, "").span(),
330                _ => panic!("not a handler"),
331            };
332
333            let token_stream = quote_spanned! {span=>
334                {
335                    debug_handler(#ident);
336
337                    fn debug_handler<F, Fut, #(#generics),*>(_f: F)
338                    where
339                        F: FnOnce(#(#generics),*) -> Fut,
340                        Fut: std::future::Future,
341                        #generic: axum::extract::FromRequest + Send,
342                    {}
343                }
344            };
345
346            vec.push(token_stream);
347        }
348
349        vec
350    }
351}