An Interest In:
Web News this Week
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
- March 26, 2024
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);
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-3d6db2bae3cd08a5running 5 teststest delete_dino ... oktest index_page ... oktest list_dinos ... oktest create_dino ... oktest update_dino ... oktest 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-3d6db2bae3cd08a5running 5 teststest delete_dino ... oktest list_dinos ... oktest create_dino ... oktest index_page ... oktest update_dino ... oktest 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-cruddate: 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-3d6db2bae3cd08a5running 5 teststest delete_dino ... oktest index_page ... oktest list_dinos ... oktest create_dino ... oktest update_dino ... oktest 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-3d6db2bae3cd08a5running 5 teststest delete_dino ... oktest list_dinos ... oktest create_dino ... oktest index_page ... oktest update_dino ... oktest 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!
Original Link: https://dev.to/pepoviola/basic-crud-with-rust-using-tide-refactoring-2meb
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To