axum icon indicating copy to clipboard operation
axum copied to clipboard

`#[debug_handler]` doesn't improve error message for `WithRejection`

Open davidpdrsn opened this issue 3 years ago • 3 comments

WithRejection<T, E> requires E to implement IntoResponse. If you forget that you get the familiar "handler not implemented" error and #[debug_handler] doesn't help:

use axum::extract::rejection::JsonRejection;
use axum::response::{IntoResponse, Response};
use axum::routing::{post, MethodRouter};
use axum::Json;
use axum_extra::extract::WithRejection;

#[tokio::main]
async fn main() {
    let _: MethodRouter = post(send);
}

#[axum_macros::debug_handler]
pub async fn send(
    _: WithRejection<Json<MessageSend>, ApiError>,
) -> Json<MessageSendResponse> {
    Json(MessageSendResponse { id: "".to_string() })
}

#[derive(serde::Deserialize)]
pub struct MessageSend {
    pub contents: String,
}

#[derive(serde::Serialize)]
pub struct MessageSendResponse {
    pub id: String,
}

#[derive(Debug, thiserror::Error)]
pub enum ApiError {
    #[error(transparent)]
    JsonExtractorRejection(#[from] JsonRejection),
}

// no `impl IntoResponse for ApiError`

The error message is

error[E0277]: the trait bound `fn(WithRejection<Json<MessageSend>, ApiError>) -> impl Future<Output = Json<MessageSendResponse>> {send}: Handler<_, _, _>` is not satisfied
   --> src/main.rs:9:32
    |
9   |     let _: MethodRouter = post(send);
    |                           ---- ^^^^ the trait `Handler<_, _, _>` is not implemented for `fn(WithRejection<Json<MessageSend>, ApiError>) -> impl Future<Output = Json<MessageSendResponse>> {send}`
    |                           |
    |                           required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S, B>`:
              <IntoHandler<H, T, S, B> as Handler<T, S, B>>
              <Layered<L, H, T, S, B> as Handler<T, S, B>>
              <Or<L, R, Lt, Rt, S, B> as Handler<(M, Lt, Rt), S, B>>
note: required by a bound in `post`
   --> /Users/davidpdrsn/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.6.0-rc.1/src/routing/method_routing.rs:404:1
    |
404 | top_level_handler_fn!(post, POST);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `post`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `WithRejection<Json<MessageSend>, ApiError>: FromRequest<(), Body, _>` is not satisfied
  --> src/main.rs:14:8
   |
14 |     _: WithRejection<Json<MessageSend>, ApiError>,
   |        ^^^^^^^^^^^^^ the trait `FromRequest<(), Body, _>` is not implemented for `WithRejection<Json<MessageSend>, ApiError>`
   |
   = help: the trait `FromRequest<S, B>` is implemented for `WithRejection<E, R>`
note: required by a bound in `__axum_macros_check_send_0_from_request_check`

The generate code is

#[allow(warnings)]
fn __axum_macros_check_send_0_from_request_check<M>()
where
    WithRejection<Json<MessageSend>, ApiError>:
        ::axum::extract::FromRequest<(), axum::body::Body, M> + Send,
{
}

#[allow(warnings)]
fn __axum_macros_check_send_0_from_request_call_check() {
    __axum_macros_check_send_0_from_request_check();
}

For some reason the additional M type parameter changes rust's error message. If you remove that and instead generate

#[allow(warnings)]
fn __axum_macros_check_send_0_from_request_check()
where
    WithRejection<Json<MessageSend>, ApiError>:
        ::axum::extract::FromRequest<(), axum::body::Body> + Send,
{
}

The error message becomes

error[E0277]: the trait bound `ApiError: IntoResponse` is not satisfied
  --> src/main.rs:36:5
   |
36 | /     WithRejection<Json<MessageSend>, ApiError>:
37 | |         ::axum::extract::FromRequest<(), axum::body::Body> + Send,
   | |_________________________________________________________________^ the trait `IntoResponse` is not implemented for `ApiError`
   |

We could look into this and see if its possible to improve this.

davidpdrsn avatar Aug 29 '22 07:08 davidpdrsn

For some reason the additional M type parameter changes rust's error message. If you remove that and instead generate

I think this is because WithRejection implements FromRequest for two different values of M, once directly, and once through FromRequests blanket impl. If rustc knows which one of the two is wanted, it elaborates on why it doesn't apply. If rustc doesn't know, it doesn't elaborate, presumably because it could lead to an explosion of further elaborations, rather than just a chain.

jplatte avatar Aug 31 '22 09:08 jplatte

I thinking we could just build in special support for WithRejection, similarly to what we for Path. If we cannot find a good general solution.

davidpdrsn avatar Sep 19 '22 20:09 davidpdrsn

Right, so I think what we can do is to "split out" the assertions for WithRejection (and other generic extractors that wrap another extractor). I.e. instead of the current

WithRejection<Json<MessageSend>, ApiError>: FromRequest<(), axum::body::Body> + Send,

we can generate

Json<MessageSend>: ::axum::extract::FromRequest<(), axum::body::Body> + Send,
ApiError:
    From<<Json<MessageSend> as FromRequest<(), axum::body::Body>::Rejection> + IntoResponse,

jplatte avatar Sep 22 '22 10:09 jplatte