Axum + Generics + Traits: Trait bound not satisfied

Objective: The AppState carries a transmitter (TX) that is supposed to forward any HTTP Post (to /beats) to a new task spawned in async main(). TX, can send any generic P implementing the HandlePulse trait.

Error: Following is compiler diagnostics:

rustc: the trait bound `fn(State<Arc<AppState<_>>>, Json<_>) -> impl Future<Output = Result<Json<_>, std::io::Error>> {root::<_>}: Handler<_, _>` is not satisfied
Consider using `#[axum::debug_handler]` to improve the error message
the following other types implement trait `Handler<T, S>`:
  `Layered<L, H, T, S>` implements `Handler<T, S>`
  `MethodRouter<S>` implements `Handler<(), S>`

debug_handler cannot be used here as it doesn't support generics (yet).

Kindly pour in the insights.

Erroneous code:

use axum::{extract::State, routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use std::{io, sync::Arc};
use tokio::sync::broadcast;

#[derive(Deserialize, Serialize, Clone, Debug)]
struct Pulse {
    payload: String,
}

struct AppState<P> {
    TX: Arc<broadcast::Sender<P>>,
}

trait HandlePulse {
    fn process_event(&self) -> Result<(), io::Error>;
}

impl<P> AppState<P>
where
    P: HandlePulse + Clone,
{
    fn new() -> Self {
        let (TX, _) = broadcast::channel(112);

        let shared_state = Self {
            TX: Arc::new(TX.clone()),
        };

        shared_state
    }
}

impl HandlePulse for Pulse {
    fn process_event(&self) -> Result<(), io::Error> {
        dbg!(self.clone().payload);

        Ok(())
    }
}

#[tokio::main]
async fn main() {
    let app_state: Arc<AppState<Pulse>> = Arc::new(AppState::new());

    // tokio::spawn a task and use trait implementation to handle message.

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    let app = Router::new()
        .route("/beats", post(root))  // ERROR HAPPENS HERE.
        .with_state(app_state);
    axum::serve(listener, app).await.unwrap();
}

async fn root<P>(
    State(state): State<Arc<AppState<P>>>,
    Json(payload): Json<P>,
) -> Result<Json<P>, io::Error>
where
    P: HandlePulse + Clone + Send + Sync + 'static,
{
    if let Err(e) = state.TX.send(payload.clone()) {
        eprintln!("Error while sending: {:?}", e.to_string());
    }

    // transmitting logic.

    Ok(Json(payload))
}

Thank you for your support.

1 Like

Perhaps the requirement for AppState<P>: Serialize here?

(I'm just doc chasing.)

But Pulse (struct) already derives the Serialize trait from serde.
I assume that will do the serialization trick in this occasion.

I think I got some inputs and outputs crossed, sorry.

std::io::Error needs to implement IntoResponse in axum::response - Rust

You may want to use something like axum::http::Response<String> for the error type. It will give you a lot of flexibility for error reporting to the client. But other types are possible, like (axum::http::StatusCode, String).

2 Likes

Thank you @parasyte for the right pointers.
+1 for http::Response suggestion.

I think I need to go through Higher-Rank Trait Bounds.


Going through serde's deserializer lifetimes also helped me fix the issues related to unsatisfied trait bounds.