Opaque Type Error with Warp Filter

As an introduction to Rust I'm trying to create the backend for a card game that my family loves to play (since we cannot play in person due to quarantine). I've decided to use Warp given its well-documented support for websockets, and am trying to get the following architecture working:

  • Users can post to a create route to create a game. That route is responsible for creating an rx/tx in an unbounded channel, incrementing the global game_id AtomicUsize, storing the tx in mutable state of type type Games = Arc<RwLock<BTreeMap<usize, UnboundedSender<msg::ToGame>>>>;, and spawning the tokio task for the game loop.
  • Users can get /ws/:game_id to get the game's sender (if) it exists, upgrade the ws connection, and spawn a player loop. If the game id does not exist the route should return a 404.

Where I'm having trouble is in returning a 404 if the game id doesn't exist on the websocket route. My filter code looks like this:

fn ws(games: Games) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    warp::path!("ws" / usize)
        .and(warp::ws())
        .and(with_games(games))
        .and_then(handlers::player_ws)
}

and player_ws looks like this:

pub async fn player_ws(
    game_id: usize,
    ws: warp::ws::Ws,
    games: Games,
) -> Result<impl warp::Reply, Infallible> {
    let games = games.read().await;
    if let Some(tx) = games.get(&game_id) {
        return Ok(ws.on_upgrade(|socket| player_loop(socket, tx.clone())));
    }

    Ok(StatusCode::NOT_FOUND)
}

That handler code produces the following compiler error:

expected opaque type, found struct `http::status::StatusCode`

note: expected opaque type `impl warp::reply::Reply`
              found struct `http::status::StatusCode`

The Warp docs seem to suggest that StatusCode does implement Reply. What's going on here? Is there a better/more idiomatic way to achieve this in Warp? Thanks!

The issue is that you’re returning two different types. Even though that both implement Reply the type returned by on_upgrade is different from the StatusCode which isn’t allowed. One common way to unify the types is to use Either from the futures library. Where you would wrap the on_upgrade call in Either::Left and the StatusCode in Either::Right. And warp implements Reply for Either which satisfies the function signature.

At least I think that should work in this case.

That makes sense, but it doesn't appear like warp implements Reply for Either. Updated handler code:

pub async fn player_ws(
    game_id: usize,
    ws: warp::ws::Ws,
    games: Games,
) -> Result<futures::future::Either<impl warp::Reply, impl warp::Reply>, Infallible> {
    let games = games.read().await;
    if let Some(tx) = games.get(&game_id) {
        let tx = tx.clone();
        return Ok(Left(ws.on_upgrade(move |socket| player_loop(socket, tx))));
    }

    Ok(Right(StatusCode::NOT_FOUND))
}

That results in the following error for the handler:

the trait bound `futures_util::future::either::Either<impl warp::reply::Reply, impl warp::reply::Reply>: warp::reply::Reply` is not satisfied

the trait `warp::reply::Reply` is not implemented for `futures_util::future::either::Either<impl warp::reply::Reply, impl warp::reply::Reply>

So it looks like warp has its own version of Either (which isn’t accessible) and doesn’t use the one from futures which is why that doesn’t work. So instead what I would do is have player_ws return not found as an error. And then in the calling function I would chain an or_else function call to map that not found error to a reply. You probably need to return a custom error in player_ws and then use find in the or_else function you make sure you’re not catching other errors with or_else. I’m on a phone right now but if you still have trouble I can create some sample code on my laptop.

Disclaimer: I don’t know if this is the idiomatic way to use warp. I mostly brute forced my way into learning how to get it to do what I want.

That did the trick, thanks! I did end up using recover instead of or_else, though. Why did you suggest or_else? Are there any advantages to using that?

Sorry, recover is correct since the return type is different.

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.