What are Filters in the warp library?

#1

They are described here and look like something that specifies properties that a request needs to have so that some function will trigger. The name Filter makes sense for that, a bit like a coffee filter it only lets certain requests through.

Then they are described here as something that also does transformations. I suppose I can imagine it to be something like a band-pass filter in digital signal processing and get behind that nomenclature as well.

But then this example goes ahead and turns a vector of objects into a filter and now I am really lost. What the hell is going on here

    // Turn our "state", our db, into a Filter so we can combine it
    // easily with others...
    let db = Arc::new(Mutex::new(Vec::<Todo>::new()));
    let db = warp::any().map(move || db.clone());

and here

    // `GET /todos`
    let list = warp::get2()
        .and(todos_index)
        .and(db.clone())
        .map(list_todos);
#2

Let’s break it down bit by bit. Here’s the first snippet of code:

  • warp::any() returns a Filter that is always successful, regardless of the route.
  • .map allows you to transform the data extracted in a filter into something else.
  • warp::any() doesn’t actually extract anything, so the closure takes no parameters.
  • We return our sharable list of Todo objects from the closure.
  • We now have a Filter that matches any route, and always returns our Todo list.

And the second:

  • warp::get2() returns a filter that matches any request that is a GET. It’s called get2 because there’s already a deprecated get filter with different semantics - they’ll be unified in a later version.
  • .and allows you to compose two filters together, so they both have to match for the request to be valid.
  • todos_index is a filter that requires the path to be /todos - so now we’re requiring our request to be GET /todos.
  • Now here’s the interesting bit - remember how we described our db filter? “We now have a Filter that matches any route, and always returns our Todo list”.
  • We compose that with our GET /todos filter just like we did previously - now we have a filter that only matches GET /todos, and always returns our Todo list.
  • We then use list_todos to transform our Todo list into a JSON response.
  • Now, finally, we have our list filter, which only matches GET /todos, and returns the JSON representation of our Todo list.

Hopefully that helps you understand what’s happening here - the db filter is a filter that always matches, and carries a piece of state inside of it, allowing you to thread that state through other filters.

#3

Thanks for the explanation, sounds a bit like a Filter is just any kind of function and then we do function composition. :slight_smile:
I’m not quite clear on two things:

  1. .and(db.clone()) Since db is our magically db-wrapping Filter, why do we have to .clone()?
  2. Is map_todos a typo and should be .map(list_todos) or did I miss something?
#4
  1. We have to clone it because we’re using the db filter in create, update and delete too, and and consumes the filter you pass in. Since the data is wrapped in an Arc, this won’t duplicate the underlying list.
  2. Whoops, yep, typo :sweat_smile: