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}