REST API with Rust + Warp 1: Introduction
Tags: rust, warp, rest, api
Published: 2021-04-14 12:20:40

There are a few good web framework options for Rust: **actix**, **rocket**, **tide**, **warp**... And they all offer some sort of trade-off; but instead of carefully analyzing these intricate aspects (as Luca did [here](https://www.lpalmieri.com/posts/2020-07-04-choosing-a-rust-web-framework-2020-edition/)), you simply decided to go with the one that has an explicit reference to _Start Trek_, right? Not ideal, mafriend, not ideal... Will I blame you? No. You will get nothing but support from me.

And since you're already down this path, I think it might be a good idea for me to show you how I used [_warp_](https://github.com/seanmonstar/warp) myself to create a REST API (for learning purposes, just like a holodeck battle). 

> **Please note** that this is not a tutorial; this is me sharing a _process_ in the hopes that it can help you. Imagine you ask a friend to help you build something. This friend can either point you to the guides she someday used or show you how she actually went about it, not only sharing the code but explaining the _whys_ and _hows_. This text is a simulation of the latter. In other words, if examples like [these](https://github.com/seanmonstar/warp/tree/master/examples) are enough to get you going, considering using them instead of reading this somewhat long text (unless you are easily amused by Star Trek references and puns, then stay).

Still up for the ride? Great! Grab your Earl Gray tea (_hot!_) and let's **make it so!**

------
## Getting started

First, the playground.

```
$ cargo new --lib holodeck
```

Now, there are (_at least_) two ways to go about it. The first is to serve the API and test it via _curl_, as Bastian did [here](https://blog.logrocket.com/creating-a-rest-api-in-rust-with-warp/); and for this, I would use a binary crate. The other way is to use _warp_'s built-in test functionalities, for which I think it is best to get a library. As one of my main goals was to actually try _warp_'s test module, I chose the second path.

I started by editing the `Cargo.toml` file and adding two [dependencies](https://doc.rust-lang.org/rust-by-example/cargo/deps.html).

```toml
[dependencies]
tokio = { version = "1", features = ["full"] }
warp = "0.3"
```

Then I wrote a very simple test case. I chose to test a dull `GET` method. By dull I mean that it will not actually return any data, it will only [1] assume that there will be a mod called `filters` where the filters will be added (filters are the dorsal spine of _warp_ usage, as shown [here](https://docs.rs/warp/0.3.1/warp/trait.Filter.html)); [2]; use the request to make make a `GET` on the path `/holodeck` using the assumed filters; [3] and finally compare the answer with the enum `StatusCode::ACCEPTED`. And because the whole thing is going to be asynchronous, the `#[tokio::test]` is needed.

```rust
#[cfg(test)]
mod tests {
    use warp::http::StatusCode;
    use warp::test::request;
    use super::filters;

    #[tokio::test]
    async fn try_list() {
        let api = filters::list();

        let response = request()
            .method("GET")
            .path("/holodeck")
            .reply(&api)
            .await;

        assert_eq!(response.status(), StatusCode::ACCEPTED);
    }
}
```

> You will notice that I chose not to write `use super::filters::*` with the __*__, which would allow me to write things like `list()` instead of `filters::list()`; I did so because it makes it easier for me (and for you) to know _what_ is coming from _where_.

Then, I created the missing filter, which is designed to expect nothing but an empty `GET` and handle it over to what I call a _"Matthew McConaughey handler"_, because all it has to do is to say _[alright, alright, alright](https://youtu.be/Dvi8P-lhJmE?t=6)_ (a.k.a. `StatusCode::ACCEPTED`).

```rust
mod filters{
    use warp::Filter;
    use super::handlers;

    pub fn list() ->  impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone{ 
        warp::path!("holodeck")
            .and(warp::get())
            .and_then(handlers::handle_list)
    }
}

mod handlers{
    use warp::http::StatusCode;
    use std::convert::Infallible;

    pub async fn handle_list() -> Result<impl warp::Reply, Infallible> {
        // "Alright, alright, alright", Matthew said.
        Ok(StatusCode::ACCEPTED)
    }
}
```

Why `Infallible` in the handler's Result? Because if something goes wrong (that's not coded yet), the problem will also be sent as Ok (e.g. `Ok(StatusCode::BAD_GATEWAY)`).

It might seem like a lot of wasted time, but let me tell you the benefits of doing this (and I'm not even going to get fancy with [TDD](http://carol-nichols.com/2015/03/28/tdd-example-in-rust/)). For starters, sometimes (maybe _every time_) it is easier to know _what_ we wanna do than _how_ we're _supposed_ to do it, and tests are a good way to force you to answer this _"what"_ before jumping into the myriad of possible answers to the _"how"_ question. Besides that, these baby steps allowed me to have a clear picture of which function was responsible for which task, who got to be async and who didn't, and so on.

```
$ cargo test

running 1 test
test tests::try_list ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

It worked; not much happened but worked nonetheless. 

-----

## In the next episode of **_Engaging Warp_**...

The next step is to build the `POST` method. That will require proper coding of both filters and handlers, as well as deserializing the JSON and storing it somewhere.

Anyway, that's all for now. If I said something wrong or made things more complicated than they should be, let me know in the comments.

🖖