I'm building an axum server to expose the functions of a Rust library over HTTP. The library defines a common trait for several algorithms:
trait Algorithm {
type T: ToString;
fn action1(input: Self::T) -> Self::T;
fn action2(input: Self::T) -> Self::T;
}
struct Algorithm1;
impl Algorithm for Algorithm1 {
type T = String;
fn action1(input: Self::T) -> Self::T {
input.to_lowercase()
}
fn action2(input: Self::T) -> Self::T {
format!("{input}!")
}
}
struct Algorithm2;
impl Algorithm for Algorithm2 {
type T = String;
fn action1(input: Self::T) -> Self::T {
input.to_uppercase()
}
fn action2(input: Self::T) -> Self::T {
format!("{input}?")
}
}
Taking advantage of the common trait, I can define a generic handler for the actions, but unfortunately I can't find how to define a generic function to create the actions routes:
async fn action1<A: Algorithm>(Path(input): Path<A::T>) -> A::T {
A::action1(input)
}
async fn action2<A: Algorithm>(Path(input): Path<A::T>) -> A::T {
A::action2(input)
}
fn algorithm_routes<A: Algorithm + 'static>() -> Router {
Router::new()
.route("/action1/:input", get(action1::<A>))
.route("/action2/:input", get(action2::<A>))
}
I get this error during the compilation:
error[E0277]: the trait bound `fn(axum::extract::Path<<A as Algorithm>::T>) -> impl Future<Output = <A as Algorithm>::T> {action1::<A>}: Handler<_, _>` is not satisfied
--> src/main.rs:58:39
|
58 | .route("/action1/:input", get(action1::<A>))
| --- ^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(axum::extract::Path<<A as Algorithm>::T>) -> impl Future<Output = <A as Algorithm>::T> {action1::<A>}`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `Handler<T, S>`:
<Layered<L, H, T, S> as Handler<T, S>>
<MethodRouter<S> as Handler<(), S>>
note: required by a bound in `axum::routing::get`
--> /home/glehmann/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.4/src/routing/method_routing.rs:385:1
|
385 | top_level_handler_fn!(get, GET);
| ^^^^^^^^^^^^^^^^^^^^^^---^^^^^^
| | |
| | required by a bound in this function
| required by this bound in `get`
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
Note that the same non generic function with the concrete type for either algorithm builds without problem:
fn algorithm1_routes() -> Router {
Router::new()
.route("/action1/:input", get(action1::<Algorithm1>))
.route("/action2/:input", get(action2::<Algorithm1>))
}
#[tokio::main]
async fn main() {
let app = Router::new()
.nest("/algorithm1", algorithm1_routes())
.nest("/algorithm2", algorithm_routes::<Algorithm2>());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
The #[debug_handler]
macro has be very helpful for non generic handlers, but unfortunately it doesn't support the generic handlers.
Is there a bound that I should specify on the generic types that appear in the generic handler signature? What is the best way with axum to define a set of routes for some generic handlers?
You need to add constraints that ensure that whatever type the Algorithm
is will be able to make a valid Handler
for action1
and action2
. There are a few key constraints on a handler function:
IntoResponse
FromRequestParts
or (up to one) FromRequest
To enforce that for algorithm_routes
, you'll need A::T
to implement IntoResponse
, and from looking at Path
's constraints, it must also implement DeserializeOwned
(from serde) and Send
. This compiles:
use axum::response::IntoResponse; // additional imports
use serde::de::DeserializeOwned;
fn algorithm_routes<A: Algorithm + 'static>() -> Router
where
A::T: IntoResponse + DeserializeOwned + Send,
{
Router::new()
.route("/action1/:input", get(action1::<A>))
.route("/action2/:input", get(action2::<A>))
}
More details on what makes a valid Handler
can be found here: Why does my axum handler not implement Handler?