error handling improvements

This commit is contained in:
Rohan Sircar 2021-05-02 21:58:18 +05:30
parent da01fee1f8
commit eb9e97c3aa
9 changed files with 125 additions and 84 deletions

View File

@ -32,12 +32,11 @@ pub fn _find_user_by_name(
pub fn get_all( pub fn get_all(
conn: &SqliteConnection, conn: &SqliteConnection,
) -> Result<Option<Vec<models::UserDto>>, errors::DomainError> { ) -> Result<Vec<models::UserDto>, errors::DomainError> {
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;
Ok(users Ok(users
.select((name, created_at)) .select((name, created_at))
.load::<models::UserDto>(conn) .load::<models::UserDto>(conn)?)
.optional()?)
} }
/// Run query using Diesel to insert a new database row and return the result. /// Run query using Diesel to insert a new database row and return the result.

View File

@ -27,43 +27,67 @@ custom_error! { #[derive(new)] pub DomainError
DbError {source: diesel::result::Error} = "Database error", DbError {source: diesel::result::Error} = "Database error",
DbPoolError {source: r2d2::Error} = "Failed to get connection from pool", DbPoolError {source: r2d2::Error} = "Failed to get connection from pool",
PasswordError {cause: String} = "Failed to validate password - {cause}", PasswordError {cause: String} = "Failed to validate password - {cause}",
GenericError {cause: String} = "Generic Error - Reason: {cause}" EntityDoesNotExistError {message: String} = "Entity does not exist - {message}",
ThreadPoolError {message: String} = "Thread pool error - {message}",
AuthError {message: String} = "Authentication Error - {message}"
} }
impl ResponseError for DomainError { impl ResponseError for DomainError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
let err = self; let err = self;
match self { match self {
DomainError::PwdHashError { source } => { DomainError::PwdHashError { source: _ } => {
HttpResponse::InternalServerError().json(ErrorModel { HttpResponse::InternalServerError().json(ErrorModel {
error_code: 500, // error_code: 500,
reason: format!("{} {}", err.to_string(), source).as_str(), success: false,
reason: err.to_string().as_str(),
}) })
} }
DomainError::DbError { source } => { DomainError::DbError { source: _ } => {
error!("{}", err);
HttpResponse::InternalServerError().json(ErrorModel { HttpResponse::InternalServerError().json(ErrorModel {
error_code: 500, // error_code: 500,
reason: format!("{} {}", err.to_string(), source).as_str(), success: false,
reason: "Error in database",
}) })
} }
DomainError::DbPoolError { source } => { DomainError::DbPoolError { source: _ } => {
error!("{}", err);
HttpResponse::InternalServerError().json(ErrorModel { HttpResponse::InternalServerError().json(ErrorModel {
error_code: 500, // error_code: 500,
reason: format!("{} {}", err.to_string(), source).as_str(), success: false,
reason: "Error getting database pool",
}) })
} }
DomainError::PasswordError { cause: _ } => { DomainError::PasswordError { cause: _ } => {
HttpResponse::BadRequest().json(ErrorModel { HttpResponse::BadRequest().json(ErrorModel {
error_code: 400, // error_code: 400,
success: false,
reason: err.to_string().as_str(),
})
}
DomainError::EntityDoesNotExistError { message: _ } => {
HttpResponse::Accepted().json(ErrorModel {
// error_code: 400,
success: false,
reason: err.to_string().as_str(),
})
}
DomainError::ThreadPoolError { message: _ } => {
error!("{}", err);
HttpResponse::InternalServerError().json(ErrorModel {
// error_code: 400,
success: false,
reason: "Thread pool error occurred",
})
}
DomainError::AuthError { message: _ } => {
HttpResponse::Accepted().json(ErrorModel {
// error_code: 400,
success: false,
reason: err.to_string().as_str(), reason: err.to_string().as_str(),
}) })
} }
DomainError::GenericError { cause } => HttpResponse::BadRequest()
.json(ErrorModel {
error_code: 400,
reason: format!("{} {}, ", err.to_string(), cause.clone())
.as_str(),
}),
} }
} }
} }

View File

@ -15,7 +15,6 @@ use rand::Rng;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager}; use diesel::r2d2::{self, ConnectionManager};
use listenfd::ListenFd;
use std::io; use std::io;
use std::io::ErrorKind; use std::io::ErrorKind;
use types::DbPool; use types::DbPool;
@ -119,8 +118,7 @@ async fn main() -> std::io::Result<()> {
)) ))
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service( .service(
web::scope("/api/authzd") // endpoint requiring authentication web::scope("/api")
// .wrap(_basic_auth_middleware.clone())
.service(routes::users::get_user) .service(routes::users::get_user)
.service(routes::users::get_all_users), .service(routes::users::get_all_users),
) )
@ -132,12 +130,5 @@ async fn main() -> std::io::Result<()> {
.service(routes::users::add_user) .service(routes::users::add_user)
.service(fs::Files::new("/", "./static")) .service(fs::Files::new("/", "./static"))
}; };
// HttpServer::new(app).bind(addr)?.run().await HttpServer::new(app).bind(addr)?.run().await
let mut listenfd = ListenFd::from_env();
let server = HttpServer::new(app);
let server = match listenfd.take_tcp_listener(0)? {
Some(l) => server.listen(l),
None => server.bind(addr),
}?;
server.run().await
} }

View File

@ -8,6 +8,7 @@ pub struct JsonErrorModel<'a> {
} }
#[derive(Debug, Clone, Serialize, new)] #[derive(Debug, Clone, Serialize, new)]
pub struct ErrorModel<'a> { pub struct ErrorModel<'a> {
pub error_code: i16, // pub error_code: i16,
pub success: bool,
pub reason: &'a str, pub reason: &'a str,
} }

View File

@ -2,7 +2,7 @@ use actix_web::web;
use actix_web_httpauth::extractors::basic::BasicAuth; use actix_web_httpauth::extractors::basic::BasicAuth;
use crate::actions::users; use crate::actions::users;
use crate::{errors, AppConfig}; use crate::{errors::DomainError, AppConfig};
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{get, Error, HttpResponse}; use actix_web::{get, Error, HttpResponse};
@ -11,7 +11,7 @@ pub async fn login(
id: Identity, id: Identity,
credentials: BasicAuth, credentials: BasicAuth,
config: web::Data<AppConfig>, config: web::Data<AppConfig>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, DomainError> {
let maybe_identity = id.identity(); let maybe_identity = id.identity();
let response = if let Some(identity) = maybe_identity { let response = if let Some(identity) = maybe_identity {
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
@ -22,16 +22,16 @@ pub async fn login(
let credentials2 = credentials.clone(); let credentials2 = credentials.clone();
let valid = let valid =
web::block(move || validate_basic_auth(credentials2, &config)) web::block(move || validate_basic_auth(credentials2, &config))
.await?; .await
.map_err(|_err| {
DomainError::new_thread_pool_error(_err.to_string())
})?;
if valid { if valid {
id.remember(credentials.user_id().to_string()); id.remember(credentials.user_id().to_string());
Ok(HttpResponse::Found().header("location", "/").finish()) Ok(HttpResponse::Found().header("location", "/").finish())
} else { } else {
Ok(HttpResponse::BadRequest().json( Err(DomainError::new_auth_error(
crate::models::errors::ErrorModel::new( "Wrong password or account does not exist".to_owned(),
20,
"Wrong password or account does not exist",
),
)) ))
} }
}; };
@ -69,7 +69,7 @@ pub async fn index(id: Identity) -> String {
pub fn validate_basic_auth( pub fn validate_basic_auth(
credentials: BasicAuth, credentials: BasicAuth,
config: &AppConfig, config: &AppConfig,
) -> Result<bool, errors::DomainError> { ) -> Result<bool, DomainError> {
let result = if let Some(password_ref) = credentials.password() { let result = if let Some(password_ref) = credentials.password() {
let pool = &config.pool; let pool = &config.pool;
let conn = pool.get()?; let conn = pool.get()?;
@ -81,7 +81,7 @@ pub fn validate_basic_auth(
)?; )?;
Ok(valid) Ok(valid)
} else { } else {
Err(errors::DomainError::new_password_error( Err(DomainError::new_password_error(
"No password given".to_owned(), "No password given".to_owned(),
)) ))
}; };

View File

@ -2,6 +2,7 @@ use actix_web::{get, post, web, HttpResponse};
use crate::errors::DomainError; use crate::errors::DomainError;
use crate::services::UserService; use crate::services::UserService;
use crate::utils::LogErrorResult;
use crate::AppConfig; use crate::AppConfig;
use crate::{actions, models}; use crate::{actions, models};
use actix_web::error::ResponseError; use actix_web::error::ResponseError;
@ -11,9 +12,9 @@ use validator::Validate;
#[get("/get/users/{user_id}")] #[get("/get/users/{user_id}")]
pub async fn get_user( pub async fn get_user(
config: web::Data<AppConfig>, config: web::Data<AppConfig>,
user_id: web::Path<i32>, user_id_param: web::Path<i32>,
) -> Result<HttpResponse, DomainError> { ) -> Result<HttpResponse, DomainError> {
let u_id = user_id.into_inner(); let u_id = user_id_param.into_inner();
// use web::block to offload blocking Diesel code without blocking server thread // use web::block to offload blocking Diesel code without blocking server thread
let res = web::block(move || { let res = web::block(move || {
let pool = &config.pool; let pool = &config.pool;
@ -21,19 +22,16 @@ pub async fn get_user(
actions::find_user_by_uid(u_id, &conn) actions::find_user_by_uid(u_id, &conn)
}) })
.await .await
.map_err(|_err| { .map_err(|err| DomainError::new_thread_pool_error(err.to_string()))
let res = DomainError::new_generic_error(format!( .log_err()?;
"No user found with uid: {}",
u_id
));
res
})?;
if let Some(user) = res { if let Some(user) = res {
Ok(HttpResponse::Ok().json(user)) Ok(HttpResponse::Ok().json(user))
} else { } else {
let res = HttpResponse::NotFound() let err = DomainError::new_entity_does_not_exist_error(format!(
.body(format!("No user found with uid: {}", u_id)); "No user found with uid: {}",
Ok(res) u_id
));
Err(err)
} }
} }
@ -47,43 +45,39 @@ pub async fn get_user2(
if let Some(user) = user { if let Some(user) = user {
Ok(HttpResponse::Ok().json(user)) Ok(HttpResponse::Ok().json(user))
} else { } else {
let res = HttpResponse::NotFound() let err = DomainError::new_entity_does_not_exist_error(format!(
.body(format!("No user found with uid: {}", u_id)); "No user found with uid: {}",
Ok(res) u_id
));
Err(err)
} }
} }
#[get("/get/users")] #[get("/get/users")]
pub async fn get_all_users( pub async fn get_all_users(
config: web::Data<AppConfig>, config: web::Data<AppConfig>,
) -> Result<HttpResponse, impl ResponseError> { ) -> Result<HttpResponse, DomainError> {
// use web::block to offload blocking Diesel code without blocking server thread // use web::block to offload blocking Diesel code without blocking server thread
let res = web::block(move || { let users = web::block(move || {
let pool = &config.pool; let pool = &config.pool;
let conn = pool.get()?; let conn = pool.get()?;
actions::get_all(&conn) actions::get_all(&conn)
}) })
.await .await
.map(|maybe_users| { .map_err(|err| DomainError::new_thread_pool_error(err.to_string()))
debug!("{:?}", maybe_users); .log_err()?;
if let Some(users) = maybe_users {
if users.is_empty() {
let res = HttpResponse::NotFound()
.json(models::ErrorModel::new(40, "No users available"));
// let res = crate::errors::DomainError::new_generic_error("".to_owned());
res
} else {
HttpResponse::Ok().json(users)
}
} else {
let res = HttpResponse::NotFound()
.json(models::ErrorModel::new(40, "No users available"));
res
}
});
res
}
debug!("{:?}", users);
if !users.is_empty() {
Ok(HttpResponse::Ok().json(users))
} else {
Err(DomainError::new_entity_does_not_exist_error(
"No users available".to_owned(),
))
}
}
//TODO: Add refinement here
/// Inserts new user with name defined in form. /// Inserts new user with name defined in form.
#[post("/do_registration")] #[post("/do_registration")]
pub async fn add_user( pub async fn add_user(

View File

@ -10,9 +10,7 @@ pub trait UserService {
user_name: String, user_name: String,
) -> Result<Option<models::UserDto>, errors::DomainError>; ) -> Result<Option<models::UserDto>, errors::DomainError>;
fn get_all( fn get_all(&self) -> Result<Vec<models::UserDto>, errors::DomainError>;
&self,
) -> Result<Option<Vec<models::UserDto>>, errors::DomainError>;
fn insert_new_user( fn insert_new_user(
&self, &self,
@ -50,9 +48,7 @@ impl UserService for UserServiceImpl {
actions::_find_user_by_name(user_name, &conn) actions::_find_user_by_name(user_name, &conn)
} }
fn get_all( fn get_all(&self) -> Result<Vec<models::UserDto>, errors::DomainError> {
&self,
) -> Result<Option<Vec<models::UserDto>>, errors::DomainError> {
let conn = self.pool.get()?; let conn = self.pool.get()?;
actions::get_all(&conn) actions::get_all(&conn)
} }

View File

@ -2,3 +2,5 @@ pub mod auth;
pub mod regexs; pub mod regexs;
pub use self::auth::*; pub use self::auth::*;
pub use self::regexs::*; pub use self::regexs::*;
pub mod ops;
pub use self::ops::*;

34
src/utils/ops.rs Normal file
View File

@ -0,0 +1,34 @@
use std::fmt::Display;
pub trait LogErrorResult<T, E> {
fn log_err(self) -> Result<T, E>;
}
impl<T, E: Display> LogErrorResult<T, E> for Result<T, E> {
fn log_err(self) -> Result<T, E> {
self.map_err(|err| {
error!("{}", err.to_string());
err
})
}
}
trait ResultOps<T, E> {
fn tap<U, F: FnOnce(T) -> U>(self, op: F) -> Result<T, E>;
fn tap_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, E>;
}
impl<T: Clone, E: Clone> ResultOps<T, E> for Result<T, E> {
fn tap<U, F: FnOnce(T) -> U>(self, op: F) -> Result<T, E> {
self.map(|x| {
op(x.clone());
x
})
}
fn tap_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, E> {
self.map_err(|err| {
op(err.clone());
err
})
}
}