Axum middleware trait bound issue when invoking a function returning boxed error result

Hi team. I'm new to rust and trying to build a websocket application on top of axum. I modify the chat example with an additional middleware (layer) to determine if the websocket connection should be established or not.

When the check function with return type Result<T, Box<dyn Error>> is invoked and the returned value is taken, the compiler reports that there's trait bound unsatisfied, while if not taking the return value (let _ = check()), or with the return type Result<T, ()> , then everything is fine.

Could you help to explain where this error comes from, and is there any best practice for axum middleware error handling for custom type of Error? Thank you.

Here's the simplified code:

use std::net::SocketAddr;
use std::error::Error;

use axum::{extract::{
    ws::{WebSocket, WebSocketUpgrade},
}, response::IntoResponse, routing::any};
use axum::http::{Request, StatusCode};
use axum::middleware::{from_fn, Next};
use axum::response::Response;
use axum::Router;

#[tokio::test]
async fn main() {
    let app = Router::new()
        .route("/chat",
               any(ws_handler)
                   .layer(from_fn(check_something)));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn check_something<B>(
    request: Request<B>,
    next: Next<B>,
) -> Response {
    // Not invoke do_check_bad : everything ok
    let _ = do_check_bad(); // everything ok
    let good_res = do_check_good(); // everything ok
    let bad_res = do_check_bad(); // trait bound error

    match bad_res {
        Ok(_) => {}
        Err(_) => { return (StatusCode::BAD_REQUEST, "err").into_response() }
    }

    let response = next.run(request).await;
    response
}

fn do_check_bad() -> Result<String, Box<dyn Error>> // BAD
{
    return Err(Box::new(std::fmt::Error/*any kind of err*/ {}));
}

fn do_check_good() -> Result<String, ()> // GOOD
{
    return Err(());
}

async fn ws_handler(
    ws: WebSocketUpgrade,
) -> impl IntoResponse {
    ws.on_upgrade(|socket| websocket(socket))
}

async fn websocket(stream: WebSocket) {}

Here's the error message:

error[E0277]: the trait bound `axum::middleware::FromFn<fn(Request<_>, axum::middleware::Next<_>) -> impl futures::Future<Output = Response<http_body::combinators::box_body::UnsyncBoxBody<bytes::Bytes, axum::Error>>> {check_something::<_>}, (), Route<_>, _>: Service<Request<_>>` is not satisfied
   --> src\chat_copy.rs:25:27
    |
25  |                    .layer(from_fn(check_something)));
    |                     ----- ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Service<Request<_>>` is not implemented for `axum::middleware::FromFn<fn(Request<_>, axum::middleware::Next<_>) -> impl futures::Future<Output = Response<http_body::combinators::box_body::UnsyncBoxBody<bytes::Bytes, axum::Error>>> {check_something::<_>}, (), Route<_>, _>`
    |                     |
    |                     required by a bound introduced by this call
    |
    = help: the following other types implement trait `Service<Request>`:
              axum::middleware::FromFn<F, S, I, (T1,)>
              axum::middleware::FromFn<F, S, I, (T1, T2)>
              axum::middleware::FromFn<F, S, I, (T1, T2, T3)>
              axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4)>
              axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5)>
              axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6)>
              axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7)>
              axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7, T8)>
            and 8 others
note: required by a bound in `MethodRouter::<S, B, E>::layer`
   --> C:\Users\user\.cargo\registry\src\some_registry\axum-0.6.20\src\routing\method_routing.rs:923:21
    |
920 |     pub fn layer<L, NewReqBody, NewError>(self, layer: L) -> MethodRouter<S, NewReqBody, NewError>
    |            ----- required by a bound in this associated function
...
923 |         L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<S, B, E>::layer`

The relevant part of the error message is this one:

L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<S, B, E>::layer`

Notice the Send bound. In your error type, you are not satisfying it:

fn do_check_bad() -> Result<String, Box<dyn Error>> // BAD
{
    return Err(Box::new(std::fmt::Error/*any kind of err*/ {}));
}

You just need to introduce the bound on Send and it should compile:

fn do_check_bad() -> Result<String, Box<dyn Error + Send>> // BAD
{
    return Err(Box::new(std::fmt::Error/*any kind of err*/ {}));
}
1 Like

Thank you! I solved it by impl Box<dyn Error + Send> for my custom Error struct. May I know is it a common approach to manually implement such Box<... + Send> trait? Is there any better practice?

Box is not a trait. Can you show us your code?

Sorry, I mean Into<Box<dyn Error + Send>>

#[derive(Debug, Clone, Copy)]
struct MyError;

impl Error for MyError{}

impl Display for MyError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "My error!")
    }
}

impl Into<Box<dyn Error + Send>> for MyError {
    fn into(self) -> Box<dyn Error + Send> {
        Box::new(self)
    }
}

You have a good intuition, but such implementation is not necessary. std::error::Error already provides several of such implementations of the From trait.

In practice, this means that in your code you can do the following:

fn do_check_bad() -> Result<String, Box<dyn Error + Send>> // BAD
{
    return Err(anything_that_implements_error.into());
}
1 Like

I see. I feel like I'm getting familiar with the From and Into idiom in Rust. Thank you for your help!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.