Warp: early exit in some case

While using warp, I want to be able to reject a request with a "Not Implemented" if some header is set. So for example, I have a URL:

/a/{value}/b

If I receive that URL and the accept header is not equal to some value, I just bail (not that I don't want this check for other URLs). I was thinking of doing something like this:

warp::path("some")
  .and(some_custom_filter_to_get_the_value())
  .and(warp::filters::header::value("accept"))
  .or(warp::path::end().and_then(/* return Not Implemented if accept header is not what I expect */))
  .or(warp::path::end().and_then(/* deal with the request */));

But this does not work obviously because the and_then filter do not receive the value or the header as parameter. The provided closure expects no parameter (coming from end).

If I do this:

warp::path("some")
  .and(some_custom_filter_to_get_the_value())
  .and(warp::filters::header::value("accept"))
  .and(warp::path::end())
  .or(warp::Filter::and_then(/* return Not Implemented if accept header is not what I expect */))
  .or(warp::Filter::and_then(/* deal with the request */));

Then I don't what to pass to and_then for the self parameter.

I can't get my head wrapped the way or filters work. Anyone can help?

It's been awhile since I've used warp, but IIRC it's impossible to access extracted values in an or.

After all, and_then receives ownership of the extracted value, and has no way to return it to the parent filter if it ends up rejecting[1]

The simplest answer would of course be to just do all of your logic in a single and_then call, but if you really want to use or you can just copy the filters. The only real downside to that is that extraction may run for each branch, which is potentially wasteful.

use std::convert::Infallible;

use warp::{hyper::StatusCode, Filter};

#[tokio::main]
async fn main() {
    // `header::value()` and `path::end()` both return filters that implement `Copy`, so we can just use this variable multiple times.
    let header_filter = warp::filters::header::value("accept").and(warp::path::end());

    let server = warp::path("some").and(
        header_filter
            .and_then(|value| async move {
                if value != "one" {
                    Ok(StatusCode::NOT_IMPLEMENTED)
                } else {
                    // Probably use a better rejection here
                    Err(warp::reject::not_found())
                }
            })
            .or(header_filter.and_then(|_| async move { Ok::<_, Infallible>(StatusCode::OK) })),
    );

    warp::serve(server).run(([127, 0, 0, 1], 3030)).await;
}

  1. which is what would cause the or branch to be executed ↩︎

Thanks a lot for your explanation, however the solution you proposed does not really address my problem because I didn't formulate it clearly. I don't necessarily need to use or, I just need to reuse multiple filters on different "branches" one of which is bailing early.

In your example, the filters implements Copy so you can just reuse them. The or node is as the root and each branch is full size. If instead of a header and a end I had a complex set of filters, they both would be replicated in the branches below the or node which is what I am trying to avoid.

   node
         \
          or
       /       \
   test         node
  header           \
                   and_then

instead of

   node
         \
          or
       /       \
   node         node
    /              \
test               and_then
header

Anyway, as you said, there is always the solution to manage this in the and_then. I was just hoping there was a way to create a filter for that so I could reuse it in multiple routes instead of having to modify the code of the and_then handlers of all the routes that are affected. One of the benefit would have been that modifying that filter in the future, as I start to handle more accept types, would have been easier.

Ideally:

warp::path("some")
  .and(some_custom_filter_to_get_the_value())
  .and(warp::filters::header::value("accept"))
  .and(warp::path::end())
  .bail_if(some_function(value, header) { /* reject if accept header is what I expect otherwise return a status code like Not Implemented */ })
  // continue here if bail_if rejected
  .and_then((value, header) { /* deal with the request */ });

Then I could have just reused the bail_if handler on multiple route.