diff --git a/Cargo.lock b/Cargo.lock index 4784185..8200336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1906,6 +1906,7 @@ dependencies = [ "sea-orm", "secrecy", "serde", + "thiserror", "tokio", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index 225f103..6ebc7c8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -12,6 +12,10 @@ portfolio-entity = { path = "../entity" } # serde serde = { version = "^1.0", features = ["derive"] } +# error + +thiserror = "^1.0" + # env dotenv = "^0.15" diff --git a/core/src/error.rs b/core/src/error.rs index 0627fdc..2aceac3 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -1,19 +1,39 @@ +use thiserror::Error; + +#[derive(Error, Debug)] + pub enum ServiceError { + #[error("Invalid application id")] InvalidApplicationId, + #[error("Invalid credentials")] InvalidCredentials, + #[error("Forbidden")] Forbidden, + #[error("Session expired, please login agai")] ExpiredSession, + #[error("Error while encoding JWT")] JwtError, + #[error("User already exists")] UserAlreadyExists, + #[error("Candidate not found")] CandidateNotFound, + #[error("Parrent not found")] ParentNotFound, - DbError, + #[error("Database error")] + DbError(#[from] sea_orm::DbErr), + #[error("User not found, please contact technical support")] UserNotFoundByJwtId, + #[error("User not found, please contact technical support")] UserNotFoundBySessionId, + #[error("Crypto hash failed, please contact technical support")] CryptoHashFailed, + #[error("Crypto encryption failed, please contact technical support")] CryptoEncryptFailed, + #[error("Crypto decryption failed, please contact technical support")] CryptoDecryptFailed, + #[error("Candidate details not set, please contact technical support")] CandidateDetailsNotSet, + } impl ServiceError { @@ -27,7 +47,7 @@ impl ServiceError { ServiceError::UserAlreadyExists => (409, "User already exists".to_string()), ServiceError::CandidateNotFound => (404, "User not found".to_string()), ServiceError::ParentNotFound => (500, "Parent not found".to_string()), - ServiceError::DbError => (500, "Database error".to_string()), + ServiceError::DbError(_) => (500, "Database error".to_string()), ServiceError::UserNotFoundByJwtId => (500, "User not found, please contact technical support".to_string()), ServiceError::UserNotFoundBySessionId => (500, "User not found, please contact technical support".to_string()), ServiceError::CryptoHashFailed => (500, "Crypto hash failed, please contact technical support".to_string()), @@ -45,15 +65,3 @@ impl ServiceError { self.code_and_message().1 } } - -impl std::fmt::Debug for ServiceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ServiceError {{ code: {}, message: {} }}", self.code(), self.message()) - } -} - -impl std::fmt::Display for ServiceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ServiceError {{ code: {}, message: {} }}", self.code(), self.message()) - } -} \ No newline at end of file diff --git a/core/src/services/admin_service.rs b/core/src/services/admin_service.rs index 12dd925..71bda95 100644 --- a/core/src/services/admin_service.rs +++ b/core/src/services/admin_service.rs @@ -13,11 +13,7 @@ impl AdminService { admin_id: i32, password: String, ) -> Result { - let admin = Query::find_admin_by_id(db, admin_id).await; - - let Ok(admin) = admin else { - return Err(ServiceError::DbError); - }; + let admin = Query::find_admin_by_id(db, admin_id).await?; let Some(admin) = admin else { return Err(ServiceError::CandidateNotFound); @@ -55,7 +51,7 @@ impl AdminService { match SessionService::auth_user_session(db, session_uuid).await { Ok(user) => match user { AdminUser::Admin(admin) => Ok(admin), - AdminUser::Candidate(_) => Err(ServiceError::DbError), + AdminUser::Candidate(_) => unreachable!(), }, Err(e) => Err(e), } diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 5793162..048e404 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -34,18 +34,14 @@ impl ApplicationService { form: ApplicationDetails, ) -> Result<(candidate::Model, parent::Model), ServiceError> { let candidate = Query::find_candidate_by_id(db, application) - .await - .map_err(|_| ServiceError::DbError)? + .await? .ok_or(ServiceError::CandidateNotFound)?; let parent = Query::find_parent_by_id(db, application) - .await - .map_err(|_| ServiceError::DbError)? + .await? .ok_or(ServiceError::ParentNotFound)?; - let Ok(admin_public_keys) = Query::get_all_admin_public_keys(db).await else { - return Err(ServiceError::DbError); - }; + let admin_public_keys = Query::get_all_admin_public_keys(db).await?; let mut admin_public_keys_refrence: Vec<&str> = admin_public_keys.iter().map(|s| &**s).collect(); @@ -70,9 +66,9 @@ impl ApplicationService { ) -> Result { let candidate = match Query::find_candidate_by_id(db, application_id).await { Ok(candidate) => candidate.unwrap(), - Err(_) => return Err(ServiceError::DbError), // TODO: logging + Err(e) => return Err(ServiceError::DbError(e)), // TODO: logging }; - let parent = Query::find_parent_by_id(db, application_id).await.unwrap().unwrap(); + let parent = Query::find_parent_by_id(db, application_id).await?.unwrap(); match crypto::verify_password((&password).to_string(), candidate.code.clone()).await { Ok(valid) => { diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index f0e1e9d..dd5bffb 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,13 +1,14 @@ -use entity::{candidate}; +use entity::candidate; use sea_orm::{prelude::Uuid, DbConn}; use crate::{ + candidate_details::EncryptedApplicationDetails, crypto::{self, hash_password}, error::ServiceError, - Mutation, Query, candidate_details::{EncryptedApplicationDetails}, + Mutation, Query, }; -use super::{session_service::{AdminUser, SessionService}}; +use super::session_service::{AdminUser, SessionService}; const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"]; @@ -53,7 +54,7 @@ impl CandidateService { return Err(ServiceError::CryptoHashFailed); }; - Mutation::create_candidate( + let candidate = Mutation::create_candidate( db, application_id, hashed_password, @@ -61,8 +62,8 @@ impl CandidateService { pubkey, encrypted_priv_key, ) - .await - .map_err(|_| ServiceError::DbError) + .await?; + Ok(candidate) } pub(in crate::services) async fn add_candidate_details( @@ -70,22 +71,21 @@ impl CandidateService { candidate: candidate::Model, enc_details: EncryptedApplicationDetails, ) -> Result { - Mutation::add_candidate_details(db, candidate, enc_details.clone()) - .await - .map_err(|_| ServiceError::DbError) + let model = Mutation::add_candidate_details(db, candidate, enc_details.clone()).await?; + Ok(model) } pub fn is_set_up(candidate: &candidate::Model) -> bool { - candidate.name.is_some() && - candidate.surname.is_some() && - candidate.birthplace.is_some() && - candidate.birthdate.is_some() && - candidate.address.is_some() && - candidate.telephone.is_some() && - candidate.citizenship.is_some() && - candidate.email.is_some() && - candidate.sex.is_some() && - candidate.study.is_some() + candidate.name.is_some() + && candidate.surname.is_some() + && candidate.birthplace.is_some() + && candidate.birthdate.is_some() + && candidate.address.is_some() + && candidate.telephone.is_some() + && candidate.citizenship.is_some() + && candidate.email.is_some() + && candidate.sex.is_some() + && candidate.study.is_some() } pub async fn add_cover_letter(candidate_id: i32, letter: Vec) -> Result<(), ServiceError> { @@ -128,12 +128,12 @@ impl CandidateService { ip_addr: String, ) -> Result<(String, String), ServiceError> { let candidate = Query::find_candidate_by_id(db, candidate_id) - .await - .map_err(|_| ServiceError::DbError)? + .await? .ok_or(ServiceError::CandidateNotFound)?; let session_id = - SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr).await; + SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr) + .await; match session_id { Ok(session_id) => { let private_key = Self::decrypt_private_key(candidate, password).await?; @@ -147,7 +147,7 @@ impl CandidateService { match SessionService::auth_user_session(db, session_uuid).await { Ok(user) => match user { AdminUser::Candidate(candidate) => Ok(candidate), - AdminUser::Admin(_) => Err(ServiceError::DbError), + AdminUser::Admin(_) => unreachable!(), }, Err(e) => Err(e), } @@ -168,17 +168,14 @@ impl CandidateService { mod tests { use sea_orm::{Database, DbConn}; - use crate::{ - crypto, - services::candidate_service::{CandidateService}, Mutation, - }; + use crate::{crypto, services::candidate_service::CandidateService, Mutation}; use super::EncryptedApplicationDetails; use chrono::NaiveDate; - use entity::{parent, candidate}; + use entity::{candidate, parent}; - use crate::services::application_service::ApplicationService; use crate::candidate_details::ApplicationDetails; + use crate::services::application_service::ApplicationService; #[tokio::test] async fn test_application_id_validation() { @@ -225,14 +222,12 @@ mod tests { let secret_message = "trnka".to_string(); - let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) .await .ok() .unwrap(); - Mutation::create_parent(&db, 103151) - .await.unwrap(); + Mutation::create_parent(&db, 103151).await.unwrap(); let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, &vec![&candidate.public_key]) @@ -255,10 +250,15 @@ mod tests { #[cfg(test)] async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) { let plain_text_password = "test".to_string(); - let (candidate, parent) = ApplicationService::create_candidate_with_parent(&db, 103151, &plain_text_password, "".to_string()) - .await - .ok() - .unwrap(); + let (candidate, parent) = ApplicationService::create_candidate_with_parent( + &db, + 103151, + &plain_text_password, + "".to_string(), + ) + .await + .ok() + .unwrap(); let form = ApplicationDetails { name: "test".to_string(), @@ -275,7 +275,6 @@ mod tests { parent_surname: "test".to_string(), parent_telephone: "test".to_string(), parent_email: "test".to_string(), - }; ApplicationService::add_all_details(&db, candidate.application, form) @@ -300,7 +299,9 @@ mod tests { let dec_priv_key = crypto::decrypt_password(enc_candidate.private_key.clone(), password) .await .unwrap(); - let enc_details = EncryptedApplicationDetails::try_from((enc_candidate, enc_parent)).ok().unwrap(); + let enc_details = EncryptedApplicationDetails::try_from((enc_candidate, enc_parent)) + .ok() + .unwrap(); let dec_details = enc_details.decrypt(dec_priv_key).await.ok().unwrap(); assert_eq!(dec_details.name, "test"); // TODO: test every element diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs index 79ce638..1990be4 100644 --- a/core/src/services/parent_service.rs +++ b/core/src/services/parent_service.rs @@ -11,8 +11,7 @@ impl ParentService { application_id: i32, ) -> Result { let parent = Mutation::create_parent(db, application_id) - .await - .map_err(|_| ServiceError::DbError)?; + .await?; Ok(parent) } @@ -23,8 +22,7 @@ impl ParentService { enc_details: EncryptedApplicationDetails, ) -> Result { let parent = Mutation::add_parent_details(db, parent, enc_details) - .await - .map_err(|_| ServiceError::DbError)?; + .await?; Ok(parent) } diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index a661f10..86827da 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -59,7 +59,7 @@ impl SessionService { Some(candidate) => candidate, None => return Err(ServiceError::CandidateNotFound), }, - Err(_) => return Err(ServiceError::DbError), + Err(e) => return Err(ServiceError::DbError(e)), }; // compare passwords @@ -80,7 +80,7 @@ impl SessionService { Some(admin) => admin, None => return Err(ServiceError::CandidateNotFound), }, - Err(_) => return Err(ServiceError::DbError), + Err(e) => return Err(ServiceError::DbError(e)), }; // compare passwords @@ -102,7 +102,7 @@ impl SessionService { Ok(session) => session, Err(e) => { eprintln!("Error creating session: {}", e); - return Err(ServiceError::DbError); + return Err(ServiceError::DbError(e)); } }; @@ -126,7 +126,7 @@ impl SessionService { Some(session) => session, None => return Err(ServiceError::UserNotFoundBySessionId), }, - Err(_) => return Err(ServiceError::DbError), + Err(e) => return Err(ServiceError::DbError(e)), }; let now = chrono::Utc::now().naive_utc();