Skip to content

Commit

Permalink
Improve error handling & Refactoring (#48)
Browse files Browse the repository at this point in the history
* Improve error handling
* use nonroot base image
  • Loading branch information
nkz-soft authored Dec 7, 2024
1 parent a200b17 commit 0dbce1f
Show file tree
Hide file tree
Showing 17 changed files with 118 additions and 62 deletions.
41 changes: 33 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:22.04
FROM gcr.io/distroless/cc-debian12:nonroot

EXPOSE 8080

Expand All @@ -7,4 +7,5 @@ WORKDIR /app
COPY ./target/release/starter .
COPY config.app.toml .

USER nonroot
CMD ["./starter"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ CQRS (Command Query Responsibility Segregation) is a pattern that separates the
- [X] Configuration file
- [X] Environment variables
- [x] OpenAPI documentation
- [ ] Advanced error handling
- [x] Advanced error handling
- [ ] Coming soon :)

## Technologies used
Expand Down
1 change: 1 addition & 0 deletions src/application/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ deadpool-postgres.workspace = true
tokio-postgres.workspace = true
postgres-types.workspace = true
uuid.workspace = true
anyhow.workspace = true

domain = { path = "../domain" }
18 changes: 8 additions & 10 deletions src/application/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ impl GetToDoItemQueryHandler {
}
}

pub async fn execute(&self, query: GetToDoItemQuery) -> Option<ToDoItem> {
self.repository.get_by_id(query.id.unwrap()).await.unwrap()
pub async fn execute(&self, query: GetToDoItemQuery) -> anyhow::Result<Option<ToDoItem>> {
self.repository.get_by_id(query.id.unwrap()).await
}
}

Expand All @@ -33,8 +33,8 @@ impl GetAllToDoItemQueryHandler {
}
}

pub async fn execute(&self) -> Vec<ToDoItem> {
self.repository.get_all().await.unwrap()
pub async fn execute(&self) -> anyhow::Result<Vec<ToDoItem>> {
self.repository.get_all().await
}
}

Expand All @@ -49,11 +49,10 @@ impl CreateToDoItemQueryHandler {
}
}

pub async fn execute(&self, query: CreateToDoItemQuery) -> Uuid {
pub async fn execute(&self, query: CreateToDoItemQuery) -> anyhow::Result<Uuid> {
self.repository
.save(ToDoItem::new(query.title, query.note))
.await
.unwrap()
}
}

Expand All @@ -68,11 +67,10 @@ impl UpdateToDoItemQueryHandler {
}
}

pub async fn execute(&self, query: UpdateToDoItemQuery) -> Uuid {
pub async fn execute(&self, query: UpdateToDoItemQuery) -> anyhow::Result<Uuid> {
self.repository
.save(ToDoItem::new_id(query.id, query.title, query.note))
.await
.unwrap()
}
}

Expand All @@ -87,7 +85,7 @@ impl DeleteToDoItemQueryHandler {
}
}

pub async fn execute(&self, query: DeleteToDoItemQuery) {
self.repository.delete(query.id).await.unwrap()
pub async fn execute(&self, query: DeleteToDoItemQuery) -> anyhow::Result<()> {
self.repository.delete(query.id).await
}
}
8 changes: 4 additions & 4 deletions src/application/src/repositories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use uuid::Uuid;

#[async_trait]
pub trait ToDoItemRepository {
async fn get_all(&self) -> Result<Vec<ToDoItem>, String>;
async fn get_by_id(&self, id: Uuid) -> Result<Option<ToDoItem>, String>;
async fn save(&self, entity: ToDoItem) -> Result<Uuid, String>;
async fn delete(&self, id: Uuid) -> Result<(), String>;
async fn get_all(&self) -> anyhow::Result<Vec<ToDoItem>>;
async fn get_by_id(&self, id: Uuid) -> anyhow::Result<Option<ToDoItem>>;
async fn save(&self, entity: ToDoItem) -> anyhow::Result<Uuid>;
async fn delete(&self, id: Uuid) -> anyhow::Result<()>;
}
2 changes: 2 additions & 0 deletions src/infrastructure/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ async-trait.workspace = true
deadpool-postgres.workspace = true
actix-web.workspace = true
uuid.workspace = true
anyhow.workspace = true
thiserror.workspace = true

domain = { path = "../domain" }
application = { path = "../application" }
12 changes: 4 additions & 8 deletions src/infrastructure/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
use deadpool_postgres::Pool;
use std::ops::DerefMut;
use tokio_postgres::Error;

use crate::migration;

pub async fn configure(pool: &Pool) -> Result<(), Error> {
let mut obj = pool.get().await.unwrap();
pub async fn configure(pool: &Pool) -> anyhow::Result<()> {
let mut obj = pool.get().await?;
let client = obj.deref_mut().deref_mut();

migration::migrations::runner()
.run_async(client)
.await
.unwrap();
migration::migrations::runner().run_async(client).await?;

Ok::<(), Error>(())
Ok(())
}
8 changes: 8 additions & 0 deletions src/infrastructure/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use thiserror::Error;
use uuid::Uuid;

#[derive(Error, Debug)]
pub enum Error {
#[error("item with id {id} not found")]
ItemNotFound { id: Uuid },
}
1 change: 1 addition & 0 deletions src/infrastructure/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub use self::config::configure;

pub mod postgres_repositories;

mod errors;
mod migration;
33 changes: 15 additions & 18 deletions src/infrastructure/src/postgres_repositories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use async_trait::async_trait;
use deadpool_postgres::Pool;
use uuid::Uuid;

use crate::errors::Error::ItemNotFound;
use domain::entities::ToDoItem;

pub struct PostgresToDoItemRepository {
Expand All @@ -19,8 +20,8 @@ impl PostgresToDoItemRepository {

#[async_trait]
impl ToDoItemRepository for PostgresToDoItemRepository {
async fn get_all(&self) -> Result<Vec<ToDoItem>, String> {
let client = self.pool.get().await.unwrap();
async fn get_all(&self) -> anyhow::Result<Vec<ToDoItem>> {
let client = self.pool.get().await?;

let rows = client
.query(
Expand All @@ -29,14 +30,13 @@ impl ToDoItemRepository for PostgresToDoItemRepository {
"#,
&[],
)
.await
.unwrap();
.await?;

Ok(ToDoItemMapper::from_vec(rows))
}

async fn get_by_id(&self, _id: Uuid) -> Result<Option<ToDoItem>, String> {
let client = self.pool.get().await.unwrap();
async fn get_by_id(&self, _id: Uuid) -> anyhow::Result<Option<ToDoItem>> {
let client = self.pool.get().await?;

let rows = client
.query(
Expand All @@ -45,8 +45,7 @@ impl ToDoItemRepository for PostgresToDoItemRepository {
"#,
&[&_id],
)
.await
.unwrap();
.await?;

let row = rows.first();
match row {
Expand All @@ -58,12 +57,12 @@ impl ToDoItemRepository for PostgresToDoItemRepository {
};
Ok(Some(to_do_item))
}
None => Ok(None),
None => Err(ItemNotFound { id: _id }.into()),
}
}

async fn save(&self, _entity: ToDoItem) -> Result<Uuid, String> {
let client = self.pool.get().await.unwrap();
async fn save(&self, _entity: ToDoItem) -> anyhow::Result<Uuid> {
let client = self.pool.get().await?;

let id = _entity.id;
let title = _entity.title;
Expand All @@ -79,21 +78,20 @@ impl ToDoItemRepository for PostgresToDoItemRepository {
"#,
&[&id, &title, &note],
)
.await
.unwrap();
.await?;

let row = rows.first();
match row {
Some(row) => {
let id = row.get(0);
Ok(id)
}
None => Err(String::from("Could not save item.")),
None => Err(ItemNotFound { id: _entity.id }.into()),
}
}

async fn delete(&self, id: Uuid) -> Result<(), String> {
let client = self.pool.get().await.unwrap();
async fn delete(&self, id: Uuid) -> anyhow::Result<()> {
let client = self.pool.get().await?;

client
.execute(
Expand All @@ -102,8 +100,7 @@ impl ToDoItemRepository for PostgresToDoItemRepository {
"#,
&[&id],
)
.await
.unwrap();
.await?;

Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions src/presentation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ readonly.workspace = true
uuid.workspace = true
utoipa.workspace = true
utoipa-actix-web.workspace = true
thiserror.workspace = true
anyhow.workspace = true

application = { path = "../application" }
infrastructure = { path = "../infrastructure" }
domain = { path = "../domain" }

Loading

0 comments on commit 0dbce1f

Please sign in to comment.