mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-21 14:46:32 +01:00
Move axum-debug into axum-macros (#724)
* Move axum-debug into axum-macros * fix ref to axum-macros in changelog * Apply suggestions from code review Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
This commit is contained in:
parent
b1283e9708
commit
f6fc5ed80c
60 changed files with 492 additions and 217 deletions
|
@ -8,7 +8,6 @@ If your project isn't listed here and you would like it to be, please feel free
|
|||
- [axum-typed-websockets](https://crates.io/crates/axum-typed-websockets): `axum::extract::ws` with type safe messages.
|
||||
- [tower-cookies](https://crates.io/crates/tower-cookies): Cookie manager middleware
|
||||
- [axum-flash](https://crates.io/crates/axum-flash): One-time notifications (aka flash messages) for axum.
|
||||
- [axum-debug](https://crates.io/crates/axum-debug): Debugging crate that provides better error messages for axum.
|
||||
- [axum-msgpack](https://crates.io/crates/axum-msgpack): MessagePack Extractors for axum.
|
||||
|
||||
## Project showcase
|
||||
|
|
|
@ -17,8 +17,3 @@ proc-macro = true
|
|||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { path = "../axum", version = "0.4" }
|
||||
trybuild = "1.0"
|
||||
rustversion = "1.0"
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
This is a debugging crate that provides better error messages for [`axum`]
|
||||
framework.
|
||||
|
||||
**Note:** this crate is deprecated. Use [axum-macros] instead.
|
||||
|
||||
More information about this crate can be found in the [crate documentation][docs].
|
||||
|
||||
## Safety
|
||||
|
@ -44,3 +46,4 @@ additional terms or conditions.
|
|||
[docs]: https://docs.rs/axum-debug
|
||||
[license]: /axum-debug/LICENSE
|
||||
[issue]: https://github.com/tokio-rs/axum/issues/new
|
||||
[axum-macros]: https://crates.io/crates/axum-macros
|
||||
|
|
|
@ -1,148 +1,19 @@
|
|||
//! This is a debugging crate that provides better error messages for [`axum`] framework.
|
||||
//!
|
||||
//! While using [`axum`], you can get long error messages for simple mistakes. For example:
|
||||
//! **Note:** this crate is deprecated. Use [axum-macros] instead.
|
||||
//!
|
||||
//! ```rust,compile_fail
|
||||
//! use axum::{routing::get, Router};
|
||||
//! [axum-macros]: https://crates.io/crates/axum-macros
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let app = Router::new().route("/", get(handler));
|
||||
//!
|
||||
//! axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
//! .serve(app.into_make_service())
|
||||
//! .await
|
||||
//! .unwrap();
|
||||
//! }
|
||||
//!
|
||||
//! fn handler() -> &'static str {
|
||||
//! "Hello, world"
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! You will get a long error message about function not implementing [`Handler`] trait. But why
|
||||
//! does this function not implement it? To figure it out, the [`debug_handler`] macro can be used.
|
||||
//!
|
||||
//! ```rust,compile_fail
|
||||
//! # use axum::{routing::get, Router};
|
||||
//! # use axum_debug::debug_handler;
|
||||
//! #
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! # let app = Router::new().route("/", get(handler));
|
||||
//! #
|
||||
//! # axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
//! # .serve(app.into_make_service())
|
||||
//! # .await
|
||||
//! # .unwrap();
|
||||
//! # }
|
||||
//! #
|
||||
//! #[debug_handler]
|
||||
//! fn handler() -> &'static str {
|
||||
//! "Hello, world"
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ```text
|
||||
//! error: handlers must be async functions
|
||||
//! --> main.rs:xx:1
|
||||
//! |
|
||||
//! xx | fn handler() -> &'static str {
|
||||
//! | ^^
|
||||
//! ```
|
||||
//!
|
||||
//! As the error message says, handler function needs to be async.
|
||||
//!
|
||||
//! ```rust,compile_fail
|
||||
//! use axum::{routing::get, Router};
|
||||
//! use axum_debug::debug_handler;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let app = Router::new().route("/", get(handler));
|
||||
//!
|
||||
//! axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
//! .serve(app.into_make_service())
|
||||
//! .await
|
||||
//! .unwrap();
|
||||
//! }
|
||||
//!
|
||||
//! #[debug_handler]
|
||||
//! async fn handler() -> &'static str {
|
||||
//! "Hello, world"
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Changing request body type
|
||||
//!
|
||||
//! By default `#[debug_handler]` assumes your request body type is `axum::body::Body`. This will
|
||||
//! work for most extractors but, for example, it wont work for `Request<axum::body::BoxBody>`,
|
||||
//! which only implements `FromRequest<BoxBody>` and _not_ `FromRequest<Body>`.
|
||||
//!
|
||||
//! To work around that the request body type can be customized like so:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use axum::{body::BoxBody, http::Request};
|
||||
//! # use axum_debug::debug_handler;
|
||||
//!
|
||||
//! #[debug_handler(body = BoxBody)]
|
||||
//! async fn handler(request: Request<BoxBody>) {}
|
||||
//! ```
|
||||
//!
|
||||
//! # Performance
|
||||
//!
|
||||
//! Macros in this crate have no effect when using release profile. (eg. `cargo build --release`)
|
||||
//!
|
||||
//! [`axum`]: https://docs.rs/axum/0.3
|
||||
//! [`Handler`]: https://docs.rs/axum/0.3/axum/handler/trait.Handler.html
|
||||
//! [`debug_handler`]: macro@debug_handler
|
||||
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::dbg_macro,
|
||||
clippy::todo,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::mem_forget,
|
||||
clippy::unused_self,
|
||||
clippy::filter_map_next,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_borrow,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::if_let_mutex,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::await_holding_lock,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::imprecise_flops,
|
||||
clippy::suboptimal_flops,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::exit,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::linkedlist,
|
||||
clippy::macro_use_imports,
|
||||
clippy::option_option,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::str_to_string,
|
||||
rust_2018_idioms,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
missing_debug_implementations,
|
||||
missing_docs
|
||||
)]
|
||||
#![deny(unreachable_pub, private_in_public)]
|
||||
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(test, allow(clippy::float_cmp))]
|
||||
//! [`axum`]: https://docs.rs/axum/latest
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
/// Generates better error messages when applied to a handler function.
|
||||
///
|
||||
/// See the [module docs](self) for more details.
|
||||
/// Note this crate is deprecated. Use [axum-macros] instead.
|
||||
///
|
||||
/// [axum-macros]: https://crates.io/crates/axum-macros
|
||||
#[deprecated(since = "0.3.3", note = "Use the axum-macros crate instead")]
|
||||
#[proc_macro_attribute]
|
||||
pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
#[cfg(not(debug_assertions))]
|
||||
|
@ -442,18 +313,3 @@ mod debug_handler {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui() {
|
||||
#[rustversion::stable]
|
||||
fn go() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/fail/*.rs");
|
||||
t.pass("tests/pass/*.rs");
|
||||
}
|
||||
|
||||
#[rustversion::not(stable)]
|
||||
fn go() {}
|
||||
|
||||
go();
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
error: expected `fn`
|
||||
--> tests/fail/not_a_function.rs:4:1
|
||||
|
|
||||
4 | struct A;
|
||||
| ^^^^^^
|
|
@ -23,4 +23,5 @@ syn = { version = "1.0", features = ["full"] }
|
|||
axum = { path = "../axum", version = "0.4", features = ["headers"] }
|
||||
rustversion = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
trybuild = "1.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright 2021 Axum Debug Contributors
|
||||
Copyright 2021 Axum Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
|
288
axum-macros/src/debug_handler.rs
Normal file
288
axum-macros/src/debug_handler.rs
Normal file
|
@ -0,0 +1,288 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn, Token, Type};
|
||||
|
||||
pub(crate) fn expand(attr: Attrs, item_fn: ItemFn) -> syn::Result<TokenStream> {
|
||||
check_extractor_count(&item_fn)?;
|
||||
|
||||
let check_inputs_impls_from_request = check_inputs_impls_from_request(&item_fn, &attr.body_ty);
|
||||
let check_output_impls_into_response = check_output_impls_into_response(&item_fn);
|
||||
let check_future_send = check_future_send(&item_fn);
|
||||
|
||||
let tokens = quote! {
|
||||
#item_fn
|
||||
#check_inputs_impls_from_request
|
||||
#check_output_impls_into_response
|
||||
#check_future_send
|
||||
};
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
pub(crate) struct Attrs {
|
||||
body_ty: Type,
|
||||
}
|
||||
|
||||
impl Parse for Attrs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut body_ty = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident = input.parse::<syn::Ident>()?;
|
||||
if ident == "body" {
|
||||
input.parse::<Token![=]>()?;
|
||||
body_ty = Some(input.parse()?);
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(ident, "unknown argument"));
|
||||
}
|
||||
|
||||
let _ = input.parse::<Token![,]>();
|
||||
}
|
||||
|
||||
let body_ty = body_ty.unwrap_or_else(|| syn::parse_quote!(axum::body::Body));
|
||||
|
||||
Ok(Self { body_ty })
|
||||
}
|
||||
}
|
||||
|
||||
fn check_extractor_count(item_fn: &ItemFn) -> syn::Result<()> {
|
||||
let max_extractors = 16;
|
||||
if item_fn.sig.inputs.len() <= max_extractors {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
&item_fn.sig.inputs,
|
||||
format!(
|
||||
"Handlers cannot take more than {} arguments. Use `(a, b): (ExtractorA, ExtractorA)` to further nest extractors",
|
||||
max_extractors,
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_inputs_impls_from_request(item_fn: &ItemFn, body_ty: &Type) -> TokenStream {
|
||||
if !item_fn.sig.generics.params.is_empty() {
|
||||
return syn::Error::new_spanned(
|
||||
&item_fn.sig.generics,
|
||||
"`#[axum_macros::debug_handler]` doesn't support generic functions",
|
||||
)
|
||||
.into_compile_error();
|
||||
}
|
||||
|
||||
item_fn
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, arg)| {
|
||||
let (span, ty) = match arg {
|
||||
FnArg::Receiver(receiver) => {
|
||||
if receiver.reference.is_some() {
|
||||
return syn::Error::new_spanned(
|
||||
receiver,
|
||||
"Handlers must only take owned values",
|
||||
)
|
||||
.into_compile_error();
|
||||
}
|
||||
|
||||
let span = receiver.span();
|
||||
(span, syn::parse_quote!(Self))
|
||||
}
|
||||
FnArg::Typed(typed) => {
|
||||
let ty = &typed.ty;
|
||||
let span = ty.span();
|
||||
(span, ty.clone())
|
||||
}
|
||||
};
|
||||
|
||||
let name = format_ident!(
|
||||
"__axum_macros_check_{}_{}_from_request",
|
||||
item_fn.sig.ident,
|
||||
idx
|
||||
);
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
fn #name()
|
||||
where
|
||||
#ty: ::axum::extract::FromRequest<#body_ty> + Send,
|
||||
{}
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>()
|
||||
}
|
||||
|
||||
fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream {
|
||||
let ty = match &item_fn.sig.output {
|
||||
syn::ReturnType::Default => return quote! {},
|
||||
syn::ReturnType::Type(_, ty) => ty,
|
||||
};
|
||||
let span = ty.span();
|
||||
|
||||
let declare_inputs = item_fn
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
FnArg::Receiver(_) => None,
|
||||
FnArg::Typed(pat_ty) => {
|
||||
let pat = &pat_ty.pat;
|
||||
let ty = &pat_ty.ty;
|
||||
Some(quote! {
|
||||
let #pat: #ty = panic!();
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
let block = &item_fn.block;
|
||||
|
||||
let make_value_name = format_ident!(
|
||||
"__axum_macros_check_{}_into_response_make_value",
|
||||
item_fn.sig.ident
|
||||
);
|
||||
|
||||
let make = if item_fn.sig.asyncness.is_some() {
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
async fn #make_value_name() -> #ty {
|
||||
#declare_inputs
|
||||
#block
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
fn #make_value_name() -> #ty {
|
||||
#declare_inputs
|
||||
#block
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let name = format_ident!("__axum_macros_check_{}_into_response", item_fn.sig.ident);
|
||||
|
||||
if let Some(receiver) = self_receiver(item_fn) {
|
||||
quote_spanned! {span=>
|
||||
#make
|
||||
|
||||
#[allow(warnings)]
|
||||
async fn #name() {
|
||||
let value = #receiver #make_value_name().await;
|
||||
fn check<T>(_: T)
|
||||
where T: ::axum::response::IntoResponse
|
||||
{}
|
||||
check(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
async fn #name() {
|
||||
#make
|
||||
|
||||
let value = #make_value_name().await;
|
||||
|
||||
fn check<T>(_: T)
|
||||
where T: ::axum::response::IntoResponse
|
||||
{}
|
||||
|
||||
check(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_future_send(item_fn: &ItemFn) -> TokenStream {
|
||||
if item_fn.sig.asyncness.is_none() {
|
||||
match &item_fn.sig.output {
|
||||
syn::ReturnType::Default => {
|
||||
return syn::Error::new_spanned(
|
||||
&item_fn.sig.fn_token,
|
||||
"Handlers must be `async fn`s",
|
||||
)
|
||||
.into_compile_error();
|
||||
}
|
||||
syn::ReturnType::Type(_, ty) => ty,
|
||||
};
|
||||
}
|
||||
|
||||
let span = item_fn.span();
|
||||
|
||||
let handler_name = &item_fn.sig.ident;
|
||||
|
||||
let args = item_fn.sig.inputs.iter().map(|_| {
|
||||
quote_spanned! {span=> panic!() }
|
||||
});
|
||||
|
||||
let name = format_ident!("__axum_macros_check_{}_future", item_fn.sig.ident);
|
||||
|
||||
if let Some(receiver) = self_receiver(item_fn) {
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
fn #name() {
|
||||
let future = #receiver #handler_name(#(#args),*);
|
||||
fn check<T>(_: T)
|
||||
where T: ::std::future::Future + Send
|
||||
{}
|
||||
check(future);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {span=>
|
||||
#[allow(warnings)]
|
||||
fn #name() {
|
||||
#item_fn
|
||||
|
||||
let future = #handler_name(#(#args),*);
|
||||
fn check<T>(_: T)
|
||||
where T: ::std::future::Future + Send
|
||||
{}
|
||||
check(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn self_receiver(item_fn: &ItemFn) -> Option<TokenStream> {
|
||||
let takes_self = item_fn
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.any(|arg| matches!(arg, syn::FnArg::Receiver(_)));
|
||||
if takes_self {
|
||||
return Some(quote! { Self:: });
|
||||
}
|
||||
|
||||
if let syn::ReturnType::Type(_, ty) = &item_fn.sig.output {
|
||||
if let syn::Type::Path(path) = &**ty {
|
||||
let segments = &path.path.segments;
|
||||
if segments.len() == 1 {
|
||||
if let Some(last) = segments.last() {
|
||||
match &last.arguments {
|
||||
syn::PathArguments::None if last.ident == "Self" => {
|
||||
return Some(quote! { Self:: });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui() {
|
||||
#[rustversion::stable]
|
||||
fn go() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/debug_handler/fail/*.rs");
|
||||
t.pass("tests/debug_handler/pass/*.rs");
|
||||
}
|
||||
|
||||
#[rustversion::not(stable)]
|
||||
fn go() {}
|
||||
|
||||
go();
|
||||
}
|
|
@ -508,8 +508,8 @@ fn ui() {
|
|||
#[rustversion::stable]
|
||||
fn go() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/fail/*.rs");
|
||||
t.pass("tests/pass/*.rs");
|
||||
t.compile_fail("tests/from_request/fail/*.rs");
|
||||
t.pass("tests/from_request/pass/*.rs");
|
||||
}
|
||||
|
||||
#[rustversion::not(stable)]
|
||||
|
|
|
@ -43,9 +43,11 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(test, allow(clippy::float_cmp))]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::parse::Parse;
|
||||
|
||||
mod debug_handler;
|
||||
mod from_request;
|
||||
|
||||
/// Derive an implementation of [`FromRequest`].
|
||||
|
@ -235,17 +237,148 @@ mod from_request;
|
|||
/// [`FromRequest`]: https://docs.rs/axum/latest/axum/extract/trait.FromRequest.html
|
||||
/// [`axum::extract::rejection::ExtensionRejection`]: https://docs.rs/axum/latest/axum/extract/rejection/enum.ExtensionRejection.html
|
||||
#[proc_macro_derive(FromRequest, attributes(from_request))]
|
||||
pub fn derive_from_request(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn derive_from_request(item: TokenStream) -> TokenStream {
|
||||
expand_with(item, from_request::expand)
|
||||
}
|
||||
|
||||
fn expand_with<F, T, K>(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream
|
||||
/// Generates better error messages when applied handler functions.
|
||||
///
|
||||
/// While using [`axum`], you can get long error messages for simple mistakes. For example:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use axum::{routing::get, Router};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let app = Router::new().route("/", get(handler));
|
||||
///
|
||||
/// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
/// .serve(app.into_make_service())
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
/// }
|
||||
///
|
||||
/// fn handler() -> &'static str {
|
||||
/// "Hello, world"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You will get a long error message about function not implementing [`Handler`] trait. But why
|
||||
/// does this function not implement it? To figure it out, the [`debug_handler`] macro can be used.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use axum::{routing::get, Router};
|
||||
/// # use axum_macros::debug_handler;
|
||||
/// #
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// # let app = Router::new().route("/", get(handler));
|
||||
/// #
|
||||
/// # axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
/// # .serve(app.into_make_service())
|
||||
/// # .await
|
||||
/// # .unwrap();
|
||||
/// # }
|
||||
/// #
|
||||
/// #[debug_handler]
|
||||
/// fn handler() -> &'static str {
|
||||
/// "Hello, world"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```text
|
||||
/// error: handlers must be async functions
|
||||
/// --> main.rs:xx:1
|
||||
/// |
|
||||
/// xx | fn handler() -> &'static str {
|
||||
/// | ^^
|
||||
/// ```
|
||||
///
|
||||
/// As the error message says, handler function needs to be async.
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{routing::get, Router};
|
||||
/// use axum_macros::debug_handler;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// # async {
|
||||
/// let app = Router::new().route("/", get(handler));
|
||||
///
|
||||
/// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
/// .serve(app.into_make_service())
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
/// # };
|
||||
/// }
|
||||
///
|
||||
/// #[debug_handler]
|
||||
/// async fn handler() -> &'static str {
|
||||
/// "Hello, world"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Changing request body type
|
||||
///
|
||||
/// By default `#[debug_handler]` assumes your request body type is `axum::body::Body`. This will
|
||||
/// work for most extractors but, for example, it wont work for `Request<axum::body::BoxBody>`,
|
||||
/// which only implements `FromRequest<BoxBody>` and _not_ `FromRequest<Body>`.
|
||||
///
|
||||
/// To work around that the request body type can be customized like so:
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{body::BoxBody, http::Request};
|
||||
/// # use axum_macros::debug_handler;
|
||||
///
|
||||
/// #[debug_handler(body = BoxBody)]
|
||||
/// async fn handler(request: Request<BoxBody>) {}
|
||||
/// ```
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// This macro has no effect when compiled with the release profile. (eg. `cargo build --release`)
|
||||
///
|
||||
/// [`axum`]: https://docs.rs/axum/latest
|
||||
/// [`Handler`]: https://docs.rs/axum/latest/axum/handler/trait.Handler.html
|
||||
/// [`debug_handler`]: macro@debug_handler
|
||||
#[proc_macro_attribute]
|
||||
pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
#[cfg(not(debug_assertions))]
|
||||
return input;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
return expand_attr_with(_attr, input, debug_handler::expand);
|
||||
}
|
||||
|
||||
fn expand_with<F, I, K>(input: TokenStream, f: F) -> TokenStream
|
||||
where
|
||||
F: FnOnce(T) -> syn::Result<K>,
|
||||
T: Parse,
|
||||
F: FnOnce(I) -> syn::Result<K>,
|
||||
I: Parse,
|
||||
K: ToTokens,
|
||||
{
|
||||
match syn::parse(input).and_then(f) {
|
||||
expand(syn::parse(input).and_then(f))
|
||||
}
|
||||
|
||||
fn expand_attr_with<F, A, I, K>(attr: TokenStream, input: TokenStream, f: F) -> TokenStream
|
||||
where
|
||||
F: FnOnce(A, I) -> syn::Result<K>,
|
||||
A: Parse,
|
||||
I: Parse,
|
||||
K: ToTokens,
|
||||
{
|
||||
let expand_result = (|| {
|
||||
let attr = syn::parse(attr)?;
|
||||
let input = syn::parse(input)?;
|
||||
f(attr, input)
|
||||
})();
|
||||
expand(expand_result)
|
||||
}
|
||||
|
||||
fn expand<T>(result: syn::Result<T>) -> TokenStream
|
||||
where
|
||||
T: ToTokens,
|
||||
{
|
||||
match result {
|
||||
Ok(tokens) => {
|
||||
let tokens = (quote! { #tokens }).into();
|
||||
if std::env::var_os("AXUM_MACROS_DEBUG").is_some() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler(foo: bool) {}
|
|
@ -1,5 +1,5 @@
|
|||
error[E0277]: the trait bound `bool: FromRequest<Body>` is not satisfied
|
||||
--> tests/fail/argument_not_extractor.rs:4:23
|
||||
--> tests/debug_handler/fail/argument_not_extractor.rs:4:23
|
||||
|
|
||||
4 | async fn handler(foo: bool) {}
|
||||
| ^^^^ the trait `FromRequest<Body>` is not implemented for `bool`
|
|
@ -2,7 +2,7 @@ use axum::{
|
|||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
};
|
||||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
struct A;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
error: Handlers must only take owned values
|
||||
--> tests/fail/extract_self_mut.rs:23:22
|
||||
--> tests/debug_handler/fail/extract_self_mut.rs:23:22
|
||||
|
|
||||
23 | async fn handler(&mut self) {}
|
||||
| ^^^^^^^^^
|
|
@ -2,7 +2,7 @@ use axum::{
|
|||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
};
|
||||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
struct A;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
error: Handlers must only take owned values
|
||||
--> tests/fail/extract_self_ref.rs:23:22
|
||||
--> tests/debug_handler/fail/extract_self_ref.rs:23:22
|
||||
|
|
||||
23 | async fn handler(&self) {}
|
||||
| ^^^^^
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler<T>() {}
|
|
@ -1,11 +1,11 @@
|
|||
error: `#[axum_debug::debug_handler]` doesn't support generic functions
|
||||
--> tests/fail/generics.rs:4:17
|
||||
error: `#[axum_macros::debug_handler]` doesn't support generic functions
|
||||
--> tests/debug_handler/fail/generics.rs:4:17
|
||||
|
|
||||
4 | async fn handler<T>() {}
|
||||
| ^^^
|
||||
|
||||
error[E0282]: type annotations needed
|
||||
--> tests/fail/generics.rs:4:10
|
||||
--> tests/debug_handler/fail/generics.rs:4:10
|
||||
|
|
||||
4 | async fn handler<T>() {}
|
||||
| ----- ^^^^^^^ cannot infer type for type parameter `T` declared on the function `handler`
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler(foo)]
|
||||
async fn handler() {}
|
|
@ -1,5 +1,5 @@
|
|||
error: unknown argument
|
||||
--> tests/fail/invalid_attrs.rs:3:17
|
||||
--> tests/debug_handler/fail/invalid_attrs.rs:3:17
|
||||
|
|
||||
3 | #[debug_handler(foo)]
|
||||
| ^^^
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
struct A;
|
|
@ -0,0 +1,5 @@
|
|||
error: expected `fn`
|
||||
--> tests/debug_handler/fail/not_a_function.rs:4:1
|
||||
|
|
||||
4 | struct A;
|
||||
| ^^^^^^
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
fn handler() {}
|
|
@ -1,5 +1,5 @@
|
|||
error: Handlers must be `async fn`s
|
||||
--> tests/fail/not_async.rs:4:1
|
||||
--> tests/debug_handler/fail/not_async.rs:4:1
|
||||
|
|
||||
4 | fn handler() {}
|
||||
| ^^
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler() {
|
|
@ -1,12 +1,12 @@
|
|||
error: future cannot be sent between threads safely
|
||||
--> tests/fail/not_send.rs:4:1
|
||||
--> tests/debug_handler/fail/not_send.rs:4:1
|
||||
|
|
||||
4 | async fn handler() {
|
||||
| ^^^^^ future returned by `handler` is not `Send`
|
||||
|
|
||||
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<()>`
|
||||
note: future is not `Send` as this value is used across an await
|
||||
--> tests/fail/not_send.rs:6:5
|
||||
--> tests/debug_handler/fail/not_send.rs:6:5
|
||||
|
|
||||
5 | let rc = std::rc::Rc::new(());
|
||||
| -- has type `Rc<()>` which is not `Send`
|
||||
|
@ -15,7 +15,7 @@ note: future is not `Send` as this value is used across an await
|
|||
7 | }
|
||||
| - `rc` is later dropped here
|
||||
note: required by a bound in `check`
|
||||
--> tests/fail/not_send.rs:4:1
|
||||
--> tests/debug_handler/fail/not_send.rs:4:1
|
||||
|
|
||||
4 | async fn handler() {
|
||||
| ^^^^^ required by this bound in `check`
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler(
|
|
@ -1,5 +1,5 @@
|
|||
error: Handlers cannot take more than 16 arguments. Use `(a, b): (ExtractorA, ExtractorA)` to further nest extractors
|
||||
--> tests/fail/too_many_extractors.rs:5:5
|
||||
--> tests/debug_handler/fail/too_many_extractors.rs:5:5
|
||||
|
|
||||
5 | / e1: String,
|
||||
6 | | e2: String,
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler() -> bool {
|
|
@ -1,11 +1,11 @@
|
|||
error[E0277]: the trait bound `bool: IntoResponse` is not satisfied
|
||||
--> tests/fail/wrong_return_type.rs:4:23
|
||||
--> tests/debug_handler/fail/wrong_return_type.rs:4:23
|
||||
|
|
||||
4 | async fn handler() -> bool {
|
||||
| ^^^^ the trait `IntoResponse` is not implemented for `bool`
|
||||
|
|
||||
note: required by a bound in `__axum_debug_check_handler_into_response::{closure#0}::check`
|
||||
--> tests/fail/wrong_return_type.rs:4:23
|
||||
note: required by a bound in `__axum_macros_check_handler_into_response::{closure#0}::check`
|
||||
--> tests/debug_handler/fail/wrong_return_type.rs:4:23
|
||||
|
|
||||
4 | async fn handler() -> bool {
|
||||
| ^^^^ required by this bound in `__axum_debug_check_handler_into_response::{closure#0}::check`
|
||||
| ^^^^ required by this bound in `__axum_macros_check_handler_into_response::{closure#0}::check`
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
struct A;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use axum::{body::BoxBody, http::Request};
|
||||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler(body = BoxBody)]
|
||||
async fn handler(_: Request<BoxBody>) {}
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
use std::future::Future;
|
||||
|
||||
#[debug_handler]
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
#[debug_handler]
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler(_one: String, _two: String, _three: String) {}
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
#[debug_handler]
|
||||
async fn handler(mut foo: String) -> String {
|
|
@ -1,4 +1,4 @@
|
|||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
use std::future::{Ready, ready};
|
||||
|
||||
#[debug_handler]
|
|
@ -3,7 +3,7 @@ use axum::{
|
|||
extract::{FromRequest, RequestParts},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use axum::response::{IntoResponse, Response};
|
||||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
struct A;
|
||||
|
|
@ -2,7 +2,7 @@ use axum::{
|
|||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
};
|
||||
use axum_debug::debug_handler;
|
||||
use axum_macros::debug_handler;
|
||||
|
||||
struct A;
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
error: `via` specified more than once
|
||||
--> tests/fail/double_via_attr.rs:5:49
|
||||
--> tests/from_request/fail/double_via_attr.rs:5:49
|
||||
|
|
||||
5 | struct Extractor(#[from_request(via(Extension), via(Extension))] State);
|
||||
| ^^^
|
||||
|
||||
warning: unused import: `axum::extract::Extension`
|
||||
--> tests/fail/double_via_attr.rs:2:5
|
||||
--> tests/from_request/fail/double_via_attr.rs:2:5
|
||||
|
|
||||
2 | use axum::extract::Extension;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -1,5 +1,5 @@
|
|||
error: `#[derive(FromRequest)] doesn't support generics
|
||||
--> tests/fail/generic.rs:4:17
|
||||
--> tests/from_request/fail/generic.rs:4:17
|
||||
|
|
||||
4 | struct Extractor<T>(Option<T>);
|
||||
| ^^^
|
|
@ -1,5 +1,5 @@
|
|||
error: expected `via`
|
||||
--> tests/fail/unknown_attr.rs:4:33
|
||||
--> tests/from_request/fail/unknown_attr.rs:4:33
|
||||
|
|
||||
4 | struct Extractor(#[from_request(foo)] String);
|
||||
| ^^^
|
|
@ -1,11 +1,11 @@
|
|||
error: `#[from_request(via(...))]` on a field cannot be used together with `#[from_request(...)]` on the container
|
||||
--> tests/fail/via_on_container_and_field.rs:6:33
|
||||
--> tests/from_request/fail/via_on_container_and_field.rs:6:33
|
||||
|
|
||||
6 | struct Extractor(#[from_request(via(Extension))] State);
|
||||
| ^^^
|
||||
|
||||
warning: unused import: `axum::extract::Extension`
|
||||
--> tests/fail/via_on_container_and_field.rs:2:5
|
||||
--> tests/from_request/fail/via_on_container_and_field.rs:2:5
|
||||
|
|
||||
2 | use axum::extract::Extension;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -5,4 +5,4 @@ can be converted [into a response](crate::response).
|
|||
Handlers is where your application logic lives and axum applications are built
|
||||
by routing between handlers.
|
||||
|
||||
[`debug_handler`]: https://docs.rs/axum-debug/latest/axum_debug/attr.debug_handler.html
|
||||
[`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
|
||||
|
|
|
@ -66,9 +66,9 @@
|
|||
//!
|
||||
//! This error doesn't tell you _why_ your function doesn't implement
|
||||
//! [`Handler`]. It's possible to improve the error with the [`debug_handler`]
|
||||
//! proc-macro from the [axum-debug] crate.
|
||||
//! proc-macro from the [axum-macros] crate.
|
||||
//!
|
||||
//! [axum-debug]: https://docs.rs/axum-debug
|
||||
//! [axum-macros]: https://docs.rs/axum-macros
|
||||
|
||||
use crate::{
|
||||
body::{boxed, Body, Bytes, HttpBody},
|
||||
|
|
|
@ -406,8 +406,8 @@
|
|||
//! [`HeaderMap`]: http::header::HeaderMap
|
||||
//! [`Request`]: http::Request
|
||||
//! [customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
|
||||
//! [axum-debug]: https://docs.rs/axum-debug
|
||||
//! [`debug_handler`]: https://docs.rs/axum-debug/latest/axum_debug/attr.debug_handler.html
|
||||
//! [axum-macros]: https://docs.rs/axum-macros
|
||||
//! [`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
|
||||
//! [`Handler`]: crate::handler::Handler
|
||||
//! [`Infallible`]: std::convert::Infallible
|
||||
//! [load shed]: tower::load_shed
|
||||
|
|
Loading…
Reference in a new issue