diff --git a/axum-macros/CHANGELOG.md b/axum-macros/CHANGELOG.md index 5429822f..3203bd8d 100644 --- a/axum-macros/CHANGELOG.md +++ b/axum-macros/CHANGELOG.md @@ -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) diff --git a/axum-macros/src/debug_handler.rs b/axum-macros/src/debug_handler.rs index 8a336799..9b42a1af 100644 --- a/axum-macros/src/debug_handler.rs +++ b/axum-macros/src/debug_handler.rs @@ -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, @@ -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! { } @@ -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 @@ -300,35 +306,9 @@ fn check_input_order(item_fn: &ItemFn) -> Option { FnArg::Typed(pat_type) => &*pat_type.ty, FnArg::Receiver(_) => return None, }; - let span = ty.span(); + let type_name = request_consuming_type_name(ty)?; - 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((idx, type_name, span)) + Some((idx, type_name, ty.span())) }) .collect::>(); @@ -343,7 +323,7 @@ fn check_input_order(item_fn: &ItemFn) -> Option { 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); @@ -386,6 +366,36 @@ fn check_input_order(item_fn: &ItemFn) -> Option { } } +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! {}, diff --git a/axum-macros/tests/debug_handler/fail/json_not_deserialize.rs b/axum-macros/tests/debug_handler/fail/json_not_deserialize.rs new file mode 100644 index 00000000..d894b0bd --- /dev/null +++ b/axum-macros/tests/debug_handler/fail/json_not_deserialize.rs @@ -0,0 +1,9 @@ +use axum::Json; +use axum_macros::debug_handler; + +struct Struct {} + +#[debug_handler] +async fn handler(foo: Json) {} + +fn main() {} diff --git a/axum-macros/tests/debug_handler/fail/json_not_deserialize.stderr b/axum-macros/tests/debug_handler/fail/json_not_deserialize.stderr new file mode 100644 index 00000000..e93ae20c --- /dev/null +++ b/axum-macros/tests/debug_handler/fail/json_not_deserialize.stderr @@ -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) {} + | ^^^^^^^^^^^^ 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` to implement `FromRequest<(), Body>` + = help: see issue #48214 + = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable