Skip to content

Commit

Permalink
Improve debug_handler message for generic request-consuming extractors (
Browse files Browse the repository at this point in the history
#1826)

Co-authored-by: David Pedersen <david.pdrsn@gmail.com>
  • Loading branch information
jplatte and davidpdrsn authored Mar 10, 2023
1 parent 1327a59 commit a26ddd1
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 31 deletions.
5 changes: 4 additions & 1 deletion axum-macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

- None.
- **fixed:** Improve `#[debug_handler]` message for known generic
request-consuming extractors ([#1826])

[#1826]: https://github.com/tokio-rs/axum/pull/1826

# 0.3.5 (03. March, 2023)

Expand Down
70 changes: 40 additions & 30 deletions axum-macros/src/debug_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ fn check_inputs_impls_from_request(
}
};

let consumes_request = request_consuming_type_name(&ty).is_some();

let check_fn = format_ident!(
"__axum_macros_check_{}_{}_from_request_check",
item_fn.sig.ident,
Expand All @@ -252,7 +254,7 @@ fn check_inputs_impls_from_request(
}
};

let check_fn_generics = if must_impl_from_request_parts {
let check_fn_generics = if must_impl_from_request_parts || consumes_request {
quote! {}
} else {
quote! { <M> }
Expand All @@ -262,6 +264,10 @@ fn check_inputs_impls_from_request(
quote_spanned! {span=>
#ty: ::axum::extract::FromRequestParts<#state_ty> + Send
}
} else if consumes_request {
quote_spanned! {span=>
#ty: ::axum::extract::FromRequest<#state_ty, #body_ty> + Send
}
} else {
quote_spanned! {span=>
#ty: ::axum::extract::FromRequest<#state_ty, #body_ty, M> + Send
Expand Down Expand Up @@ -300,35 +306,9 @@ fn check_input_order(item_fn: &ItemFn) -> Option<TokenStream> {
FnArg::Typed(pat_type) => &*pat_type.ty,
FnArg::Receiver(_) => return None,
};
let span = ty.span();

let path = match ty {
Type::Path(type_path) => &type_path.path,
_ => return None,
};

let ident = match path.segments.last() {
Some(path_segment) => &path_segment.ident,
None => return None,
};

let type_name = match &*ident.to_string() {
"Json" => "Json<_>",
"BodyStream" => "BodyStream",
"RawBody" => "RawBody<_>",
"RawForm" => "RawForm",
"Multipart" => "Multipart",
"Protobuf" => "Protobuf",
"JsonLines" => "JsonLines<_>",
"Form" => "Form<_>",
"Request" => "Request<_>",
"Bytes" => "Bytes",
"String" => "String",
"Parts" => "Parts",
_ => return None,
};
let type_name = request_consuming_type_name(ty)?;

Some((idx, type_name, span))
Some((idx, type_name, ty.span()))
})
.collect::<Vec<_>>();

Expand All @@ -343,7 +323,7 @@ fn check_input_order(item_fn: &ItemFn) -> Option<TokenStream> {
let (_idx, type_name, span) = &types_that_consume_the_request[0];
let error = format!(
"`{type_name}` consumes the request body and thus must be \
the last argument to the handler function"
the last argument to the handler function"
);
return Some(quote_spanned! {*span=>
compile_error!(#error);
Expand Down Expand Up @@ -386,6 +366,36 @@ fn check_input_order(item_fn: &ItemFn) -> Option<TokenStream> {
}
}

fn request_consuming_type_name(ty: &Type) -> Option<&'static str> {
let path = match ty {
Type::Path(type_path) => &type_path.path,
_ => return None,
};

let ident = match path.segments.last() {
Some(path_segment) => &path_segment.ident,
None => return None,
};

let type_name = match &*ident.to_string() {
"Json" => "Json<_>",
"BodyStream" => "BodyStream",
"RawBody" => "RawBody<_>",
"RawForm" => "RawForm",
"Multipart" => "Multipart",
"Protobuf" => "Protobuf",
"JsonLines" => "JsonLines<_>",
"Form" => "Form<_>",
"Request" => "Request<_>",
"Bytes" => "Bytes",
"String" => "String",
"Parts" => "Parts",
_ => return None,
};

Some(type_name)
}

fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream {
let ty = match &item_fn.sig.output {
syn::ReturnType::Default => return quote! {},
Expand Down
9 changes: 9 additions & 0 deletions axum-macros/tests/debug_handler/fail/json_not_deserialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use axum::Json;
use axum_macros::debug_handler;

struct Struct {}

#[debug_handler]
async fn handler(foo: Json<Struct>) {}

fn main() {}
20 changes: 20 additions & 0 deletions axum-macros/tests/debug_handler/fail/json_not_deserialize.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error[E0277]: the trait bound `for<'de> Struct: serde::de::Deserialize<'de>` is not satisfied
--> tests/debug_handler/fail/json_not_deserialize.rs:7:23
|
7 | async fn handler(foo: Json<Struct>) {}
| ^^^^^^^^^^^^ the trait `for<'de> serde::de::Deserialize<'de>` is not implemented for `Struct`
|
= help: the following other types implement trait `serde::de::Deserialize<'de>`:
&'a [u8]
&'a serde_json::raw::RawValue
&'a std::path::Path
&'a str
()
(T0, T1)
(T0, T1, T2)
(T0, T1, T2, T3)
and $N others
= note: required for `Struct` to implement `serde::de::DeserializeOwned`
= note: required for `Json<Struct>` to implement `FromRequest<(), Body>`
= help: see issue #48214
= help: add `#![feature(trivial_bounds)]` to the crate attributes to enable

0 comments on commit a26ddd1

Please sign in to comment.