diff --git a/Cargo.lock b/Cargo.lock index 31d350e..2bd77d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,6 +793,7 @@ dependencies = [ "log", "sea-orm", "serde_json", + "service", ] [[package]] @@ -3370,8 +3371,10 @@ version = "0.1.0" dependencies = [ "axum", "entity", + "entity_api", "log", "sea-orm", + "serde", "serde_json", "service", "tower-http", diff --git a/entity_api/Cargo.toml b/entity_api/Cargo.toml index 54e47b0..0caf146 100644 --- a/entity_api/Cargo.toml +++ b/entity_api/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] entity = { path = "../entity" } +service = { path = "../service" } serde_json = "1.0.107" log = "0.4.20" diff --git a/entity_api/src/error.rs b/entity_api/src/error.rs new file mode 100644 index 0000000..fd2d36f --- /dev/null +++ b/entity_api/src/error.rs @@ -0,0 +1,66 @@ +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: EntityApiError, +} + +#[derive(Debug)] +pub enum EntityApiError { + DatabaseConnectionLost, + // 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: EntityApiError::RecordNotFound, + }, + DbErr::RecordNotUpdated => Error { + inner: err, + error_type: EntityApiError::RecordNotUpdated, + }, + DbErr::ConnectionAcquire(_) => Error { + inner: err, + error_type: EntityApiError::SystemError, + }, + DbErr::Conn(_) => Error { + inner: err, + error_type: EntityApiError::DatabaseConnectionLost, + }, + DbErr::Exec(_) => Error { + inner: err, + error_type: EntityApiError::SystemError, + }, + _ => Error { + inner: err, + error_type: EntityApiError::SystemError, + }, + } + } +} diff --git a/entity_api/src/lib.rs b/entity_api/src/lib.rs index 94f9a60..08d7600 100644 --- a/entity_api/src/lib.rs +++ b/entity_api/src/lib.rs @@ -1,7 +1,8 @@ -use sea_orm::DatabaseConnection; +use service::AppState; +pub mod error; pub mod organization; -pub async fn seed_database(db: &DatabaseConnection) { - organization::seed_database(db).await; +pub async fn seed_database(app_state: &AppState) { + organization::seed_database(app_state).await; } diff --git a/entity_api/src/organization.rs b/entity_api/src/organization.rs index dde4cfa..7b6458b 100644 --- a/entity_api/src/organization.rs +++ b/entity_api/src/organization.rs @@ -1,14 +1,29 @@ +use super::error::Error; use entity::organization; -use sea_orm::{entity::prelude::*, ActiveValue, DatabaseConnection}; +use organization::{Entity, Model}; +use sea_orm::{entity::prelude::*, ActiveValue}; use serde_json::json; +use service::AppState; -pub(crate) async fn seed_database(db: &DatabaseConnection) { +pub async fn find_all(app_state: &AppState) -> Result, Error> { + let db = app_state.database_connection.as_ref().unwrap(); + Ok(Entity::find().all(db).await?) +} + +pub async fn find_by_id(app_state: &AppState, id: i32) -> Result, Error> { + let db = app_state.database_connection.as_ref().unwrap(); + Ok(Entity::find_by_id(id).one(db).await?) +} + +pub(crate) async fn seed_database(app_state: &AppState) { let organization_names = [ "Jim Hodapp Coaching", "Caleb Coaching", "Enterprise Software", ]; + let db = app_state.database_connection.as_ref().unwrap(); + for name in organization_names { let organization = organization::ActiveModel::from_json(json!({ "name": name, diff --git a/src/main.rs b/src/main.rs index 7b1c475..28c3d44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ async fn main() { let mut app_state = AppState::new(config); app_state = service::init_database(app_state).await.unwrap(); - entity_api::seed_database(app_state.database_connection.as_ref().unwrap()).await; + entity_api::seed_database(&app_state).await; web::init_server(app_state).await.unwrap(); } diff --git a/web/Cargo.toml b/web/Cargo.toml index 802c1a1..9194ab8 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -7,12 +7,14 @@ edition = "2021" [dependencies] entity = { path = "../entity" } +entity_api = { path = "../entity_api" } service = { path = "../service" } 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 4f87cf2..498b632 100644 --- a/web/src/controller/organization_controller.rs +++ b/web/src/controller/organization_controller.rs @@ -4,6 +4,7 @@ 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}; @@ -21,10 +22,9 @@ impl OrganizationController { /// --request GET \ /// http://localhost:4000/organizations pub async fn index(State(app_state): State) -> impl IntoResponse { - let organizations = organization::Entity::find() - .all(&app_state.database_connection.unwrap()) + let organizations = OrganizationApi::find_all(&app_state) .await - .unwrap_or(vec![]); + .unwrap_or_default(); Json(organizations) } @@ -36,10 +36,10 @@ 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> = + OrganizationApi::find_by_id(&app_state, id) + .await + .map_err(|err| err.into()); Json(organization) } diff --git a/web/src/error.rs b/web/src/error.rs index 09a8436..e16e9c4 100644 --- a/web/src/error.rs +++ b/web/src/error.rs @@ -1,27 +1,57 @@ +use std::error::Error as StdError; + use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; +use serde::Serialize; + +use entity_api::error::EntityApiError; +use entity_api::error::Error as EntityApiErrorSuper; pub type Result = core::result::Result; -#[derive(Debug)] +#[derive(Debug, Serialize)] + pub enum Error { + DatabaseConnectionLost, 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 { fn into_response(self) -> Response { match self { + Error::DatabaseConnectionLost => ( + StatusCode::INTERNAL_SERVER_ERROR, + "DB CONNECTION LOST - INTERNAL SERVER ERROR", + ) + .into_response(), Error::InternalServer => { (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: EntityApiErrorSuper) -> Self { + match err.error_type { + EntityApiError::DatabaseConnectionLost => Error::DatabaseConnectionLost, + EntityApiError::RecordNotFound => Error::EntityNotFound, + EntityApiError::RecordNotUpdated => Error::UnprocessableEntity, + EntityApiError::SystemError => Error::InternalServer, + } } }