mirror of
https://github.com/tokio-rs/axum.git
synced 2024-10-24 09:56:56 +02:00
Make axum-debug
handle more cases (#518)
* Make `axum-debug` handle more cases * Only just trybuild tests on stable * revert changes to hello-world example * Remove a bit of duplication * return error on generics * address review feedback * Support associated functions with receiver or returns `Self` * fix indentation
This commit is contained in:
parent
22931688f7
commit
f1f004a057
24 changed files with 408 additions and 275 deletions
|
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- Make macro handle more cases such as mutable extractors and handlers taking
|
||||||
|
`self` ([#518])
|
||||||
|
|
||||||
|
[#518]: https://github.com/tokio-rs/axum/pull/518
|
||||||
|
|
||||||
# 0.2.0 (13. October 2021)
|
# 0.2.0 (13. October 2021)
|
||||||
|
|
||||||
- **breaking:** Removed `debug_router` macro.
|
- **breaking:** Removed `debug_router` macro.
|
||||||
|
|
|
@ -21,3 +21,4 @@ syn = { version = "1.0", features = ["full"] }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum = { path = "../axum", version = "0.3" }
|
axum = { path = "../axum", version = "0.3" }
|
||||||
trybuild = "1.0"
|
trybuild = "1.0"
|
||||||
|
rustversion = "1.0"
|
||||||
|
|
|
@ -124,298 +124,273 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
/// Generates better error messages when applied to a handler function.
|
/// Generates better error messages when applied to a handler function.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Function is not async:
|
|
||||||
///
|
|
||||||
/// ```rust,compile_fail
|
|
||||||
/// #[axum_debug::debug_handler]
|
|
||||||
/// fn handler() -> &'static str {
|
|
||||||
/// "Hello, world"
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// error: handlers must be async functions
|
|
||||||
/// --> main.rs:xx:1
|
|
||||||
/// |
|
|
||||||
/// xx | fn handler() -> &'static str {
|
|
||||||
/// | ^^
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Wrong return type:
|
|
||||||
///
|
|
||||||
/// ```rust,compile_fail
|
|
||||||
/// #[axum_debug::debug_handler]
|
|
||||||
/// async fn handler() -> bool {
|
|
||||||
/// false
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// error[E0277]: the trait bound `bool: IntoResponse` is not satisfied
|
|
||||||
/// --> main.rs:xx:23
|
|
||||||
/// |
|
|
||||||
/// xx | async fn handler() -> bool {
|
|
||||||
/// | ^^^^
|
|
||||||
/// | |
|
|
||||||
/// | the trait `IntoResponse` is not implemented for `bool`
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Wrong extractor:
|
|
||||||
///
|
|
||||||
/// ```rust,compile_fail
|
|
||||||
/// #[axum_debug::debug_handler]
|
|
||||||
/// async fn handler(a: bool) -> String {
|
|
||||||
/// format!("Can I extract a bool? {}", a)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// error[E0277]: the trait bound `bool: FromRequest` is not satisfied
|
|
||||||
/// --> main.rs:xx:21
|
|
||||||
/// |
|
|
||||||
/// xx | async fn handler(a: bool) -> String {
|
|
||||||
/// | ^^^^
|
|
||||||
/// | |
|
|
||||||
/// | the trait `FromRequest` is not implemented for `bool`
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Too many extractors:
|
|
||||||
///
|
|
||||||
/// ```rust,compile_fail
|
|
||||||
/// #[axum_debug::debug_handler]
|
|
||||||
/// async fn handler(
|
|
||||||
/// a: String,
|
|
||||||
/// b: String,
|
|
||||||
/// c: String,
|
|
||||||
/// d: String,
|
|
||||||
/// e: String,
|
|
||||||
/// f: String,
|
|
||||||
/// g: String,
|
|
||||||
/// h: String,
|
|
||||||
/// i: String,
|
|
||||||
/// j: String,
|
|
||||||
/// k: String,
|
|
||||||
/// l: String,
|
|
||||||
/// m: String,
|
|
||||||
/// n: String,
|
|
||||||
/// o: String,
|
|
||||||
/// p: String,
|
|
||||||
/// q: String,
|
|
||||||
/// ) {}
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// error: too many extractors. 16 extractors are allowed
|
|
||||||
/// note: you can nest extractors like "a: (Extractor, Extractor), b: (Extractor, Extractor)"
|
|
||||||
/// --> main.rs:xx:5
|
|
||||||
/// |
|
|
||||||
/// xx | / a: String,
|
|
||||||
/// xx | | b: String,
|
|
||||||
/// xx | | c: String,
|
|
||||||
/// xx | | d: String,
|
|
||||||
/// ... |
|
|
||||||
/// xx | | p: String,
|
|
||||||
/// xx | | q: String,
|
|
||||||
/// | |______________^
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Future is not [`Send`]:
|
|
||||||
///
|
|
||||||
/// ```rust,compile_fail
|
|
||||||
/// #[axum_debug::debug_handler]
|
|
||||||
/// async fn handler() {
|
|
||||||
/// let not_send = std::rc::Rc::new(());
|
|
||||||
///
|
|
||||||
/// async {}.await;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// error: future cannot be sent between threads safely
|
|
||||||
/// --> main.rs:xx:10
|
|
||||||
/// |
|
|
||||||
/// xx | async fn handler() {
|
|
||||||
/// | ^^^^^^^
|
|
||||||
/// | |
|
|
||||||
/// | future returned by `handler` is not `Send`
|
|
||||||
/// ```
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
return input;
|
return input;
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
return debug::apply_debug_handler(input);
|
return debug_handler::expand(_attr, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod debug {
|
mod debug_handler {
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote_spanned};
|
use quote::{format_ident, quote, quote_spanned};
|
||||||
use syn::{parse_macro_input, spanned::Spanned, FnArg, Ident, ItemFn, ReturnType, Signature};
|
use syn::{parse::Parse, spanned::Spanned, FnArg, ItemFn};
|
||||||
|
|
||||||
pub(crate) fn apply_debug_handler(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub(crate) fn expand(
|
||||||
let function = parse_macro_input!(input as ItemFn);
|
attr: proc_macro::TokenStream,
|
||||||
|
input: proc_macro::TokenStream,
|
||||||
let vis = &function.vis;
|
) -> proc_macro::TokenStream {
|
||||||
let sig = &function.sig;
|
match try_expand(attr.into(), input.into()) {
|
||||||
let ident = &sig.ident;
|
Ok(tokens) => tokens.into(),
|
||||||
let span = ident.span();
|
Err(err) => err.into_compile_error().into(),
|
||||||
let len = sig.inputs.len();
|
|
||||||
let generics = create_generics(len);
|
|
||||||
let params = sig.inputs.iter().map(|fn_arg| {
|
|
||||||
if let FnArg::Typed(pat_type) = fn_arg {
|
|
||||||
&pat_type.pat
|
|
||||||
} else {
|
|
||||||
panic!("not a handler function");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let block = &function.block;
|
|
||||||
|
|
||||||
if let Err(err) = async_check(sig) {
|
|
||||||
return err.into_compile_error().into();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(err) = param_limit_check(sig) {
|
pub(crate) fn try_expand(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
return err.into_compile_error().into();
|
syn::parse2::<Attrs>(attr)?;
|
||||||
}
|
let item_fn = syn::parse2::<ItemFn>(input.clone())?;
|
||||||
|
|
||||||
let check_trait = check_trait_code(sig, &generics);
|
check_extractor_count(&item_fn)?;
|
||||||
let check_return = check_return_code(sig, &generics);
|
|
||||||
let check_params = match check_params_code(sig, &generics) {
|
let check_inputs_impls_from_request = check_inputs_impls_from_request(&item_fn);
|
||||||
Ok(tokens) => tokens,
|
let check_output_impls_into_response = check_output_impls_into_response(&item_fn);
|
||||||
Err(err) => return err.into_compile_error().into(),
|
let check_future_send = check_future_send(&item_fn);
|
||||||
|
|
||||||
|
let tokens = quote! {
|
||||||
|
#input
|
||||||
|
#check_inputs_impls_from_request
|
||||||
|
#check_output_impls_into_response
|
||||||
|
#check_future_send
|
||||||
};
|
};
|
||||||
|
|
||||||
let expanded = quote_spanned! {span=>
|
Ok(tokens)
|
||||||
#vis #sig {
|
|
||||||
#check_trait
|
|
||||||
#check_return
|
|
||||||
#(#check_params)*
|
|
||||||
|
|
||||||
#sig #block
|
|
||||||
|
|
||||||
#ident(#(#params),*).await
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expanded.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_generics(len: usize) -> Vec<Ident> {
|
struct Attrs;
|
||||||
(1..=len).map(|i| format_ident!("T{}", i)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_check(sig: &Signature) -> Result<(), syn::Error> {
|
impl Parse for Attrs {
|
||||||
if sig.asyncness.is_none() {
|
fn parse(_input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
Err(syn::Error::new_spanned(
|
Ok(Self)
|
||||||
sig.fn_token,
|
|
||||||
"handlers must be async functions",
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn param_limit_check(sig: &Signature) -> Result<(), syn::Error> {
|
fn check_extractor_count(item_fn: &ItemFn) -> syn::Result<()> {
|
||||||
let max_extractors = 16;
|
let max_extractors = 16;
|
||||||
|
if item_fn.sig.inputs.len() <= max_extractors {
|
||||||
if sig.inputs.len() > max_extractors {
|
|
||||||
let msg = format!(
|
|
||||||
"too many extractors. {} extractors are allowed\n\
|
|
||||||
note: you can nest extractors like \"a: (Extractor, Extractor), b: (Extractor, Extractor)\"",
|
|
||||||
max_extractors
|
|
||||||
);
|
|
||||||
|
|
||||||
Err(syn::Error::new_spanned(&sig.inputs, msg))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
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_trait_code(sig: &Signature, generics: &[Ident]) -> proc_macro2::TokenStream {
|
fn check_inputs_impls_from_request(item_fn: &ItemFn) -> TokenStream {
|
||||||
let ident = &sig.ident;
|
if !item_fn.sig.generics.params.is_empty() {
|
||||||
let span = ident.span();
|
return syn::Error::new_spanned(
|
||||||
|
&item_fn.sig.generics,
|
||||||
quote_spanned! {span=>
|
"`#[axum_debug::debug_handler]` doesn't support generic functions",
|
||||||
{
|
)
|
||||||
debug_handler(#ident);
|
.into_compile_error();
|
||||||
|
|
||||||
fn debug_handler<F, Fut, #(#generics),*>(_f: F)
|
|
||||||
where
|
|
||||||
F: ::std::ops::FnOnce(#(#generics),*) -> Fut + Clone + Send + 'static,
|
|
||||||
Fut: ::std::future::Future + Send,
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn check_return_code(sig: &Signature, generics: &[Ident]) -> proc_macro2::TokenStream {
|
item_fn
|
||||||
let span = match &sig.output {
|
.sig
|
||||||
ReturnType::Default => sig.output.span(),
|
.inputs
|
||||||
ReturnType::Type(_, ty) => ty.span(),
|
|
||||||
};
|
|
||||||
let ident = &sig.ident;
|
|
||||||
|
|
||||||
quote_spanned! {span=>
|
|
||||||
{
|
|
||||||
debug_handler(#ident);
|
|
||||||
|
|
||||||
fn debug_handler<F, Fut, Res, #(#generics),*>(_f: F)
|
|
||||||
where
|
|
||||||
F: ::std::ops::FnOnce(#(#generics),*) -> Fut,
|
|
||||||
Fut: ::std::future::Future<Output = Res>,
|
|
||||||
Res: ::axum::response::IntoResponse,
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_params_code(
|
|
||||||
sig: &Signature,
|
|
||||||
generics: &[Ident],
|
|
||||||
) -> Result<Vec<TokenStream>, syn::Error> {
|
|
||||||
let ident = &sig.ident;
|
|
||||||
generics
|
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.map(|arg| {
|
||||||
.map(|(i, generic)| {
|
let (span, ty) = match arg {
|
||||||
let span = match &sig.inputs[i] {
|
|
||||||
FnArg::Typed(pat_type) => pat_type.ty.span(),
|
|
||||||
FnArg::Receiver(receiver) => {
|
FnArg::Receiver(receiver) => {
|
||||||
// TODO: look into whether its possible to make this work
|
if receiver.reference.is_some() {
|
||||||
return Err(syn::Error::new_spanned(
|
return syn::Error::new_spanned(
|
||||||
receiver,
|
receiver,
|
||||||
"`#[debug_handler]` is not supported on methods",
|
"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 tokens = quote_spanned! {span=>
|
let name = format_ident!("__axum_debug_check_{}_from_request", item_fn.sig.ident);
|
||||||
{
|
quote_spanned! {span=>
|
||||||
debug_handler(#ident);
|
#[allow(warnings)]
|
||||||
|
fn #name()
|
||||||
fn debug_handler<F, Fut, #(#generics),*>(_f: F)
|
where
|
||||||
where
|
#ty: ::axum::extract::FromRequest + Send,
|
||||||
F: ::std::ops::FnOnce(#(#generics),*) -> Fut,
|
{}
|
||||||
Fut: ::std::future::Future,
|
}
|
||||||
#generic: ::axum::extract::FromRequest + Send,
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(tokens)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.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 make_value_name = format_ident!(
|
||||||
|
"__axum_debug_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 { panic!() }
|
||||||
|
}
|
||||||
|
} else if let syn::Type::ImplTrait(_) = &**ty {
|
||||||
|
// lets just assume it returns `impl Future`
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#[allow(warnings)]
|
||||||
|
fn #make_value_name() -> #ty { async { panic!() } }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#[allow(warnings)]
|
||||||
|
fn #make_value_name() -> #ty { panic!() }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = format_ident!("__axum_debug_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_debug_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]
|
#[test]
|
||||||
fn ui() {
|
fn ui() {
|
||||||
let t = trybuild::TestCases::new();
|
#[rustversion::stable]
|
||||||
t.pass("tests/pass/*.rs");
|
fn go() {
|
||||||
t.compile_fail("tests/fail/*.rs");
|
let t = trybuild::TestCases::new();
|
||||||
|
t.compile_fail("tests/fail/*.rs");
|
||||||
|
t.pass("tests/pass/*.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::not(stable)]
|
||||||
|
fn go() {}
|
||||||
|
|
||||||
|
go();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,4 @@ error[E0277]: the trait bound `bool: FromRequest` is not satisfied
|
||||||
4 | async fn handler(foo: bool) {}
|
4 | async fn handler(foo: bool) {}
|
||||||
| ^^^^ the trait `FromRequest` is not implemented for `bool`
|
| ^^^^ the trait `FromRequest` is not implemented for `bool`
|
||||||
|
|
|
|
||||||
note: required by a bound in `handler::{closure#0}::debug_handler`
|
= help: see issue #48214
|
||||||
--> tests/fail/argument_not_extractor.rs:4:23
|
|
||||||
|
|
|
||||||
4 | async fn handler(foo: bool) {}
|
|
||||||
| ^^^^ required by this bound in `handler::{closure#0}::debug_handler`
|
|
||||||
|
|
6
axum-debug/tests/fail/attrs.rs
Normal file
6
axum-debug/tests/fail/attrs.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
|
||||||
|
#[debug_handler(foo)]
|
||||||
|
async fn handler() {}
|
||||||
|
|
||||||
|
fn main() {}
|
5
axum-debug/tests/fail/attrs.stderr
Normal file
5
axum-debug/tests/fail/attrs.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: unexpected token
|
||||||
|
--> tests/fail/attrs.rs:3:17
|
||||||
|
|
|
||||||
|
3 | #[debug_handler(foo)]
|
||||||
|
| ^^^
|
23
axum-debug/tests/fail/extract_self_mut.rs
Normal file
23
axum-debug/tests/fail/extract_self_mut.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::{FromRequest, RequestParts},
|
||||||
|
};
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for A {
|
||||||
|
type Rejection = ();
|
||||||
|
|
||||||
|
async fn from_request(_req: &mut RequestParts) -> Result<Self, Self::Rejection> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
5
axum-debug/tests/fail/extract_self_mut.stderr
Normal file
5
axum-debug/tests/fail/extract_self_mut.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: Handlers must only take owned values
|
||||||
|
--> tests/fail/extract_self_mut.rs:20:22
|
||||||
|
|
|
||||||
|
20 | async fn handler(&mut self) {}
|
||||||
|
| ^^^^^^^^^
|
23
axum-debug/tests/fail/extract_self_ref.rs
Normal file
23
axum-debug/tests/fail/extract_self_ref.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::{FromRequest, RequestParts},
|
||||||
|
};
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequest for A {
|
||||||
|
type Rejection = ();
|
||||||
|
|
||||||
|
async fn from_request(_req: &mut RequestParts) -> Result<Self, Self::Rejection> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
5
axum-debug/tests/fail/extract_self_ref.stderr
Normal file
5
axum-debug/tests/fail/extract_self_ref.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: Handlers must only take owned values
|
||||||
|
--> tests/fail/extract_self_ref.rs:20:22
|
||||||
|
|
|
||||||
|
20 | async fn handler(&self) {}
|
||||||
|
| ^^^^^
|
6
axum-debug/tests/fail/generics.rs
Normal file
6
axum-debug/tests/fail/generics.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler<T>() {}
|
||||||
|
|
||||||
|
fn main() {}
|
13
axum-debug/tests/fail/generics.stderr
Normal file
13
axum-debug/tests/fail/generics.stderr
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
error: `#[axum_debug::debug_handler]` doesn't support generic functions
|
||||||
|
--> tests/fail/generics.rs:4:17
|
||||||
|
|
|
||||||
|
4 | async fn handler<T>() {}
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error[E0282]: type annotations needed
|
||||||
|
--> tests/fail/generics.rs:4:10
|
||||||
|
|
|
||||||
|
4 | async fn handler<T>() {}
|
||||||
|
| ----- ^^^^^^^ cannot infer type for type parameter `T` declared on the function `handler`
|
||||||
|
| |
|
||||||
|
| consider giving `future` a type
|
|
@ -1,4 +1,4 @@
|
||||||
error: handlers must be async functions
|
error: Handlers must be `async fn`s
|
||||||
--> tests/fail/not_async.rs:4:1
|
--> tests/fail/not_async.rs:4:1
|
||||||
|
|
|
|
||||||
4 | fn handler() {}
|
4 | fn handler() {}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
error: future cannot be sent between threads safely
|
error: future cannot be sent between threads safely
|
||||||
--> tests/fail/not_send.rs:4:10
|
--> tests/fail/not_send.rs:4:1
|
||||||
|
|
|
|
||||||
4 | async fn handler() {
|
4 | async fn handler() {
|
||||||
| ^^^^^^^ future returned by `handler` is not `Send`
|
| ^^^^^ future returned by `handler` is not `Send`
|
||||||
|
|
|
|
||||||
= help: within `impl Future`, the trait `Send` is not implemented for `Rc<()>`
|
= help: within `impl Future`, the trait `Send` is not implemented for `Rc<()>`
|
||||||
note: future is not `Send` as this value is used across an await
|
note: future is not `Send` as this value is used across an await
|
||||||
|
@ -14,8 +14,8 @@ note: future is not `Send` as this value is used across an await
|
||||||
| ^^^^^^^^^^^^^^ await occurs here, with `rc` maybe used later
|
| ^^^^^^^^^^^^^^ await occurs here, with `rc` maybe used later
|
||||||
7 | }
|
7 | }
|
||||||
| - `rc` is later dropped here
|
| - `rc` is later dropped here
|
||||||
note: required by a bound in `handler::{closure#0}::debug_handler`
|
note: required by a bound in `check`
|
||||||
--> tests/fail/not_send.rs:4:10
|
--> tests/fail/not_send.rs:4:1
|
||||||
|
|
|
|
||||||
4 | async fn handler() {
|
4 | async fn handler() {
|
||||||
| ^^^^^^^ required by this bound in `handler::{closure#0}::debug_handler`
|
| ^^^^^ required by this bound in `check`
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
error: `#[debug_handler]` is not supported on methods
|
|
||||||
--> tests/fail/self_receiver.rs:17:22
|
|
||||||
|
|
|
||||||
17 | async fn handler(self) {}
|
|
||||||
| ^^^^
|
|
|
@ -1,5 +1,4 @@
|
||||||
error: too many extractors. 16 extractors are allowed
|
error: Handlers cannot take more than 16 arguments. Use `(a, b): (ExtractorA, ExtractorA)` to further nest extractors
|
||||||
note: you can nest extractors like "a: (Extractor, Extractor), b: (Extractor, Extractor)"
|
|
||||||
--> tests/fail/too_many_extractors.rs:5:5
|
--> tests/fail/too_many_extractors.rs:5:5
|
||||||
|
|
|
|
||||||
5 | / e1: String,
|
5 | / e1: String,
|
||||||
|
|
|
@ -4,8 +4,8 @@ error[E0277]: the trait bound `bool: IntoResponse` is not satisfied
|
||||||
4 | async fn handler() -> bool {
|
4 | async fn handler() -> bool {
|
||||||
| ^^^^ the trait `IntoResponse` is not implemented for `bool`
|
| ^^^^ the trait `IntoResponse` is not implemented for `bool`
|
||||||
|
|
|
|
||||||
note: required by a bound in `handler::{closure#0}::debug_handler`
|
note: required by a bound in `__axum_debug_check_handler_into_response::{closure#0}::check`
|
||||||
--> tests/fail/wrong_return_type.rs:4:23
|
--> tests/fail/wrong_return_type.rs:4:23
|
||||||
|
|
|
|
||||||
4 | async fn handler() -> bool {
|
4 | async fn handler() -> bool {
|
||||||
| ^^^^ required by this bound in `handler::{closure#0}::debug_handler`
|
| ^^^^ required by this bound in `__axum_debug_check_handler_into_response::{closure#0}::check`
|
||||||
|
|
10
axum-debug/tests/pass/associated_fn_without_self.rs
Normal file
10
axum-debug/tests/pass/associated_fn_without_self.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
9
axum-debug/tests/pass/impl_future.rs
Normal file
9
axum-debug/tests/pass/impl_future.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
fn handler() -> impl Future<Output = ()> {
|
||||||
|
async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
9
axum-debug/tests/pass/impl_into_response.rs
Normal file
9
axum-debug/tests/pass/impl_into_response.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler() -> impl IntoResponse {
|
||||||
|
"hi!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
9
axum-debug/tests/pass/mut_extractor.rs
Normal file
9
axum-debug/tests/pass/mut_extractor.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler(mut foo: String) -> String {
|
||||||
|
foo += "bar";
|
||||||
|
foo
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
9
axum-debug/tests/pass/ready.rs
Normal file
9
axum-debug/tests/pass/ready.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
use std::future::{Ready, ready};
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
fn handler() -> Ready<()> {
|
||||||
|
ready(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
27
axum-debug/tests/pass/returns_self.rs
Normal file
27
axum-debug/tests/pass/returns_self.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use axum::{
|
||||||
|
body::{Bytes, Full},
|
||||||
|
http::Response,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use axum_debug::debug_handler;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
#[debug_handler]
|
||||||
|
async fn handler() -> Self {
|
||||||
|
A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for A {
|
||||||
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -1,5 +1,8 @@
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::{FromRequest, RequestParts},
|
||||||
|
};
|
||||||
use axum_debug::debug_handler;
|
use axum_debug::debug_handler;
|
||||||
use axum::{async_trait, extract::{FromRequest, RequestParts}};
|
|
||||||
|
|
||||||
struct A;
|
struct A;
|
||||||
|
|
Loading…
Reference in a new issue