REST API with Rust + Warp 3: GET
Tags: rust, warp, rest, api
Published: 2021-04-14 12:21:15

Welcome back! Last time we saw each other I wrote:
> Next in line is the `GET` method, which means we'll see parameter handling and (finally) deal with this HashSet thing.

So, _"let us not waste our time in idle discourse!"_

-----

## Warp 3, make it so!

> The code for this part is available [here](https://github.com/rogertorres/dev.to/tree/main/warp/warp3).

First, I needed another dependency to help me deserialize the `GET` return, so I changed the `Cargo.toml` file:

```toml
serde_json = "1.0"
```

Then, the time came to change `try_list()`. As of our last encounter, this test had only a `request()` and the `assert_eq!`. I added two things:
- Before the request, I manually inserted two entries into the HashSet (I could've called `POST`, but since it is already being tested elsewhere, it is ok to take this shortcut);
- After the request, I deserialized the HTML body and compared its content to the data I had previously inserted.

There's a chance that a few things will appear weird, but don't worry, I will go through each one of them.

```rust
use std::collections::HashSet;

#[tokio::test]
async fn try_list() {
    use std::str;
    use serde_json;

    let simulation1 = models::Simulation{
        id: 1, 
        name: String::from("The Big Goodbye"),
    };


    let simulation2 = models::Simulation{
        id: 2, 
        name: String::from("Bride Of Chaotica!"),
    };

    let db = models::new_db();
    db.lock().await.insert(simulation1.clone());
    db.lock().await.insert(simulation2.clone());

    let api = filters::list_sims(db);

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

    let result: Vec<u8> = response.into_body().into_iter().collect();
    let result = str::from_utf8(&result).unwrap();
    let result: HashSet<models::Simulation> = serde_json::from_str(result).unwrap();
    assert_eq!(models::get_simulation(&result, 1).unwrap(), &simulation1);
    assert_eq!(models::get_simulation(&result, 2).unwrap(), &simulation2);

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

    let result: Vec<u8> = response.into_body().into_iter().collect();
    let result = str::from_utf8(&result).unwrap();
    let result: HashSet<models::Simulation> = serde_json::from_str(result).unwrap();
    assert_eq!(result.len(),1);
    assert_eq!(models::get_simulation(&result, 2).unwrap(), &simulation2);
}
```

The first thing I take as deserving an explanation is the `db.lock().await.insert()`. The `lock()` gives you what's inside the Arc, and in this case, it returns a [Future](https://cfsamson.github.io/books-futures-explained/1_futures_in_rust.html). Why? Because we are not using `std::sync::Mutex`, but `tokio::sync::Mutex`, which is an Async implementation of the former. That's why we don't `unwrap()`, but instead `await`, as we need to [suspend execution until the result of the Future is ready](https://doc.rust-lang.org/std/keyword.await.html).

Moving on, `filters::list_sims()` is now getting a parameter, which is the data it will return (which, in a real execution, would come from the HTTP body).

After the request—that remains the same—there are three lines of Bytes-handling-jibber-jabber.

[Bytes](https://docs.rs/bytes/1.0.1/bytes/struct.Bytes.html) is the format with which _warp_'s [RequestBuilder](https://docs.rs/warp/0.3.1/warp/test/struct.RequestBuilder.html) handles the HTML body content. It _looks like_ a [u8] (that is, an array of the primitive u8], but it is a little bit more painful to handle. What I did with it, however, is simple. I:
- Mapped its content to a Vector of u8
- Moved the Vector's content to the slice
- Used `serde_json::from_str()` function to map it to the Simulation struct inside the HashSet.

And this is one of the reasons I wanted a HashSet. As far as I know, standard Rust doesn't allow you to create a HashMap referring to a struct of two fields; that is, you cannot do that:

```rust
\\ This code is not in the project!
struct Example {
    id: u64,
    name: String,
}

type Impossible = HashMap<Example>;
```

And without using a struct as I did with the HashMap (as well as the cool kids did with Vector [here]() at line 205), using `serde` gets... _complicated_ (which means I have no idea how to do it).

Nonetheless, there is another reason why I wanted to stick the struct within the HashSet: it gave me the chance to implement some traits for my type.

Before diving into the traits, I would like to explain the last part of the test (which should be a different test, but the example is already too big). 

The `GET` method can be used in three different ways:
1. Fetch all the entries: `/holodeck`
2. Fetch a single entry: `/holodeck/:id`
3. Fetch filtered entries: `/holodeck/?search=query`

This last `request()` using path `/holodeck/2` was written to cover the second case. I did not (and will not) develop the third one.

-----

## Boldly implementing traits

If you compare the HashSet element with another, it will compare everything. That's no good if you have a key-value-pair struct. As I didn't want to use HashMap because of the aforementioned reasons, the way to go is to change this behavior, making comparisons only care about the id.

First, I brought `Hash` and `Hasher`, then I removed the `Eq`, `PartialEq` and `Hash`, so I could implement them myself. And the implementation was this:

```rust
use std::hash::{Hash, Hasher};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Simulation {
    pub id: u64,
    pub name: String,
}

impl PartialEq for Simulation{
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for Simulation {}

impl Hash for Simulation{
    fn hash<H: Hasher>(&self, state: &mut H){
            self.id.hash(state);
    }
}
```

How did I know how to do it? I just followed the documentation where it says ["How can I implement Eq?"](https://doc.rust-lang.org/std/cmp/trait.Eq.html#how-can-i-implement-eq). Yes, Rust docs are that good.

And what about Hash? Same thing. But it is interesting to note why I did it. HashSet requires the [Hash](https://doc.rust-lang.org/std/hash/trait.Hash.html) trait, and the Hash trait demands this:

```
k1 == k2 -> hash(k1) == hash(k2)
```

That means, if the values you're comparing are equal, their hashes also have to be equal, which would not hold after the implementation of `PartialEq` and `Eq` because both values were being hashed and compared, while the direct comparison only cared about `id`.

> 99% chance that I am wrong, but I think it should not be an implication (→), but a biconditional (↔), because the way it stands if `k1 == k2` is false and `hash(k1) == hash(k2)` is true, the implication's result is still true. But I am not a trained computer scientist and I am not sure this uses first-order logic notation. Let me know in the comments if you do.

One last addition I made below the Hash implementation was this:

```rust
pub fn get_simulation<'a>(sims: &'a HashSet<Simulation>, id: u64) -> Option<&'a Simulation>{
    sims.get(&Simulation{
        id,
        name: String::new(),
    })
}
```

Even though the only relevant field for comparisons is `id` when using methods such as `get()` we have to pass the entire struct, so I created `get_simulation()` to replace it.

Ok, back to the `GET` method.

-----

## Getting away with it

The functions dealing with the `GET` method now have to deal with two additional information, the HashSet from where it will fetch the result and the parameter that might be used.

```rust
pub fn list_sims(db: models::Db) ->  impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    let db_map = warp::any()
        .map(move || db.clone());

    let opt = warp::path::param::<u64>()
        .map(Some)
        .or_else(|_| async { 
            // Ok(None) 
            Ok::<(Option<u64>,), std::convert::Infallible>((None,))
        });

    warp::path!("holodeck" / ..)
        .and(opt)
        .and(warp::path::end())
        .and(db_map)
        .and_then(handlers::handle_list_sims)
}
```

The `opt` represents the optional parameter that can be sent. It gets a param, map it as an `Option` (i.e., `Some`). If it was not provided, the `or_else()` returns a `None`. The reason why there's and `async` there is because `or_else()` [returns a TryFuture](https://docs.rs/warp/0.3.1/warp/trait.Filter.html#method.or_else).

The `path` we are actually returning includes this `opt` the same way we included the `db_bap`. the `/ ..` at the and of `path!` is there to tell the macro to not add the `end()` so I could add the `opt`. That's why there's a manual `end()` there soon after.

> I didn't found this solution in the docs or in the examples. Actually, for some reason, most tutorials omit `GET` parameters. They either just list everything or use query. I found one tutorial that implemented this, but they did so by creating two filters and two handlers. It didn't felt ok, and I knew there should be a solution and that the problem was probably my searching skills; so I asked for help in warp's discord channel, and the nice gentleman **jxs** [pointed me to the solution you saw above](https://github.com/seanmonstar/warp/issues/586).

The next step was to fix the handler:

```rust
pub async fn handle_list_sims(param: u64, db: models::Db) -> Result<impl warp::Reply, Infallible> {
    let mut result = db.lock().await.clone();
    if param > 0 {
        result.retain(|k| k.id == param);
    };
    Ok(warp::reply::json(&result)) 
}
```

It is no longer a _Matthew McConaughey handler_, but still very simple. I am using `retain` instead of a `get_simulation()` because it returns a HashSet (and get would give me a `models::Simulation`), which is exactly what the handler must return.

```
$ cargo test

running 3 tests
test tests::try_create ... ok
test tests::try_create_duplicates ... ok
test tests::try_list ... ok

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

-----

## In the next episode of _**Engaging Warp**_...
We will finish the implementation by implementing the `PUT` and `DELETE` methods. 

🖖