use axum::{
Json,
http::StatusCode as AxumStatusCode,
response::{IntoResponse, Response as AxumResponse},
};
use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
#[derive(Error, Debug, Serialize, Deserialize, PartialEq)]
pub enum CurxmontErrorStatus {
#[error("Requested resource was not found")]
NotFound,
#[error("You are forbidden to access requested resource.")]
Forbidden,
#[error("Unknown Internal Error")]
Unknown,
#[error("Bad Request")]
BadRequest,
#[error("Conflict")]
Conflict,
#[error("Unauthorized")]
Unauthorized,
}
impl CurxmontErrorStatus {
pub fn from_code(code: u16) -> CurxmontErrorStatus {
match code {
404 => CurxmontErrorStatus::NotFound,
403 => CurxmontErrorStatus::Forbidden,
400 => CurxmontErrorStatus::BadRequest,
409 => CurxmontErrorStatus::Conflict,
401 => CurxmontErrorStatus::Unauthorized,
_ => CurxmontErrorStatus::Unknown,
}
}
}
#[derive(Serialize, Deserialize, Debug, Error)]
pub struct CruxmontError {
pub message: String,
pub status: CurxmontErrorStatus,
}
impl CruxmontError {
pub fn new(message: impl Into<String>, status: CurxmontErrorStatus) -> CruxmontError {
CruxmontError {
message: message.into(),
status,
}
}
pub fn not_found(message: impl Into<String>) -> CruxmontError {
CruxmontError {
message: message.into(),
status: CurxmontErrorStatus::NotFound,
}
}
pub fn forbidden(message: impl Into<String>) -> CruxmontError {
CruxmontError {
message: message.into(),
status: CurxmontErrorStatus::Forbidden,
}
}
pub fn unknown(message: impl Into<String>) -> CruxmontError {
CruxmontError {
message: message.into(),
status: CurxmontErrorStatus::Unknown,
}
}
pub fn bad_request(message: impl Into<String>) -> CruxmontError {
CruxmontError {
message: message.into(),
status: CurxmontErrorStatus::BadRequest,
}
}
pub fn conflict(message: impl Into<String>) -> CruxmontError {
CruxmontError {
message: message.into(),
status: CurxmontErrorStatus::Conflict,
}
}
pub fn unauthorized(message: impl Into<String>) -> CruxmontError {
CruxmontError {
message: message.into(),
status: CurxmontErrorStatus::Unauthorized,
}
}
}
impl fmt::Display for CruxmontError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl IntoResponse for CruxmontError {
fn into_response(self) -> AxumResponse {
let status_code = match self.status {
CurxmontErrorStatus::NotFound => AxumStatusCode::NOT_FOUND,
CurxmontErrorStatus::Forbidden => AxumStatusCode::FORBIDDEN,
CurxmontErrorStatus::Unknown => AxumStatusCode::INTERNAL_SERVER_ERROR,
CurxmontErrorStatus::BadRequest => AxumStatusCode::BAD_REQUEST,
CurxmontErrorStatus::Conflict => AxumStatusCode::CONFLICT,
CurxmontErrorStatus::Unauthorized => AxumStatusCode::UNAUTHORIZED,
};
(status_code, Json(self.message)).into_response()
}
}
impl From<sqlx::Error> for CruxmontError {
fn from(error: sqlx::Error) -> Self {
match error {
sqlx::Error::RowNotFound => CruxmontError::new(
"Resource not found".to_string(),
CurxmontErrorStatus::NotFound,
),
sqlx::Error::Database(db_err) if db_err.code().as_deref() == Some("23505") => {
CruxmontError::new(
"Duplicate entry".to_string(),
CurxmontErrorStatus::Conflict,
)
}
sqlx::Error::Database(db_err) if db_err.code().as_deref() == Some("23503") => {
CruxmontError::new(
"Foreign key constraint violation".to_string(),
CurxmontErrorStatus::BadRequest,
)
}
_ => CruxmontError::new(
format!("Database error: {}", error),
CurxmontErrorStatus::Unknown,
),
}
}
}
impl From<CruxmontError> for u32 {
fn from(value: CruxmontError) -> Self {
let outcome = match value.status {
CurxmontErrorStatus::NotFound => 404,
CurxmontErrorStatus::Forbidden => 403,
CurxmontErrorStatus::Unknown => 500,
CurxmontErrorStatus::BadRequest => 400,
CurxmontErrorStatus::Conflict => 409,
CurxmontErrorStatus::Unauthorized => 401,
};
outcome as u32
}
}