Rocket Tutorial 03: Proper routing
Tags: rust, api, beginners, tutorial
Published: 2020-11-17 09:36:47

[<span>Photo by <a href="https://unsplash.com/@dnevozhai?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Denys Nevozhai</a> on <a href="https://unsplash.com/s/photos/route-night?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>, modified (cropped)]

We will continue to explore Rocket's capabilities. This time we are getting closer to the real deal (sort of...)

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/tut3
```

Today will talk about or routes... So allow me a little detour first.

## Improve Tests using a Single Common Client

If there's one thing we need to do about our tests, that we didn't do last time, is to create only an instance of the Rocket client.

This thing has been bugging me, we should have got it right the first time around! So we will immediately correct it.

First we need to add [lazy_static](https://crates.io/crates/lazy_static) as a *dev-dependency*, that is a dependency only used for tests or benches.

```bash
cargo add lazy_static --dev
```

Now we can correct the code inside *tests/common/mod.rs*:

```rust
use crate::lazy_static::lazy_static;

use rocket::local::Client;
use rocket_tut::rocket_builder;

pub fn setup () -> &'static Client {
    lazy_static! {
        static ref CLIENT: Client = Client::new(rocket_builder()).expect("Valid Rocket instance");
    }
    &*CLIENT
}
```

Remember, in order to use the `lazy_static` crate inside a mod we need to add it at the root as well, in *tests/basic_test.rs*:

```rust
use lazy_static;
```

Now with `cargo test` we should see a big improvement time-wise, because there is just one *Rocket Client* that gets created only once, and used in all the tests. Before this, each test would create its own `Client` slowing down the testing process, and consuming CPU resources.

Now, back to the theme of today's installment!

## Using Proper UUIDs

So far we have just created toy routes. It is time to get real(er).

The first thing we will improve is the enforcing of the use of proper UUID to refer to users inside the API.

We will need the [uuid](https://crates.io/crates/uuid) crate, and the corresponding [rocket_contrib uuid features](https://api.rocket.rs/v0.4/rocket_contrib/uuid/index.html)

```bash
cargo add rocket_contrib --features "helmet uuid"
cargo add uuid --features "serde v4"
```

Remember, to add more than one feature to a crate we need to specify a space-separated list inside double quotes for the flag `--features` as shown above.

Now in *src/routes/user.rs* we will add the handling of uuids. First the `use`

```rust
use rocket::*;
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use rocket_contrib::uuid::Uuid;
```

Then in `info_user_rt`, `update_user_rt`, and `delete_user_rt` we will manage the proper uuid type:

```rust
#[get("/users/<id>")]
pub fn info_user_rt(id: Uuid) -> Json<Response> {
    Json(Response::ok(&* format!("Info for user {}", id)))
}

#[put("/users/<id>")]
pub fn update_user_rt(id: Uuid) -> Json<Response> {
    Json(Response::ok(&* format!("Update info for user {}", id)))
}

#[delete("/users/<id>")]
pub fn delete_user_rt(id: Uuid) -> Json<Response> {
    Json(Response::ok(&* format!("Delete user {}", id)))
}
```

In this way, only proper uuid's will be accepted as `<id>`.

For example the following gives a 404 error page:

```bash
curl "http://localhost:8000/api/users/1"
```

While instead this one gets through:

```bash
curl "http://localhost:8000/api/users/$(uuidgen)"
```

NB: we use the command `uuidgen`. It should be present in any distribution by now, otherwise we can replace it with

```bash
curl "http://localhost:8000/api/users/$(cat /proc/sys/kernel/random/uuid)"
```

## Handling users the easy way

To keep things easy for this part we will keep the users in memory, not interfacing with any database.

Still, we need some structs to handle the users. So, we will create the folder *data/* inside *src/*.
In this folder we will add two files *src/data/mod.rs* to handle the module, and *src/data/db.rs* where we will keep our virtual users database.

Inside *mod.rs* we will just add the following:

```rust
pub mod db;
```

While the meatiest part is in *db.rs* Before editing that, we will need to install 3 new crates: [rand](https://crates.io/crates/rand) for random generation, [chrono](https://crates.io/crates/chrono) to handle timestamps, and [rust-argon2](https://crates.io/crates/rust-argon2) as our trusty hashing utility.

```bash
cargo add rand rust-argon2
cargo add chrono --features serde
```

We added to *chrono* the feature to integrate with *serde*.

Let's import now everything we need in *src/data/db.rs*

```rust
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use argon2;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use chrono::{DateTime, Utc};
```

We are using *serde* to serialize and deserialize our structs, we are using *uuid* to handle the id, we are using *argon2* to hash the passwords, and *rand* to obtain a random alphanumerical salt; *chrono* of course is for timestamps.

The first of our structs is `User`:

```rust
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct User {
    #[serde(rename = "_id")]
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub hashed_password: String,
    pub salt: String,
    pub created: DateTime<Utc>,
    pub updated: DateTime<Utc>,
}
```

I got a little ahead renaming `id` to `_id` just for use with [mongodb](), more on that later on (another tutorial on this).

We are not saving passwords here, in accordance with EU laws, and common sense. We save the hash of the password + a unique salt for each user, for added security. Please notice that we are going to receive the passwords in clear though, so a man-in-the-middle attack would be the easiet way to defeat all hash and salts... Therefore, all this security is useless if the communication channel can be compromised. In production we need to use always the `https` protocol.

As you can see we have also a `created` and `updated` fields, useful for stats on users.

In order for our APIs to work, we need also few other structs. The first of which is `InsertableUser`:

```rust
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InsertableUser {
    pub name: String,
    pub email: String,
    pub password: String,
}
```

In this way, we can easily insert only few needed fields in our `User`, and do the rest through code. Let's see the `impl` for User:

```rust
impl User {
    pub fn new(name: String, email: String, password: String) -> Self {
        let salt: String = thread_rng()
            .sample_iter(&Alphanumeric)
            .take(20)
            .collect();
        let hashed_password = hash_password(&password, &salt);

        User {
            id: Uuid::new_v4(),
            name,
            email,
            hashed_password,
            salt,
            created: Utc::now(),
            updated: Utc::now(),
        }
    }
    pub fn from_insertable(insertable: InsertableUser) -> Self {
        User::new(insertable.name, insertable.email, insertable.password)
    }
    pub fn match_password(&self, password: &String) -> bool {
        argon2::verify_encoded(&self.hashed_password, password.as_bytes()).unwrap()
    }
    pub fn update_password(&mut self, password: &String) {
        self.hashed_password = hash_password(password, &self.salt);
        self.updated = Utc::now();
    }
    pub fn update_user(&mut self, name: &String, email: &String) {
        self.name = name.to_string();
        self.email = email.to_string();
        self.updated = Utc::now();
    }
}
```

Lot of code! To break it down:

- the first `fn` is `new()`: it takes as parameters the same fields we have in `InsertableUser`, and calculates all the rest:
	- creates a `salt` out of 20 random alphanumeric characters
	- creates the password's hash out of the real password and the salt with a helper function (more on that later on)
	- fills in all the fields of `User` generating a `uuid` version 4, and two timestamps for `created` and `updated` (if we wanted them to coincide we could have called `Utc::now()` on a variable beforehand, and then fill in both fields with the same var.
- the second function is `from_insertable()`: it takes an `InsertableUser` and creates a `User::new()` out of it
- we have a `match_password()` to check if the provided password matches with the hash saved; 
- we have also a `update_password()` to change the user password, and a `update_user()` to update the rest of the info

The following is the `hash_password()` which is very simple, and there's not much about it:

```rust
fn hash_password(password: &String, salt: &String) -> String {
    let config = argon2::Config::default();
    argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config).unwrap()
}
```

To round off our DB, we have also a `ResponseUser` struct, that is used to send info on the selected `User` back to thrhough the API:

```rust
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResponseUser {
    pub id: String,
    pub name: String,
    pub email: String,
}
impl ResponseUser{
    pub fn from_user(user: &User)-> Self {
        ResponseUser{
            id: user.id.to_string(),
            name: format!("{}", user.name),
            email: format!("{}", user.email),
        }
    }
}
```

As you can see, instead of a `new()` we have a `from_user()` because indeed we need to construct one such object only in answering through the API: we just need to mask out the fields we need to keep secret. Think about it: if for any request we would send back all the info we have saved for our `User` we would be giving out also `salt` and `hashed_password`: not good at all!

We have a last utility struct, that we will use in order to receive the password or an old password plus a new one:

```rust
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserPassword {
    pub password: String,
    pub new_password: Option<String>,
}
```

We are set to go with all our structs.
The only doubt we can have: where do we store our Users?

We said we were keeping everything in memory for the time being, without saving data anywhere, so let's create a `state` in our server to keep all user info.

Let's modify our *src/lib.rs*:

```rust
use std::sync::{Arc, Mutex};
pub mod data;
```

Well, yes, we could use a simpler method... but we need to lock the state in each API call in whch is needed in order to give a minimum [ACID]() guarantee. I'm well aware that in a real world scenario this would be a major bottleneck, but... in a real world scenario we would be using a proper DB; for now this is still a *core concepts guide*, we didn't get yet to the real world experience. 

Now we need to declare a State; we do so by creatng a struct which contains a Vec of our `User`s, wrapped in a `Mutex`, wrapped in a `Arc`.

```rust
pub struct Users {
    pub db: Arc<Mutex<Vec<data::db::User>>>
}
impl Users {
    pub fn new()-> Self {
        Users {
            db: Arc::new(Mutex::new(vec![]))
        } 
    }
}
```

We'll see soon fearless concurrency in action(TM), don't worry!  In the meanwhile, check out the `rocket_builder()`:

```rust
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
    ])
    .mount("/files", StaticFiles::from("static/"))
    .manage(Users::new())
}
```

Yes, in reality we just added a `manage()` method, passing a new `Users` state to it. And that is really all that there is to do on our part: Rocket'll do the rest of the job.

## Our new User routes

Finally let's see how we need to update the *src/routes/user.rs* in order to use our new state and all our new data structures.

```rust
use rocket::*;
use rocket_contrib::json::{Json, JsonValue};
use rocket_contrib::json;
use rocket_contrib::uuid::Uuid;
use rocket::State;
use rocket::response;
use rocket::http::{ContentType, Status};
use rocket::response::{Responder, Response};
use crate::Users;
use crate::data::db::{User, InsertableUser, ResponseUser, UserPassword};
```

Yes, we need many `use` imports. Apart from our custom structures in `data::db`, from `rocket_contrib` we are going to need the `Json` and `Json` values, as well as `rocket_contrib::json`, which will allow us to use the `json!` macro.

From `rocket` itself we will need to manage the `State`, but also various `response` utilities: we are going to create a custom responder!

In fact, we will change `Response` to `ApiResponse` and impl a `responder` for it.

```rust
#[derive(Debug)]
pub struct ApiResponse {
    status: Status,
    message: JsonValue,
}
```

It is very similar to the old `Response`, but we are not returning two String, but a `JsonValue`. We will use the `status` field to return a real http status. In fact, before this we were returning always a `400` status of `success`, but with an error inside.

Check now the `impl`:

```rust
impl ApiResponse {
    pub fn ok(message: JsonValue) -> Self {
        ApiResponse {
            status: Status::Ok,
            message: message,
        }
    }
    pub fn err(message: JsonValue) -> Self {
        ApiResponse {
            status: Status::InternalServerError,
            message: message,
        }
    }
}
```

Ok, for the `err()` we are simply reporting a `InternalServerError`, that is `500`. We could make other functions, notably for `unauthorized`.
However, this is a further improvement, which you are free to pursue on your own.

Now, to make our `ApiResponse` into a `rocket::response::Responder`, we need to `impl` it this way:

```rust
impl<'r> Responder<'r> for ApiResponse {
    fn respond_to(self, req: &Request) -> response::Result<'r> {
        Response::build_from(self.message.respond_to(&req).unwrap())
            .status(self.status)
            .header(ContentType::JSON)
            .ok()
    }
}
```

This way we will just return `ApiResponse` from our route functions, and rocket will do the rest, setting the correct status and content header.

For example, take a look at the new `fn user_list_rt()`:

```rust
#[get("/users")]
pub fn user_list_rt(userdb: State<Users>) -> ApiResponse {
    let v = userdb.db.lock().unwrap();
    let users = &*v;
    ApiResponse::ok(json!([users.len()]))
}
```

We get the `State<Users>` as the route function parameter.

Then, we need to get a `lock()` on its `Mutex`, and get a reference to its content. Once we have a reference to the `Vec<User>`, we can return its lenght transforming `[]` into the corresponding JSON vector, trhough the `json!` macro. We return it building our `ApiResponse` struct through the `ok()` method we implemented earlier. Easy, isn't it?

What about inserting a new user?

```rust
#[post("/users", format = "json", data = "<user>")]
pub fn new_user_rt(userdb: State<Users>, user: Json<InsertableUser>) -> ApiResponse {
    let mut v = userdb.db.lock().unwrap();
    let users = &mut *v;
    users.push(User::from_insertable((*user).clone()));
    ApiResponse::ok(json!(ResponseUser::from_user(&users.last().unwrap())))
}
```

Here we need a mutable reference to the inner `Vec<User>`, with `&mut`, so that we can modify it, adding a `User` with the `Vec::push()`.

In order to add a new user we make use of use the `User::from_insertable`, giving to it the `IsertableUser` we got in the body of the `PUT` request, as specified in the function decorator and parameters:

```rust
#[post("/users", format = "json", data = "<user>")]
pub fn new_user_rt(userdb: State<Users>, user: Json<InsertableUser>) -> ApiResponse {
```

Remember to set `format = "json"` for it to work properly, and specify the type of the `data` (the data in the body of the request) as `Json<InsertableUser>`.

Next, for the GET with `<id>`, we need the following:

```rust
#[get("/users/<id>")]
pub fn info_user_rt(userdb: State<Users>, id: Uuid) -> ApiResponse {
    let mut v = userdb.db.lock().unwrap();
    let users = &mut *v;
    let pos = users.iter().position(|x| x.id.to_string() == id.to_string());
    match pos {
        Some(p) => ApiResponse::ok(json!(ResponseUser::from_user(&v[p]))),
        None => ApiResponse::err(json!(format!("id {} not found",  id)))
    }
}
```

We iterate over the vector, mapping it with the `position()` method, which returns an iterator with the position of the element(s) where the closure returns `true`.

```rust
let pos = users.iter().position(|x| x.id.to_string() == id.to_string());
```

We `match` against the result, so that if an element is not found, we can return an error. If found, we return a `ResponseUser::from_user()` with the `User` we found at the `v[p]` position.

Similarly for all other routes that involve the `<id>` parameter in the request, each with its quirks.

For example, for the `PUT` request (update username or email), in order to update the user we request an `InsertableUser`, because we need approval by matching the `password`:

```rust
#[put("/users/<id>", format = "json", data = "<user>")]
pub fn update_user_rt(userdb: State<Users>, user: Json<InsertableUser>, id: Uuid) -> ApiResponse {
    let mut v = userdb.db.lock().unwrap();
    let users = &mut *v;
    let pos = users.iter().position(|x| x.id.to_string() == id.to_string());
    match pos {
        Some(p) => {
            if v[p].match_password(&user.password) {
                v[p].update_user(&user.name, &user.email);
                ApiResponse::ok(json!(ResponseUser::from_user(&v[p])))
            }
            else { ApiResponse::err(json!("user not authenticated")) }
        },
        None => ApiResponse::err(json!(format!("id {} not found",  id)))
    }
}
```

For the `DELETE`, we instead ask for a `UserPassword` to get a password for authorization. 

```rust
#[delete("/users/<id>", format = "json", data = "<user>")]
pub fn delete_user_rt(userdb: State<Users>, user: Json<UserPassword>, id: Uuid) -> ApiResponse {
    let mut v = userdb.db.lock().unwrap();
    let users = &mut *v;
    let pos = users.iter().position(|x| x.id.to_string() == id.to_string());
    match pos {
        Some(p) => {
            if v[p].match_password(&user.password) {
                let u = v[p].clone();
                v.remove(p);
                ApiResponse::ok(json!(ResponseUser::from_user(&u)))
            }
            else { ApiResponse::err(json!("user not authenticated")) }
        },
        None => ApiResponse::err(json!(format!("id {} not found",  id)))
    }
}
```

Of course in this case we `remove()` the element present at the position where we found the id.

## Improving the API

Speacking of `UserPassword`, we added an optional field for a new password, to update the user password if needed. So let's write a route for that.

We will use a `PATCH` request to the same `users/<id>` trusted route. 

Why `PATCH` for one thing, `PUT` for another? Well we need two ways to update the user: name and email, or the password. We could make use of a single route, with many guard controls, etc. But since we have two http requests (PUT and PATCH) that more or less do the same job of updating data, I prefer to keep two different functions, and have therefore two routes. And I often use `PUT` and `PATCH` interchangeably. We started with `PUT` for name and email, we'll use `PATCH` for the password. Feel free to improve over it as you see fit.

```rust
#[patch("/users/<id>", format = "json", data = "<user>")]
pub fn patch_user_rt(userdb: State<Users>, user: Json<UserPassword>, id: Uuid) -> ApiResponse {
    let mut v = userdb.db.lock().unwrap();
    let users = &mut *v;
    let pos = users.iter().position(|x| x.id.to_string() == id.to_string());
    match pos {
        Some(p) => {
            if v[p].match_password(&user.password) {
                match &user.new_password {
                    Some(passw) => {
                        v[p].update_password(&passw);
                        ApiResponse::ok(json!("Password updated"))
                    },
                    None => ApiResponse::err(json!("Password not provided"))
                }
            }
            else { ApiResponse::err(json!("user not authenticated")) }
        },
        None => ApiResponse::err(json!(format!("id {} not found",  id)))
    }
}
```

In this way, we can update our passwords.

We need also another route, an utility to get the `id` from the user info. in fact, people do not remember a UUID: I can barely remember username and password of the most used services...

We could either use the `name` or the `email` to extrapolate the id. However, the name does not need to be unique, while the email is unique by definition. 

Besides, in order to restore passwords or data, it is more useful to keep an official email to send the restore info to. In fact, in many services the name is optional, while the email is used as identifier. 

So let's proceed with the email; we will use again the GET request:

```rust
#[get("/users/<email>", rank = 2)]
pub fn id_user_rt(userdb: State<Users>, email: String) -> ApiResponse {
    let mut v = userdb.db.lock().unwrap();
    let users = &mut *v;
    let pos = users.iter().position(|x| x.email == email);
    match pos {
        Some(p) => ApiResponse::ok(json!(ResponseUser::from_user(&v[p]))),
        None => ApiResponse::err(json!(format!("user {} not found",  email)))
    }
}
```

You might wonder how can we distinguish from calling the right GET `/users/...`, when we have both a `/users/<id>` and a `/users/<email>`.

Well, rocket has two similar routes, it attempts first to use one, then the other if the first fails. We do not specify anything special with `email`, we reduce it to a `String`. However, while a `uuid` could be confused with a `String`, the contrary is not possible: there are various checks to ensure that a string corresponds to a UUID (it cannot be confused with a email!). With `rank=2` we insure that the generic `String` is the second GET route it is used. In this way, only if the check to the UUID fails, we know that the data is most probably an email, not a UUID.

That is, if `/users/<id>` fails, then `/users/<email>` will be called only after the matching with a `Uuid` fails.

Notice that we return a `ResponseUser` containing the `id`, `name`, and `email`, for convenience.

We need to update Rocket's `mount()` method in *src/lib.rs* in order to use the last two routes:

```rust
.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, // new
    routes::user::id_user_rt // new
])
```

## Quick tests

Right now the *tests/basic_test.rs* tests are no longer valid. To make it work we have some more work to do... but we will tend to this job in the next tutorial.

For now, let's use our *postman*, *ARC*, ... or the trusty old `curl`.

```bash
curl "http://localhost:8000/api/users"
```

That should get you a `[0]` response
 
If we create a file called *request_post.json*, with the following content:

```json
{
    "name": "John Doe",
    "email": "j.doe@m.com",
    "password": "123456"
}
```

Then we can test the `POST` route this way:

```bash
curl -d @request_post.json -H "Content-Type: application/json" "http://localhost:8000/api/users"
```

As an example, this is the response I got:

```json
{"email":"j.doe@m.com","id":"e3404b3d-0298-40a8-95bd-de642ba5d8c2","name":"John Doe"}
```

If we have [jq](https://stedolan.github.io/jq/) installed (`sudo apt-get install jq -y`) we can get it pretty-printed:

```bash
curl -s -d @request_post.json -H "Content-Type: application/json" "http://localhost:8000/api/users" | jq

{
  "email": "j.doe@m.com",
  "id": "4cc7dcf7-37e1-4d58-abd2-ab8b13837c86",
  "name": "John Doe"
}
```

And of course, if we just need to inser and get only the `id` for later usage, we can:

```bash
curl -s -d @request_post.json -H "Content-Type: application/json" "http://localhost:8000/api/users" | jq '.id'
"fa6fb75b-7f90-4347-b627-afa2c5c5d9b5"
```

Great! Let's get the complete info now:

```bash
curl -s "http://localhost:8000/api/users/fa6fb75b-7f90-4347-b627-afa2c5c5d9b5" | jq

{
  "email": "j.doe@m.com",
  "id": "fa6fb75b-7f90-4347-b627-afa2c5c5d9b5",
  "name": "John Doe"
}
```

We could get it also using the email:

```bash
curl -s "http://localhost:8000/api/users/j.doe@m.com" | jq

{
  "email": "j.doe@m.com",
  "id": "fa6fb75b-7f90-4347-b627-afa2c5c5d9b5",
  "name": "John Doe"
}
```

if we create a *request_put.json* with the following content:

```json
{
    "name": "Jane Doe",
    "email": "j.doe@m.com",
    "password": "123456"
}
```

we can udate the info on our user:

```bash
curl -s -d @request_put.json -H "Content-Type: application/json" -X PUT "http://localhost:8000/api/users/fa6fb75b-7f90-4347-b627-afa2c5c5d9b5" | jq

{
  "email": "j.doe@m.com",
  "id": "fa6fb75b-7f90-4347-b627-afa2c5c5d9b5",
  "name": "Jane Doe"
}
```

Similarly, with a *request_changepassword.json*:

```json
{
    "password": "123456",
    "new_password": "qwertyuiop"
}
```

we can change the user's password

```bash
curl -s -d @request_changepassword.json -H "Content-Type: application/json" -X PATCH "http://localhost:8000/api/users/fa6fb75b-7f90-4347-b627-afa2c5c5d9b5" | jq

"Password updated"
```

And with a *request_delete.json* with the new password, 

```json
{
    "password": "qwertyuiop"
}
```

we can delete the user:

```bash
curl -s -d @request_delete.json -H "Content-Type: application/json" -X DELETE "http://localhost:8000/api/users/fa6fb75b-7f90-4347-b627-afa2c5c5d9b5" | jq

{
  "email": "j.doe@m.com",
  "id": "fa6fb75b-7f90-4347-b627-afa2c5c5d9b5",
  "name": "Jane Doe"
}
```

I think we can be quite  satisfied by the results

## Conclusions

For today that's all folks! Hope it has been useful.
Next time we will see to it that the tests are updated, before exploring ways to make our API more useful in a real project.

See you soon!