DEV Community

Javier Viola
Javier Viola

Posted on • Originally published at javierviola.com

1

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 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);


Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 );
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);


Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 );
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

While many AI coding tools operate as simple command-response systems, Qodo Gen 1.0 represents the next generation: autonomous, multi-step problem-solving agents that work alongside you.

Read full post

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay