use actix_web::{
http::{header::ContentType, StatusCode},
HttpResponse, ResponseError,
};
use derive_more::{Display, Error};
use diesel::result::{DatabaseErrorKind, Error as DieselError};
use diesel_async::pooled_connection::deadpool::PoolError;
#[derive(Debug, Display, Error)]
#[display(fmt = "{status_code}: {reason}")]
#[allow(clippy::module_name_repetitions)] pub struct ServiceError {
pub status_code: StatusCode,
pub reason: String,
}
impl ServiceError {
#[must_use]
pub fn new(status_code: StatusCode, reason: &str) -> Self {
Self {
status_code,
reason: reason.to_owned(),
}
}
}
impl ResponseError for ServiceError {
fn status_code(&self) -> StatusCode {
self.status_code
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::plaintext())
.body(self.reason.clone())
}
}
impl From<PoolError> for ServiceError {
fn from(value: PoolError) -> Self {
let error_string = value.to_string();
log::error!("Unable to get connection to database: {}", error_string);
Self::new(StatusCode::INTERNAL_SERVER_ERROR, &error_string)
}
}
impl From<DieselError> for ServiceError {
fn from(value: DieselError) -> Self {
let status_code = if value == DieselError::NotFound {
StatusCode::NOT_FOUND
} else if let DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _) = value {
StatusCode::CONFLICT
} else if let DieselError::DatabaseError(DatabaseErrorKind::CheckViolation, _) = value {
StatusCode::BAD_REQUEST
} else if let DieselError::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _) = value
{
StatusCode::CONFLICT
} else if let DieselError::DatabaseError(DatabaseErrorKind::NotNullViolation, _) = value {
StatusCode::BAD_REQUEST
} else {
log::error!("Error executing diesel SQL query: {}", value.to_string());
StatusCode::INTERNAL_SERVER_ERROR
};
Self::new(status_code, &value.to_string())
}
}