Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve debug_handler message for generic request-consuming extractors #1826

Merged
merged 3 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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