Rocket Tutorial 04 ALTERNATIVE VERSION: Rocket and Redis
Tags: rust, redis, tutorial, beginners
Published: 2020-12-08 17:08:43

[<span>Photo by <a href="https://unsplash.com/@possessedphotography?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Possessed Photography</a> on <a href="https://unsplash.com/s/photos/memory?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>, modified(cropped)]
Last time we have shown the importance of data persistency with the help of a database.

This time we will explore an alternative "DB" that I think is very fitting for a micro-service architecture.

The code for this tutorial can be found in this repository: [github.com/davidedelpapa/rocket-tut](https://github.com/davidedelpapa/rocket-tut), and has been tagged for your convenience:

```bash
git clone https://github.com/davidedelpapa/rocket-tut.git
cd rocket-tut
git checkout tags/tut4alt
```

## Redis

We will use [Redis](https://redis.io/) as database for this tutorial. Redis is not a database in the strict sense, yet it is not something different from a database.

What?

Properly speaking Redis is a key-value store, that is it stores data, just as a DB, but this data is stored as a `(key = value)` pair. That is, the data is retrieved mostly by key. 

The Redis server is meant to be run in-memory, BUT it syncs (almost instantaneously) with a persistent storage on the hard drive. Since it is in-memory Redis can be run on the same machine (or virtual machine, or container) as the micro-service that uses it, and be blazing fast with a small footprint; yet it will act as a persistent store, so the data is saved.

Redis is a good choice for fast response times, and it can be used as a (persistent) cache or a message broker. It is quite flexible too, but it cannot compare to a proper database in fact of user management and scalability (yes, it can be scaled, but it is nothing compared to proper DBMS).

It can be a good solution if the API server does not need to store much data, or if it just needs a simple cache to exchange data with other services. We will explore it as an alternative solution to MongoDB in order to have more choices to architecture the solution that best fit our problem.

## A new branch 

Notice that in order to be a replacement for MongoDB, we need to branch off from the old commit, before the changes made to adapt to MongoDB. If you have been following on your own repo, here are the instructions to revert from the older commit. 

First let's check the ID of the commit with

```bash
git log --pretty=oneline
```

When you have found it, you have to branch off from the older commit (fill in your own commit ID)

```bash
git checkout -b tut04alt <Commit-ID>
```

With `checkout -b` we create the branch and switch to it at the same time, otherwise `branch` will just create the new branch so that we can switch to it later on.

Now we are at the same point we were after the tutorial 3 (or 3 part II).

## Prerequisites

We need Redis, in case it's not already installed on our system.

```bash
sudo apt install redis-server
```

Now we need to let Redis interface with `systemctl`. We need to modify */etc/redis/redis.conf*, for example using `nano`

```bash
sudo nano /etc/redis/redis.conf
```

If you have never used `nano` before (shame on you; ahahah, just kidding) to search the text we just need `ctrl+w` (on the bottom the instructions say `^W Where Is`). We search for `"supervised"` which is the configuration we need to change. Once found we need to pass from

```bash
supervised no
```

to

```bash
supervised systemd
```

We save with `ctrl+o` and leave the same file name (just press `enter`). Now we can quit nano with `ctrl+x`.

Let's restart the Redis server:

```bash
sudo systemctl restart redis
```

Now when we need it we can start it with `sudo systemctl start redis` and stop it with `sudo systemctl stop redis`

We can check that all went well with the cli (installed by default with the server):

```bash
redis-cli

127.0.0.1:6379> 
```

If we get the prompt with local address and Redis port, everything went smooth. We can even check if the server is alive with the command `ping`:

```bash
127.0.0.1:6379> ping
PONG
```
Perfect! (Command `quit` to exit...).

You can play with Redis and familiarize with its data structures if you want. Take a look at the [interactive tutorial](http://try.redis.io/?undefined&undefined) and the [list of all commands](https://redis.io/commands) to learn some more.

Let's get going with the Rust part now:

```bash
cargo add dotenv r2d2 r2d2_redis anyhow
```

We'll see the use of [anyhow](https://crates.io/crates/anyhow) later on.

Now we can use Redis on our Rust server.

## Let's put Redis to use

As for the MongoDB, we need to change *src/data/db.rs* in order to let `update_password()` and `update_user()` return an instance of the `User` changed.

```rust
pub fn update_password(&mut self, password: &String) -> Self {
    self.hashed_password = hash_password(password, &self.salt);
    self.updated = Utc::now();
    self.to_owned()
}
pub fn update_user(&mut self, name: &String, email: &String) -> Self {
    self.name = name.to_string();
    self.email = email.to_string();
    self.updated = Utc::now();
    self.to_owned()
}
```

In *src/data/* we need a *redis_connection.rs*

```rust
use std::ops::{Deref, DerefMut};
use std::env;
use dotenv::dotenv;
use r2d2;
use r2d2::PooledConnection;
use r2d2_redis::RedisConnectionManager;
use rocket::http::Status;
use rocket::request::{self, FromRequest};
use rocket::{Outcome, Request, State};

type Pool = r2d2::Pool<RedisConnectionManager>;
type PooledConn = PooledConnection<RedisConnectionManager>;

pub struct Conn(pub PooledConn);

impl<'a, 'r> FromRequest<'a, 'r> for Conn {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> {
        let pool = request.guard::<State<Pool>>()?;
        match pool.get() {
            Ok(database) => Outcome::Success(Conn(database)),
            Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
        }
    }
}
impl Deref for Conn {
    type Target = PooledConn;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl DerefMut for Conn {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

pub fn init_pool() -> Pool {
    dotenv().ok();
    let redis_address = env::var("REDIS_ADDRESS").expect("REDIS_ADDRESS missing");
    let redis_port = env::var("REDIS_PORT").expect("REDIS_PORT missing");
    let redis_db = env::var("REDIS_DB").expect("REDIS_DB missing");
    //let redis_password = env::var("REDIS_PASSWORD").expect("REDIS_PASSWORD missing");
    let manager = RedisConnectionManager::new(format!("redis://{}:{}/{}", redis_address, redis_port, redis_db)).expect("connection manager");
    // Otherwise, with password:
    //let manager = RedisConnectionManager::new(format!("redis://user:{}@{}:{}/{}", redis_password redis_address, redis_port, redis_db)).expect("connection manager");
    match r2d2::Pool::builder().max_size(15).build(manager) {
        Ok(pool) => pool,
        Err(e) => panic!("Error: failed to create database pool {}", e),
    }
}
```

As you can see it is mostly similar to *mongo_connection.rs*: we define a `Pool`, a `PooledConn` and a `Conn`; we impl a `FromRequest` for `Conn`, as well as a `Deref`. This time we need also a `DerefMut` because all the `redis` commands are traits implemented on the connection, and they all take a `&mut self` as first argument. If the connection cannot be dereferenced as mutable, it won't work. 

   **NOTE**: see the [full list of Commands](https://docs.rs/redis/0.15.1/redis/trait.Commands.html); most of Redis' are implemented.

In the `init_pool()` we are still taking info from our `.env` file, but this time if we want a secure login we only need the password... Redis is not a proper database management system, and up to recently it did not have a way to register users, but only authenticate passwords... 

Here is an example `.env` file, 
I did not commit it, as usual...

```env
REDIS_ADDRESS=127.0.0.1
REDIS_PORT=6379
REDIS_DB=1
REDIS_PASSWORD=password
```

Speaking about the DB... Redis uses integers for the DBs names, and the cli connects directly to DB `0`. We are going to use a separate DB (`1`) in order to have a series of keys that is unique for the server we are using, and not have a polluted name-space. 

Another alternative that gets used a lot: just use a prefix for each key, in the form `"prefix:actual_key"`. I'm going for the less complicated code-wise, a different DB.

Time to move on to *src/lib.rs*:

```rust
#![feature(proc_macro_hygiene, decl_macro)]
#![allow(unused_attributes)]

#[macro_use] use rocket::*;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::helmet::SpaceHelmet;

pub mod routes;
pub mod data;

pub fn rocket_builder() -> rocket::Rocket {

    rocket::ignite().attach(SpaceHelmet::default())
    .mount("/", routes![routes::ping::ping_fn])
    .mount("/api", routes![
        routes::user::user_list_rt,
        routes::user::new_user_rt,
        routes::user::info_user_rt,
        routes::user::update_user_rt,
        routes::user::delete_user_rt,
        routes::user::patch_user_rt,
        routes::user::id_user_rt
    ])
    .mount("/files", StaticFiles::from("static/"))
    .manage(data::redis_connection::init_pool())
}
```

This too is almost identical to the one for MongoDB. Using `r2d2` clearly has its advantages, and the similarity between the various interfaces is striking.

What is absolutely different between `redis` and `mongodb` is the handling of the commands.

As we have seen, the `mongodb` crate relied heavily on the pattern of returning `Result<Option<T>, E>`, whereas `redis` relies on the sole `Result<T, E>`.  THis means there's less to match for us.

## The user routes

Some architectural considerations are due, before even taking a look at the code of *src/routes/user.rs*:

1. Since the crate's interface requires less matching, and since the Redis commands are more simple and less nuanced than those of MongoDB (for one, we do not have to use BSON docs!), we will resort to a strategy that for MongoDB would have been much more complicated and requiring many more exceptions: we are implementing two methods over User, one to retrieve a user from Redis, and the other one to save the User to Redis. That is, we are *serializing* and *deserializing* our Rust structs using the data types available in Redis.

2. We will use a Redis data type called *hashes* to serialize the User. In practice, for each hash key, the value is an object with many fields: it is like a nested key-value. The number of keys each hash stores can be variable, like MongoDB documents, but we will use it just to serialize a struct conveniently (that is, we will know for now the number of fields at compile time). 

3. We will use an index lookup, that is, we will make a sort of index where checking for the email will return the corresponding user. This was absolutely not needed in MongoDB. Notice however, that this is a technique heavily used in relational databases in order to speed-up the response times of intensive queries. Here we will use it to the same purpose, in order not to search inside every user to check if it corresponds to the email we need to find (that would be a dumb and slow process).

A note here: since we were using BSON to serialize the User to MongoDB, in reality we *did* use this strategy also with MongoDB... But due to the complexity of the communication, we could not take it to the lengths we will see in Redis.

### Serializing and deserializing the User

Before everything the `use` section:

```rust
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use rocket::*;
use rocket_contrib::json::{Json, JsonValue};
use rocket_contrib::json;
use rocket_contrib::uuid::Uuid;
use uuid::Uuid as Uuid2;
use rocket::response;
use rocket::http::{ContentType, Status};
use rocket::response::{Responder, Response};
use r2d2_redis::redis as redis;
use redis::Commands;
use anyhow::Result as AnyResult;
use anyhow::anyhow;
use crate::data::db::{User, InsertableUser, ResponseUser, UserPassword};
use crate::data::redis_connection::Conn;

const LOOKUP: &str = "email_lookup";
```

We define also the lookup key.

Notice that we rename *redis* and *uuid*, the first for convenience, the other not to pollute the name-space; for this same reason we rename the `Result` of *anyhow* as well... (in reality there should be no need to rename it, but I prefer this way). 

We use the standard collection `HashMap` to get a hash from Redis.

Next we implement a default error response for the "Internal server error" as we did for the MongoDB tutorial; nothing new there, so check it in the repo if you need a reminder on how to do it.

What we are interested mostly is the `impl User{}` part.

```rust
impl User {
    fn to_redis(self, connection: &mut Conn) -> AnyResult<()> {
        let id = self.id.to_string();
        let email = self.email.clone();
        let r_user = [
            ("name", self.name),
            ("email", self.email),
            ("hashed_password", self.hashed_password),
            ("salt", self.salt),
            ("created", self.created.to_string()),
            ("updated", self.updated.to_string())
        ];
        connection.hset_multiple(&id, &r_user)?;
        // Add email lookup index
        let _ = connection.zadd(LOOKUP, format!("{}:{}", email, id), 0)?;
        Ok(())
    }
}
```

In order to serialize the User, we first convert the id from `Uuid` to `String`, and we clone the email in order to borrow it two times.

We construct an array out of the info, the array being a list of tuples in the form `(key: String, value: String)`.

Then we pass the array to the `hset_multiple()` setting an hash with key corresponding to the User's id, and the value being the array we constructed.

After this we add the email and id also to the lookup index with `zadd()`. The lookup index is a Redis sorted set; you can read more on its use as lookup index in this article: [Secondary indexing with Redis](https://redis.io/topics/indexes).

The index works as this: the key is the lookup index name, the value is the pair `email:id`, and we have to attach to it a score of `0`; this is because Redis has a function to search the by value for all the values that have the same score, in this way, by searching the email (first part of the value), we get also the id (second part).

All along we use anyhow to manage the errors, so we can use the `?` instead of matching or (never to be done in production) `unwrap` -pping.

Now we need also to deserialize:

```rust
impl User {
    fn from_redis(connection: &mut Conn, id: &String) -> AnyResult<Self> {
        let r_user: HashMap<String, String>  = connection.hgetall(id)?;
        
        let r_user_id = Uuid2::parse_str(&* id)?;
        
        let r_user_name: &String = r_user.get(&"name".to_string()).ok_or(anyhow!(""))?;
        let r_user_email: &String = r_user.get(&"email".to_string()).ok_or(anyhow!(""))?;
        let r_user_hashed_password: &String = r_user.get(&"hashed_password".to_string()).ok_or(anyhow!(""))?;
        let r_user_salt: &String = r_user.get(&"salt".to_string()).ok_or(anyhow!(""))?;
        let r_user_created: &String = r_user.get(&"created".to_string()).ok_or(anyhow!(""))?;
        let r_user_updated: &String = r_user.get(&"updated".to_string()).ok_or(anyhow!(""))?;

        let created: DateTime<Utc> = r_user_created.parse()?;
        let updated: DateTime<Utc> = r_user_updated.parse()?;

        Ok(User {
            id: r_user_id,
            name: r_user_name.to_owned(),
            email: r_user_email.to_owned(),
            hashed_password: r_user_hashed_password.to_owned(),
            salt: r_user_salt.to_owned(),
            created,
            updated,
        })
    }
}
```

`hgetall()` returns a `HashMap<String, String>`. 

We get from it all the info we serialized; for error handling we use the macro `anyhow!()` that lets us create a `String` error as a one-off error message.

We parse the `id` string to a Uuid with the Uuid crate, since the one provided by *rocket_contrib* does not have the `parse_str()` function. It would be nice if rocket_contib would export the current version of `uuid` but for now, let's get the best out of it...

We have to parse also the two timestamps, because in the *HashMap* we have only *String* values.

Once we have everything in order we can return the User well constructed.

## Finally, the routes

The `GET /users` will not use the serialization/deserialization mechanism. It will not even use the regular `redis` Commands trait, because the command we need is not implemented, We need to make a manual call.

```rust
#[get("/users")]
pub fn user_list_rt(mut connection: Conn) -> ApiResponse {
    let connection = &mut *connection;
    let connection_raw: &mut r2d2_redis::redis::Connection = &mut *connection;
    let users_keys: Result<i32, _> = redis::cmd("DBSIZE").query(connection_raw);
    match users_keys {
        Ok(mut user_size) => {
            if user_size >= 2 {user_size -=1 };
            ApiResponse::ok(json!([user_size]))
        },
        Err(_) => ApiResponse::internal_err(),
    }
}
```

In order to perform a manual command, we need to get a `Connection` object from the `Conn` we crated, which involves a two orders of dereferencing. Once we have it, we construct the command with the `cmd()` that is a lower command, and it is used with a builder pattern. So we have to pass to it also the `Connection` we will use with the `query()` method. If the command had some arguments, those would be passed with `arg()`.

Notice that we need to decrease the number we get by 1 if it is 2 or more. This is so, because if even 1 user is set, the lookup key gets set as well, so we have either 0 or 2 keys in the DB.
We have to think of all the edge cases.

To create a new user with `POST /users` we will get first a User from the `InsertableUser` in the body of the request, as usual, then we will just serialize it with our custom serialization method:

```rust
#[post("/users", format = "json", data = "<user>")]
pub fn new_user_rt(mut connection: Conn, user: Json<InsertableUser>) -> ApiResponse {
    let ins_user = User::from_insertable((*user).clone());
    match ins_user.clone().to_redis(&mut connection){
        Ok(_) => ApiResponse::ok(json!(ResponseUser::from_user(&ins_user))),
        Err(_) => ApiResponse::internal_err(),
    }
}
```

That was easy.

Also the `GET /users/<id>` is easy:

```rust
#[get("/users/<id>")]
pub fn info_user_rt(mut connection: Conn, id: Uuid) -> ApiResponse {
    let id = id.to_string();
    match User::from_redis(&mut connection, &id){
        Ok(user) => ApiResponse::ok(json!(ResponseUser::from_user(&user))),
        Err(_) => ApiResponse::err(json!(format!("id {} not found", id))),
    }
}
```

In fact, it is just a matter of deserialization.

Next, the `PUT /users/<id>`:

```rust
#[put("/users/<id>", format = "json", data = "<user>")]
pub fn update_user_rt(mut connection: Conn, user: Json<InsertableUser>, id: Uuid) -> ApiResponse {
    let id = id.to_string();
    match User::from_redis(&mut connection, &id){
        Ok(user_from_redis) =>{
            let mut user_to_redis = user_from_redis.clone();
            if user_to_redis.match_password(&user.password) {
                let _res_lookup: Result<i32, _> = connection.zrem(LOOKUP, format!("{}:{}", user_from_redis.email, id));
                let insert_user = user_to_redis.update_user(&user.name, &user.email);
                match insert_user.clone().to_redis(&mut connection) {
                    Ok(_) => ApiResponse::ok(json!(ResponseUser::from_user(&insert_user))),
                    Err(_) => ApiResponse::internal_err(),
                }
            }
            else { ApiResponse::err(json!("user not authenticated")) }
        },
        Err(_) => ApiResponse::err(json!(format!("id {} not found", id)))
    }
}
```

This requires first to deserialize a user, then to match if the password we got in the request body is correct, then to update the user. Finally we will serialize the updated user. Notice that if the key already exists Redis will just overwrite it with the new information.

Notice though, that before each time we serialize again the user we need to remove its email from the lookup table, otherwise there will be more than one email registered. Worst of all if the user updates the email field: in that case we have the old and the new email both pointing to the same uuid.

The method we employ is prone to fail, and there is no check that it actually failed. However this is a problem that can happen more in bigger databases settings.
We could go about in a more secure way here, but let's face it: if you think that it will be a big problem, because it can actually happen many times, maybe then you have to consider that Redis is also not the correct solution. Time to switch to a full-fledged DBMS.

In any case, you could write a script that once in a while (a *cron* once every week, maybe?) goes over the whole DB and prunes it, signals incorrectness, inconsistency, missing or fragmented data, and so on... It's actually good practice.

As for the `DELETE /users/<id>` route, we extract and match the password as for the `PUT`:

```rust
#[delete("/users/<id>", format = "json", data = "<user>")]
pub fn delete_user_rt(mut connection: Conn, user: Json<UserPassword>, id: Uuid) -> ApiResponse {
    let id = id.to_string();
    match User::from_redis(&mut connection, &id){
        Ok(user_from_redis) =>{
            if user_from_redis.match_password(&user.password) {
                let res: Result<i32, _> = connection.del(&id);
                let _res_lookup: Result<i32, _> = connection.zrem(LOOKUP, format!("{}:{}", user_from_redis.email, id));
                match res {
                    Ok(_) => ApiResponse::ok(json!(ResponseUser::from_user(&user_from_redis))),
                    Err(_) => ApiResponse::internal_err(),
                }
            }
            else { ApiResponse::err(json!("user not authenticated")) }
        },
        Err(_) => ApiResponse::err(json!(format!("id {} not found", id)))
    }
}
```

As you can see, if the password matches, we send a `del(&id)` over to the connection to delete the key altogether (with all its value). The lookup key is taken care of as well.

The `PATCH /users/<id>` route is very similar to the `PUT /users/<id>` route:

```rust
#[patch("/users/<id>", format = "json", data = "<user>")]
pub fn patch_user_rt(mut connection: Conn, user: Json<UserPassword>, id: Uuid) -> ApiResponse {
    match &user.new_password {
        Some(passw) => {
            let id = id.to_string();
            match User::from_redis(&mut connection, &id){
                Ok(mut user_from_redis) =>{
                    if user_from_redis.clone().match_password(&user.password) {
                        let insert_user = user_from_redis.update_password(&passw);
                        let _res_lookup: Result<i32, _> = connection.zrem(LOOKUP, format!("{}:{}", user_from_redis.email, id));
                        match insert_user.clone().to_redis(&mut connection) {
                            Ok(_) => ApiResponse::ok(json!("Password updated")),
                            Err(_) => ApiResponse::internal_err(),
                        }
                    }
                    else { ApiResponse::err(json!("user not authenticated")) }
                },
                Err(_) => ApiResponse::err(json!(format!("id {} not found", id))),
            }
        },
        None => ApiResponse::err(json!("Password not provided"))
    }
}
```

The only difference with the `PUT` route is that in this case we do not `update()` the user but `update_password()`. Here too, since we serialize again the User, we need to cleanup the lookup index. Of course, we start off by matching if the `new_password` is given at all, otherwise we abort the procedure with an error, without even making a call to the Redis service.

The last route, the second ranking `GET /users/<email>` route, which gets us a user based on the email, uses finally the lookup index to retrieve stuff:

```rust
#[get("/users/<email>", rank = 2)]
pub fn id_user_rt(mut connection: Conn, email: String) -> ApiResponse {
    let get_item: Result<Vec<String>, _> = connection.zrangebylex(LOOKUP, format!("[{}", &email), format!("({}\\xff", &email));
    match get_item {
        Ok(lookup_vector) => {
            if lookup_vector.is_empty(){
                return ApiResponse::err(json!(format!("user {} not found",  &email)));
            }
            let split = lookup_vector[0].split(":").collect::<Vec<&str>>();
            let id = split[1].to_string();
            match User::from_redis(&mut connection, &id){
                Ok(user) => ApiResponse::ok(json!(ResponseUser::from_user(&user))),
                Err(_) => ApiResponse::err(json!(format!("user {} not found",  &email))),
            }
        },
        Err(_) => ApiResponse::internal_err()
    }
}
```

At the start we make a call to the lookup key. We use the `zrangebylex()` which gets a range of sub-string o search for. it is similar as a regex, but it as a min argument, that sets the beginning of the research, and a max that says how to stop the range. 

We start with the email followed by the colon. This way it will search for all keys starting with the email and colon, for example `[me@mail.com` will get all keys tat start with those characters, so it can get, for example:

```
me@mail.com
me@mail.com:A
me@mail.com:AA
me@mail.com:B
me@mail.com:BA
(...)
```

The second parameter will set the stop. If we would give it a `(me@mail.com:B` value it would get of the above the following subset:

```
me@mail.com
me@mail.com:A
me@mail.com:AA
```

If we set it wit a `\xff`, as in our case (backslash escaped), it will get all the keys starting with `[me@mail.com`, whatever the characters afterwards. This means, it will search all the `uuid` that are set for that email (remember, each email is unique, so it will get us the pair `email:uuid`) we are searching. It will get it as a String, though, not yet split up; but we will take care of that. 

The actual `zrangebylex()` method will get us a Result wrapping a `Vec<String>`, because there might be more than a result.

The rest is easy: if we do not have an error (connection) we either have an empty vector, which means no user was found corresponding to that email, or we have a user in the vector. We pick up the String and we split it to the colon; we will find the `uuid` in the second sub-string, and we use it to get user, as in the other `GET /users/<email>`. At the end of the whole process, we return that user.

And that closes our routes, and our tour of integration between Rocket and Redis.

## What is Wrong with our server so far...

Now we should build, then run the tests and see that everything works correctly!

Well, actually not, the tests FAIL!!!

How come?

There are two reasons these tests fail:

First of all the tests do not delete the database they fill, so there's always need to do it by hands. We should really delete each User we insert, after testing it.

But more importantly, our system has a requirement that the email is **unique**, but it does not enforce it, rendering everything useless.

Apart from this, since we used in both *basic_test.rs* and *failures_test,rs* some repeated user (my bad copypasta!), we already have users with duplicated info. In MongoDB this is not a problem, since we pick up the first user with the correct info... but with Redis it is not the case, since we are enforcing an email-based index...

Therefore, if we simply run:

```bash
cargo test
```

we see a series of failures. The point is that every time the `to_redis()` and the email is used again, it keeps adding a new record with the same email, and another email with different id in the index. This leads to the failure of `get_rank_fail` and `id_user_rt_fail`, since the system find a user, when it actually should have found none!

We can see it easily in the *redis-cli*:

```redis
127.0.0.1:6379[1]> ZRANGE email_lookup 0 -1
 1) "j.doe@m.com:9f3d9ab7-7d8f-4369-9861-90b2d14aaa66"
 2) "j85@m.com:fb886446-256d-4723-8745-ad6369c0d212"
 3) "jack.doe@m.com:76cc101c-74e3-439a-ae72-520faf31a391"
 4) "jane.doe@m.com:472921bb-4cf7-4cdc-b9dc-4ba3263b34fb"
 5) "jane.doe@m.com:63db7cce-1c0c-4111-96a9-38ce1b3f706a"
 6) "janet.doe@m.com:08fc9080-cf8c-4acd-baf4-6156b1d8b7dc"
 7) "janet.doe@m.com:c375b7dd-006b-44f9-ac93-8eb973643ba0"
 8) "jkd@m.com:b0dbca02-e6a4-4b45-87f8-1d7ac71e623f"
 9) "jondon@m.com:647f8aa3-4f57-4c29-8fb4-3d750b6c1726"
10) "jondon@m.com:683b7520-3fd1-42fe-8239-bf13b2ce4a22"
```

A `ZRANGE` on the email lookup reveals that there are at least three different emails, each used twice!

To run the tests, and make them pass, we should run each suite separately, while **flushing the db** with *redis-cli* in betweens with:

```redis
127.0.0.1:6379[1]> flushdb
OK
```

So we run separately and flush:

```bash
cargo test --test basic_test  # now FLUSHDB on redis-cli
cargo test --test failures_test # now FLUSHDB on redis-cli
cargo test --test persistency_test
```

Every test passes. Yeah! 

But it is not the desired result!

We should change the tests to remove each user inserted after testing it, but also, we should enforce in our code the rejection of already used emails, as it is usually done in these cases.

However, this will be a matter for the next tutorial, together with another useful feature we will implement... so stay tuned!!!