1use actix_web::{
4 http::{header::ContentType, StatusCode},
5 HttpResponse, ResponseError,
6};
7use derive_more::{Display, Error};
8use diesel::result::{DatabaseErrorKind, Error as DieselError};
9use diesel_async::pooled_connection::deadpool::PoolError;
10
11#[derive(Debug, Display, Error)]
13#[display(fmt = "{status_code}: {reason}")]
14#[allow(clippy::module_name_repetitions)] pub struct ServiceError {
16 pub status_code: StatusCode,
18 pub reason: String,
20}
21
22impl ServiceError {
23 #[must_use]
25 pub fn new(status_code: StatusCode, reason: &str) -> Self {
26 Self {
27 status_code,
28 reason: reason.to_owned(),
29 }
30 }
31}
32
33impl ResponseError for ServiceError {
34 fn status_code(&self) -> StatusCode {
35 self.status_code
36 }
37
38 fn error_response(&self) -> HttpResponse {
39 HttpResponse::build(self.status_code())
40 .insert_header(ContentType::plaintext())
41 .body(self.reason.clone())
42 }
43}
44
45impl From<PoolError> for ServiceError {
46 fn from(value: PoolError) -> Self {
47 let error_string = value.to_string();
48 log::error!("Unable to get connection to database: {}", error_string);
49 Self::new(StatusCode::INTERNAL_SERVER_ERROR, &error_string)
50 }
51}
52
53impl From<DieselError> for ServiceError {
54 fn from(value: DieselError) -> Self {
55 let status_code = if value == DieselError::NotFound {
56 StatusCode::NOT_FOUND
57 } else if let DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _) = value {
58 StatusCode::CONFLICT
59 } else if let DieselError::DatabaseError(DatabaseErrorKind::CheckViolation, _) = value {
60 StatusCode::BAD_REQUEST
61 } else if let DieselError::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _) = value
62 {
63 StatusCode::CONFLICT
64 } else if let DieselError::DatabaseError(DatabaseErrorKind::NotNullViolation, _) = value {
65 StatusCode::BAD_REQUEST
66 } else {
67 log::error!("Error executing diesel SQL query: {}", value.to_string());
68 StatusCode::INTERNAL_SERVER_ERROR
69 };
70
71 Self::new(status_code, &value.to_string())
72 }
73}