From 7d88bd3a6699a548a47f9742edb612a8f8f88234 Mon Sep 17 00:00:00 2001
From: David Pedersen <david.pdrsn@gmail.com>
Date: Mon, 16 May 2022 15:11:40 +0100
Subject: [PATCH] Check for multiple body extractors in `debug_handler` (#1036)

* Check for multiple body extractors in `debug_handler`

* changelog link
---
 axum-macros/CHANGELOG.md                      |  2 ++
 axum-macros/src/debug_handler.rs              | 28 +++++++++++++++
 .../fail/multiple_body_extractors.rs          |  7 ++++
 .../fail/multiple_body_extractors.stderr      | 11 ++++++
 .../debug_handler/fail/too_many_extractors.rs | 35 ++++++++++---------
 .../fail/too_many_extractors.stderr           | 16 ++++-----
 .../debug_handler/pass/multiple_extractors.rs |  3 +-
 7 files changed, 76 insertions(+), 26 deletions(-)
 create mode 100644 axum-macros/tests/debug_handler/fail/multiple_body_extractors.rs
 create mode 100644 axum-macros/tests/debug_handler/fail/multiple_body_extractors.stderr

diff --git a/axum-macros/CHANGELOG.md b/axum-macros/CHANGELOG.md
index c1b438d9..bf1d8656 100644
--- a/axum-macros/CHANGELOG.md
+++ b/axum-macros/CHANGELOG.md
@@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - **added:** In `debug_handler`, check if `Request` is used as non-final extractor ([#1035])
 - **added:** In `debug_handler`, check if multiple `Path` extractors are used ([#1035])
+- **added:** In `debug_handler`, check if multiple body extractors are used ([#1036])
 
 [#1035]: https://github.com/tokio-rs/axum/pull/1035
+[#1036]: https://github.com/tokio-rs/axum/pull/1036
 
 # 0.2.1 (10. May, 2022)
 
diff --git a/axum-macros/src/debug_handler.rs b/axum-macros/src/debug_handler.rs
index 3404cda6..7945e8bb 100644
--- a/axum-macros/src/debug_handler.rs
+++ b/axum-macros/src/debug_handler.rs
@@ -6,6 +6,7 @@ pub(crate) fn expand(attr: Attrs, item_fn: ItemFn) -> TokenStream {
     let check_extractor_count = check_extractor_count(&item_fn);
     let check_request_last_extractor = check_request_last_extractor(&item_fn);
     let check_path_extractor = check_path_extractor(&item_fn);
+    let check_multiple_body_extractors = check_multiple_body_extractors(&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);
@@ -16,6 +17,7 @@ pub(crate) fn expand(attr: Attrs, item_fn: ItemFn) -> TokenStream {
         #check_extractor_count
         #check_request_last_extractor
         #check_path_extractor
+        #check_multiple_body_extractors
         #check_inputs_impls_from_request
         #check_output_impls_into_response
         #check_future_send
@@ -124,6 +126,32 @@ fn check_path_extractor(item_fn: &ItemFn) -> TokenStream {
     }
 }
 
+fn check_multiple_body_extractors(item_fn: &ItemFn) -> TokenStream {
+    let body_extractors = extractor_idents(item_fn)
+        .filter(|(_, _, ident)| {
+            *ident == "String"
+                || *ident == "Bytes"
+                || *ident == "Json"
+                || *ident == "RawBody"
+                || *ident == "BodyStream"
+                || *ident == "Multipart"
+                || *ident == "Request"
+        })
+        .collect::<Vec<_>>();
+
+    if body_extractors.len() > 1 {
+        body_extractors
+            .into_iter()
+            .map(|(_, arg, _)| {
+                syn::Error::new_spanned(arg, "Only one body extractor can be applied")
+                    .to_compile_error()
+            })
+            .collect()
+    } else {
+        quote! {}
+    }
+}
+
 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(
diff --git a/axum-macros/tests/debug_handler/fail/multiple_body_extractors.rs b/axum-macros/tests/debug_handler/fail/multiple_body_extractors.rs
new file mode 100644
index 00000000..875c7540
--- /dev/null
+++ b/axum-macros/tests/debug_handler/fail/multiple_body_extractors.rs
@@ -0,0 +1,7 @@
+use axum_macros::debug_handler;
+use axum::body::Bytes;
+
+#[debug_handler]
+async fn handler(_: String, _: Bytes) {}
+
+fn main() {}
diff --git a/axum-macros/tests/debug_handler/fail/multiple_body_extractors.stderr b/axum-macros/tests/debug_handler/fail/multiple_body_extractors.stderr
new file mode 100644
index 00000000..098f3675
--- /dev/null
+++ b/axum-macros/tests/debug_handler/fail/multiple_body_extractors.stderr
@@ -0,0 +1,11 @@
+error: Only one body extractor can be applied
+ --> tests/debug_handler/fail/multiple_body_extractors.rs:5:18
+  |
+5 | async fn handler(_: String, _: Bytes) {}
+  |                  ^^^^^^^^^
+
+error: Only one body extractor can be applied
+ --> tests/debug_handler/fail/multiple_body_extractors.rs:5:29
+  |
+5 | async fn handler(_: String, _: Bytes) {}
+  |                             ^^^^^^^^
diff --git a/axum-macros/tests/debug_handler/fail/too_many_extractors.rs b/axum-macros/tests/debug_handler/fail/too_many_extractors.rs
index d24fdcbb..d9317bf9 100644
--- a/axum-macros/tests/debug_handler/fail/too_many_extractors.rs
+++ b/axum-macros/tests/debug_handler/fail/too_many_extractors.rs
@@ -1,24 +1,25 @@
 use axum_macros::debug_handler;
+use axum::http::Uri;
 
 #[debug_handler]
 async fn handler(
-    e1: String,
-    e2: String,
-    e3: String,
-    e4: String,
-    e5: String,
-    e6: String,
-    e7: String,
-    e8: String,
-    e9: String,
-    e10: String,
-    e11: String,
-    e12: String,
-    e13: String,
-    e14: String,
-    e15: String,
-    e16: String,
-    e17: String,
+    e1: Uri,
+    e2: Uri,
+    e3: Uri,
+    e4: Uri,
+    e5: Uri,
+    e6: Uri,
+    e7: Uri,
+    e8: Uri,
+    e9: Uri,
+    e10: Uri,
+    e11: Uri,
+    e12: Uri,
+    e13: Uri,
+    e14: Uri,
+    e15: Uri,
+    e16: Uri,
+    e17: Uri,
 ) {}
 
 fn main() {}
diff --git a/axum-macros/tests/debug_handler/fail/too_many_extractors.stderr b/axum-macros/tests/debug_handler/fail/too_many_extractors.stderr
index a1afce89..0ba46212 100644
--- a/axum-macros/tests/debug_handler/fail/too_many_extractors.stderr
+++ b/axum-macros/tests/debug_handler/fail/too_many_extractors.stderr
@@ -1,11 +1,11 @@
 error: Handlers cannot take more than 16 arguments. Use `(a, b): (ExtractorA, ExtractorA)` to further nest extractors
-  --> tests/debug_handler/fail/too_many_extractors.rs:5:5
+  --> tests/debug_handler/fail/too_many_extractors.rs:6:5
    |
-5  | /     e1: String,
-6  | |     e2: String,
-7  | |     e3: String,
-8  | |     e4: String,
+6  | /     e1: Uri,
+7  | |     e2: Uri,
+8  | |     e3: Uri,
+9  | |     e4: Uri,
 ...  |
-20 | |     e16: String,
-21 | |     e17: String,
-   | |________________^
+21 | |     e16: Uri,
+22 | |     e17: Uri,
+   | |_____________^
diff --git a/axum-macros/tests/debug_handler/pass/multiple_extractors.rs b/axum-macros/tests/debug_handler/pass/multiple_extractors.rs
index 5c7ab044..6cc05b51 100644
--- a/axum-macros/tests/debug_handler/pass/multiple_extractors.rs
+++ b/axum-macros/tests/debug_handler/pass/multiple_extractors.rs
@@ -1,6 +1,7 @@
 use axum_macros::debug_handler;
+use axum::http::{Method, Uri};
 
 #[debug_handler]
-async fn handler(_one: String, _two: String, _three: String) {}
+async fn handler(_one: Method, _two: Uri, _three: String) {}
 
 fn main() {}