Axum web framework encapsulation type error, help

use axum::{
    routing::{get, post},
    Router,
};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root))
        .route("/admin", post(admin));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> &'static str {
    "Hello, Root!"
}
async fn admin() -> &'static str {
    "Hello, Admin!"
}

This is an example given by axum official, and now I want to wrap this example for convenience

async fn http_srv_new<H, T, S>(addr: &str, routes: Vec<(&str, &str, H)>) -> Result<(), axum::Error>
where
    H: Handler<T, S>,
    T: 'static,
    S: Clone + Send + Sync + 'static,
{
    let listener = TcpListener::bind(addr).await.unwrap();

    let router = Router::new();
    for (path, method_type, method) in routes {
        let rou = if method_type == "post" {
            post(method)
        } else {
            get(method)
        };
        router.route(path, rou);
    }

    axum::serve(listener, router).await.unwrap();

    Ok(())
}

Example call:
#[tokio::main]
async fn main() {
    http_srv_new(
            "0.0.0.0:3000",
            vec![("/", "get", root), ("/admin", "post", admin_handler)],
        ).await;
}

Now the problem is that the router in the http_srv_new() function is incorrect

the trait bound `for<'a> axum::Router<S>: Service<IncomingStream<'a>>` is not satisfied
the trait `for<'a> Service<IncomingStream<'a>>` is not implemented for `axum::Router<S>`

Help, how can we solve this problem

The snippet doesn't look right to me. router.route(path, rou) call consumes the Router instance and returns the updated Router:

async fn http_srv_new<H, T, S>(addr: &str, routes: Vec<(&str, &str, H)>) -> Result<(), axum::Error>
where
    H: Handler<T, S>,
    T: 'static,
    S: Clone + Send + Sync + 'static,
{
    let listener = TcpListener::bind(addr).await.unwrap();

-   let router = Router::new();
+   let mut router = Router::new();
    for (path, method_type, method) in routes {
        let rou = if method_type == "post" {
            post(method)
        } else {
            get(method)
        };
-       router.route(path, rou);
+       router = router.route(path, rou);
    }

    axum::serve(listener, router).await.unwrap();

    Ok(())
}

That being said, I believe the error stems from the fact that Service<IncomingStream<'_>> is only implemented for Router<()>, not for all Router<S> where S: Clone + Send + 'static. So I believe you should omit S from your function signature:

async fn http_srv_new<H, T>(addr: &str, routes: Vec<(&str, &str, H)>) -> Result<(), axum::Error>
where
    H: Handler<T, ()>,
    T: 'static,

Okay, I played around with it and while my previous post was able to fix your initial error, there is still a problem with your generic arguments. H resolves to one concrete type, namely the one from the first handler you pass in your routes vector, which in your example would be the root handler. You can fix this by passing the MethodRouter directly, instead of constructing it in your function based on the method_type string. This has the additional benefit of making your API cleaner, less noisy due to the removal of the generic arguments and more idiomatic IMO:

/*
[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.7"
tower = "0.4"
*/
use axum::routing::{get, post, MethodRouter};
use axum::Router;
use tokio::net::TcpListener;

async fn http_srv_new(addr: &str, routes: Vec<(&str, MethodRouter)>) -> Result<(), axum::Error> {
    let listener = TcpListener::bind(addr).await.unwrap();

    let mut router = Router::new();
    for (path, method) in routes {
        router = router.route(path, method);
    }

    axum::serve(listener, router).await.unwrap();

    Ok(())
}

async fn root() -> &'static str {
    "Hello, Root!"
}

async fn admin() -> &'static str {
    "Hello, Admin!"
}

#[tokio::main]
async fn main() {
    http_srv_new(
        "0.0.0.0:3000",
        vec![("/", get(root)), ("/admin", post(admin))],
    )
    .await
    .unwrap();
}

Rustexplorer.

This is fine, but I want to wrap it like this because I don't want to install and introduce dependencies where I call:
use axum::routing::{get, post};

async fn http_srv_new<H, T, S>(addr: &str, routes: Vec<(&str, &str, H)>) -> Result<(), axum::Error>
where
    H: Handler<T, S>,
    T: 'static,
    S: Clone + Send + Sync + 'static,
{
    let listener = TcpListener::bind(addr).await.unwrap();
    let mut router = Router::new();
    for (path, method_type, method) in routes {
        let rou = if method_type == "post" {
            post(method)
        } else {
            get(method)
        };
        router = router.route(path, rou);
    }
    axum::serve(listener, router).await.unwrap();
    Ok(())
}

If your users are writing proper endpoints, they will have to import types from axum anyway. Building an unnecessarily complex interface that is not congruent to the types axum provides you with[1], just to avoid a use statement does not sound sensible to me. Handler is not object save, so you can pass your routes as Box<dyn Handler>s. Without trait objects, you will never be able to use a generic H and pass more than a single route to your function.


  1. Even if you were able to get such an interface to work, it would most likely be unnecessarily less flexible as using the axum types directly, which is not very desirable IMO ↩︎