backend/
error.rs

1//! Error of the server and their implementation.
2
3use 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/// The default error used by the server.
12#[derive(Debug, Display, Error)]
13#[display(fmt = "{status_code}: {reason}")]
14#[allow(clippy::module_name_repetitions)] // Error structs need to be easily recognizable.
15pub struct ServiceError {
16    /// The status code to be used for the response.
17    pub status_code: StatusCode,
18    /// The reason for the error.
19    pub reason: String,
20}
21
22impl ServiceError {
23    /// Creates a new service error containing an [`Http status code`][StatusCode] and a reason for the error.
24    #[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}