Basic CRUD with rust using tide – refactoring

In the last post I started a basic crud using tide and we end up with a simple api that allow us to store dinosaurs information.

Starting from there, let’s clean the code a little bit to be more organized. First, we had a closure in every route (let’s…


This content originally appeared on DEV Community and was authored by Javier Viola

In the last post I started a basic crud using tide and we end up with a simple api that allow us to store dinosaurs information.

Starting from there, let's clean the code a little bit to be more organized. First, we had a closure in every route (let's call it endpoint from here) and will be more clear if we extract that to functions.

async fn dinos_create(mut req: Request<State>) -> tide::Result {
    let dino: Dino = req.body_json().await?;
    // let get a mut ref of our store ( hashMap )
    let mut dinos = req.state().dinos.write().await;
    dinos.insert(String::from(&dino.name), dino.clone());
    let mut res = Response::new(201);
    res.set_body(Body::from_json(&dino)?);
    Ok(res)
}

async fn dinos_list(req: tide::Request<State>) -> tide::Result {
    let dinos = req.state().dinos.read().await;
    // get all the dinos as a vector
    let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
    let mut res = Response::new(200);
    res.set_body(Body::from_json(&dinos_vec)?);
    Ok(res)
}


( ... )

    app.at("/dinos")
        .post(dinos_create)
        .get(dinos_list);


We moved the closures for this two endpoints to its own functions, let's run the tests to make sure we didn't break anything.

$ cargo test

    Finished test [unoptimized + debuginfo] target(s) in 12.48s
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test index_page ... ok
test list_dinos ... ok
test create_dino ... ok
test update_dino ... ok

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

Great! works as expected. We can move now the rest of the endpoints

    app.at("/dinos/:name")
        .get( dinos_get )
        .put( dinos_update )
        .delete( dinos_delete );

Awesome, but now we have five dinos_ functions in the main file. Let's refactor this to be more organized

First, let's create a new struct to represent a rest entity with a base_path field.

struct RestEntity {
    base_path: String,
}

And implement the same methods we had earlier

impl RestEntity {
    async fn create(mut req: Request<State>) -> tide::Result {
        let dino: Dino = req.body_json().await?;
        // let get a mut ref of our store ( hashMap )
        let mut dinos = req.state().dinos.write().await;
        dinos.insert(String::from(&dino.name), dino.clone());
        let mut res = Response::new(201);
        res.set_body(Body::from_json(&dino)?);
        Ok(res)
    }

    async fn list(req: tide::Request<State>) -> tide::Result {
        let dinos = req.state().dinos.read().await;
        // get all the dinos as a vector
        let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
        let mut res = Response::new(200);
        res.set_body(Body::from_json(&dinos_vec)?);
        Ok(res)
    }

    async fn get(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(entry) => {
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn update(mut req: tide::Request<State>) -> tide::Result {
        let dino_update: Dino = req.body_json().await?;
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(mut entry) => {
                *entry.get_mut() = dino_update;
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn delete(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let deleted = dinos.remove(&key);
        let res = match deleted {
            None => Response::new(404),
            Some(_) => Response::new(204),
        };
        Ok(res)
    }
}

Now we can create a helper function that allow us to register rest like entities to our server, registering five different endpoints to handle the list/create/read/update/delete operations.

fn register_rest_entity(app: &mut Server<State>, entity: RestEntity) {
    app.at(&entity.base_path)
        .get(RestEntity::list)
        .post(RestEntity::create);

    app.at(&format!("{}/:id", entity.base_path))
        .get(RestEntity::get)
        .put(RestEntity::update)
        .delete(RestEntity::delete);
}

And in the server we just need to create a new instance of the struct with the desired base_path and call the helper fn

    let dinos_endpoint = RestEntity {
        base_path: String::from("/dinos"),
    };

    register_rest_entity(&mut app, dinos_endpoint);

Great, let's just run the test to ensure that all the operations are still working...

cargo test
   Compiling tide-basic-crud v0.1.0
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test list_dinos ... ok
test create_dino ... ok
test index_page ... ok
test update_dino ... ok

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

Awesome, we just create a nice abstraction that allow us to create easily more rest like entities and implement the basic operations.

That's all for today, in the next iteration I will try to move away from the HashMap and persist the entities information in a db.

As always, I write this as a learning journal and there could be another more elegant and correct way to do it and any feedback is welcome.

I leave here the repo of this example and the pr of the refactor.

Thanks!

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

blog/content/post on  blog [$!?] on ☁️ (us-east-1)

❯ cat basic-crud-with-rust-using-tide-refactoring.md

layout: blog
title: "Basic CRUD with rust using tide - refactoring"
tags:

  • rust
  • notes
  • tide
  • http_rs
  • tide-basic-crud date: 2020-10-03T19:20:36.901Z --- In the last post I started a basic crud using tide and we end up with a simple api that allow us to store dinosaurs information.

Starting from there, let's clean the code a little bit to be more organized. First, we had a closure in every route (let's call it endpoint from here) and will be more clear if we extract that to functions.

async fn dinos_create(mut req: Request<State>) -> tide::Result {
    let dino: Dino = req.body_json().await?;
    // let get a mut ref of our store ( hashMap )
    let mut dinos = req.state().dinos.write().await;
    dinos.insert(String::from(&dino.name), dino.clone());
    let mut res = Response::new(201);
    res.set_body(Body::from_json(&dino)?);
    Ok(res)
}

async fn dinos_list(req: tide::Request<State>) -> tide::Result {
    let dinos = req.state().dinos.read().await;
    // get all the dinos as a vector
    let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
    let mut res = Response::new(200);
    res.set_body(Body::from_json(&dinos_vec)?);
    Ok(res)
}


( ... )

    app.at("/dinos")
        .post(dinos_create)
        .get(dinos_list);


We moved the closures for this two endpoints to its own functions, let's run the tests to make sure we didn't break anything.

$ cargo test

    Finished test [unoptimized + debuginfo] target(s) in 12.48s
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test index_page ... ok
test list_dinos ... ok
test create_dino ... ok
test update_dino ... ok

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

Great! works as expected. We can move now the rest of the endpoints

    app.at("/dinos/:name")
        .get( dinos_get )
        .put( dinos_update )
        .delete( dinos_delete );

Awesome, but now we have five dinos_ functions in the main file. Let's refactor this to be more organized

First, let's create a new struct to represent a rest entity with a base_path field.

struct RestEntity {
    base_path: String,
}

And implement the same methods we had earlier

impl RestEntity {
    async fn create(mut req: Request<State>) -> tide::Result {
        let dino: Dino = req.body_json().await?;
        // let get a mut ref of our store ( hashMap )
        let mut dinos = req.state().dinos.write().await;
        dinos.insert(String::from(&dino.name), dino.clone());
        let mut res = Response::new(201);
        res.set_body(Body::from_json(&dino)?);
        Ok(res)
    }

    async fn list(req: tide::Request<State>) -> tide::Result {
        let dinos = req.state().dinos.read().await;
        // get all the dinos as a vector
        let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
        let mut res = Response::new(200);
        res.set_body(Body::from_json(&dinos_vec)?);
        Ok(res)
    }

    async fn get(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(entry) => {
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn update(mut req: tide::Request<State>) -> tide::Result {
        let dino_update: Dino = req.body_json().await?;
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(mut entry) => {
                *entry.get_mut() = dino_update;
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn delete(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let deleted = dinos.remove(&key);
        let res = match deleted {
            None => Response::new(404),
            Some(_) => Response::new(204),
        };
        Ok(res)
    }
}

Now we can create a helper function that allow us to register rest like entities to our server, registering five different endpoints to handle the list/create/read/update/delete operations.

fn register_rest_entity(app: &mut Server<State>, entity: RestEntity) {
    app.at(&entity.base_path)
        .get(RestEntity::list)
        .post(RestEntity::create);

    app.at(&format!("{}/:id", entity.base_path))
        .get(RestEntity::get)
        .put(RestEntity::update)
        .delete(RestEntity::delete);
}

And in the server we just need to create a new instance of the struct with the desired base_path and call the helper fn

    let dinos_endpoint = RestEntity {
        base_path: String::from("/dinos"),
    };

    register_rest_entity(&mut app, dinos_endpoint);

Great, let's just run the test to ensure that all the operations are still working...

cargo test
   Compiling tide-basic-crud v0.1.0
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test list_dinos ... ok
test create_dino ... ok
test index_page ... ok
test update_dino ... ok

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

Awesome, we just create a nice abstraction that allow us to create easily more rest like entities and implement the basic operations.

That's all for today, in the next iteration I will try to move away from the HashMap and persist the entities information in a db.

As always, I write this as a learning journal and there could be another more elegant and correct way to do it and any feedback is welcome.

I leave here the repo of this example and the pr of the refactor.

Thanks!


This content originally appeared on DEV Community and was authored by Javier Viola


Print Share Comment Cite Upload Translate Updates
APA

Javier Viola | Sciencx (2021-07-11T19:17:37+00:00) Basic CRUD with rust using tide – refactoring. Retrieved from https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/

MLA
" » Basic CRUD with rust using tide – refactoring." Javier Viola | Sciencx - Sunday July 11, 2021, https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/
HARVARD
Javier Viola | Sciencx Sunday July 11, 2021 » Basic CRUD with rust using tide – refactoring., viewed ,<https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/>
VANCOUVER
Javier Viola | Sciencx - » Basic CRUD with rust using tide – refactoring. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/
CHICAGO
" » Basic CRUD with rust using tide – refactoring." Javier Viola | Sciencx - Accessed . https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/
IEEE
" » Basic CRUD with rust using tide – refactoring." Javier Viola | Sciencx [Online]. Available: https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/. [Accessed: ]
rf:citation
» Basic CRUD with rust using tide – refactoring | Javier Viola | Sciencx | https://www.scien.cx/2021/07/11/basic-crud-with-rust-using-tide-refactoring/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.