diff --git a/Cargo.lock b/Cargo.lock index dd1b651..5619fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3262,6 +3262,7 @@ dependencies = [ "entity_api", "log", "sea-orm", + "serde", "serde_json", "service", "tower-http", diff --git a/entity_api/src/error.rs b/entity_api/src/error.rs new file mode 100644 index 0000000..09c3311 --- /dev/null +++ b/entity_api/src/error.rs @@ -0,0 +1,65 @@ +use std::error::Error as StdError; +use std::fmt; + +use sea_orm::error::DbErr; + +/// Errors while executing operations related to entities. +/// The intent is to categorize errors into two major types: +/// * Errors related to data. Ex DbError::RecordNotFound +/// * Errors related to interactions with the database itself. Ex DbError::Conn +#[derive(Debug)] +pub struct Error { + // Underlying error emitted from seaORM internals + pub inner: DbErr, + // Enum representing which category of error + pub error_type: EntityApiErrorType, +} + +#[derive(Debug)] +pub enum EntityApiErrorType { + // Record not found + RecordNotFound, + // Record not updated + RecordNotUpdated, + // Errors related to interactions with the database itself. Ex DbError::Conn + SystemError, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Entity API Error: {:?}", self) + } +} + +impl StdError for Error {} + +impl From for Error { + fn from(err: DbErr) -> Self { + match err { + DbErr::RecordNotFound(_) => Error { + inner: err, + error_type: EntityApiErrorType::RecordNotFound, + }, + DbErr::RecordNotUpdated => Error { + inner: err, + error_type: EntityApiErrorType::RecordNotUpdated, + }, + DbErr::ConnectionAcquire(_) => Error { + inner: err, + error_type: EntityApiErrorType::SystemError, + }, + DbErr::Conn(_) => Error { + inner: err, + error_type: EntityApiErrorType::SystemError, + }, + DbErr::Exec(_) => Error { + inner: err, + error_type: EntityApiErrorType::SystemError, + }, + _ => Error { + inner: err, + error_type: EntityApiErrorType::SystemError, + }, + } + } +} diff --git a/entity_api/src/lib.rs b/entity_api/src/lib.rs index 94f9a60..159daad 100644 --- a/entity_api/src/lib.rs +++ b/entity_api/src/lib.rs @@ -1,5 +1,6 @@ use sea_orm::DatabaseConnection; +pub mod error; pub mod organization; pub async fn seed_database(db: &DatabaseConnection) { diff --git a/entity_api/src/organization.rs b/entity_api/src/organization.rs index 821feec..2053a71 100644 --- a/entity_api/src/organization.rs +++ b/entity_api/src/organization.rs @@ -1,10 +1,18 @@ +use super::error::Error; use entity::organization; use organization::{Entity, Model}; use sea_orm::{entity::prelude::*, ActiveValue, DatabaseConnection}; use serde_json::json; pub async fn find_all(db: &DatabaseConnection) -> Vec { - Entity::find().all(db).await.unwrap_or(vec![]) + Entity::find().all(db).await.unwrap_or(vec![]) +} + +pub async fn find_by_id(db: &DatabaseConnection, id: i32) -> Result, Error> { + match Entity::find_by_id(id).one(db).await { + Ok(result) => Ok(result), + Err(error) => Err(error.into()), + } } pub(crate) async fn seed_database(db: &DatabaseConnection) { diff --git a/web/Cargo.toml b/web/Cargo.toml index cb8ac3f..9194ab8 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -14,6 +14,7 @@ axum = "0.6.20" log = "0.4" tower-http = { version = "0.4.4", features = ["fs"] } serde_json = "1.0.107" +serde = { version = "1.0", features = ["derive"] } [dependencies.sea-orm] version = "0.12.3" # sea-orm version diff --git a/web/src/controller/organization_controller.rs b/web/src/controller/organization_controller.rs index 1347288..f09c6f0 100644 --- a/web/src/controller/organization_controller.rs +++ b/web/src/controller/organization_controller.rs @@ -4,12 +4,12 @@ use axum::response::IntoResponse; use axum::Json; use entity::organization; use entity::organization::Entity as Organization; +use entity_api::organization as OrganizationApi; use sea_orm::entity::EntityTrait; use sea_orm::ActiveModelTrait; use sea_orm::ActiveValue::{NotSet, Set}; use sea_orm::DeleteResult; use serde_json::json; -use entity_api::organization as OrganizationApi; extern crate log; use log::*; @@ -22,7 +22,8 @@ impl OrganizationController { /// --request GET \ /// http://localhost:3000/organizations pub async fn index(State(app_state): State) -> impl IntoResponse { - let organizations = OrganizationApi::find_all(&app_state.database_connection.unwrap()).await; + let organizations = + OrganizationApi::find_all(&app_state.database_connection.unwrap()).await; Json(organizations) } @@ -34,10 +35,11 @@ impl OrganizationController { pub async fn read(State(app_state): State, Path(id): Path) -> impl IntoResponse { debug!("GET Organization by id: {}", id); - let organization: Option = organization::Entity::find_by_id(id) - .one(&app_state.database_connection.unwrap()) - .await - .unwrap_or_default(); + let organization: Result, Error> = + match OrganizationApi::find_by_id(&app_state.database_connection.unwrap(), id).await { + Ok(result) => Ok(result), + Err(error) => Err(error.into()), + }; Json(organization) } diff --git a/web/src/error.rs b/web/src/error.rs index 09a8436..0e76bd9 100644 --- a/web/src/error.rs +++ b/web/src/error.rs @@ -1,12 +1,28 @@ +use std::error::Error as StdError; + use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; +use serde::Serialize; + +use entity_api::error::EntityApiErrorType; +use entity_api::error::Error as EntityApiError; pub type Result = core::result::Result; -#[derive(Debug)] +#[derive(Debug, Serialize)] + pub enum Error { InternalServer, EntityNotFound, + UnprocessableEntity, +} + +impl StdError for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> core::result::Result<(), std::fmt::Error> { + write!(fmt, "{self:?}") + } } impl IntoResponse for Error { @@ -16,12 +32,19 @@ impl IntoResponse for Error { (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response() } Error::EntityNotFound => (StatusCode::NOT_FOUND, "ENTITY NOT FOUND").into_response(), + Error::UnprocessableEntity => { + (StatusCode::UNPROCESSABLE_ENTITY, "UNPROCESSABLE ENTITY").into_response() + } } } } -impl std::fmt::Display for Error { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> core::result::Result<(), std::fmt::Error> { - write!(fmt, "{self:?}") +impl From for Error { + fn from(err: EntityApiError) -> Self { + match err.error_type { + EntityApiErrorType::RecordNotFound => Error::EntityNotFound, + EntityApiErrorType::RecordNotUpdated => Error::UnprocessableEntity, + EntityApiErrorType::SystemError => Error::InternalServer, + } } }