use std::fmt; use axum::http::StatusCode; use axum::Json; use axum::response::{IntoResponse, Response}; use serde::{Deserialize, Serialize}; pub type SirenResult = Result; #[derive(Debug, Deserialize, Serialize)] pub struct Error { pub status: u16, pub details: String, } impl Error { pub fn new(error_status_code: u16, error_message: String) -> Self { Self { status: error_status_code, details: error_message, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.details.as_str()) } } impl std::error::Error for Error { fn description(&self) -> &str { &self.details } } impl IntoResponse for Error { fn into_response(self) -> Response { let status = StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); // Create a JSON response with the structured error let body = Json(serde_json::json!({ "error": { "status": self.status, "details": self.details, } })); // Return the response with the proper status and error body (status, body).into_response() } } impl From for Error { fn from(error: std::io::Error) -> Self { Self::new(500, format!("Unknown io error: {}", error)) } } impl From for Error { fn from(status: StatusCode) -> Self { Error { status: status.as_u16(), details: status .canonical_reason() .unwrap_or("Unknown error") .to_string(), } } } impl From for Error { fn from(error: std::string::FromUtf8Error) -> Self { Self::new(500, format!("Unknown from utf8 error: {}", error)) } } impl From for Error { fn from(error: sqlx::Error) -> Self { match error { sqlx::Error::RowNotFound => Error::new(404, "Not found".to_string()), sqlx::Error::ColumnIndexOutOfBounds { .. } => Error::new(422, error.to_string()), sqlx::Error::ColumnNotFound { .. } => Error::new(422, error.to_string()), sqlx::Error::ColumnDecode { .. } => Error::new(422, error.to_string()), sqlx::Error::Decode(_) => Error::new(422, error.to_string()), sqlx::Error::PoolTimedOut => Error::new(503, error.to_string()), sqlx::Error::PoolClosed => Error::new(503, error.to_string()), sqlx::Error::Tls(_) => Error::new(500, error.to_string()), sqlx::Error::Io(_) => Error::new(500, error.to_string()), sqlx::Error::Protocol(_) => Error::new(500, error.to_string()), sqlx::Error::Configuration(_) => Error::new(500, error.to_string()), sqlx::Error::AnyDriverError(_) => Error::new(500, error.to_string()), sqlx::Error::Database(err) => { if let Some(code) = err.code() { match code.trim() { // Unique violation "23505" => return Error::new(409, err.to_string()), _ => (), } } Error::new(500, err.to_string()) } sqlx::Error::Migrate(_) => Error::new(500, error.to_string()), sqlx::Error::TypeNotFound { type_name } => { Error::new(500, format!("Type not found: {}", type_name)) } sqlx::Error::WorkerCrashed => Error::new(500, error.to_string()), _ => Error::new(500, error.to_string()), } } } impl From for Error { fn from(error: sqlx::migrate::MigrateError) -> Self { Error::new(500, error.to_string()) } } impl From for Error { fn from(error: reqwest::Error) -> Self { Self::new(500, format!("Unknown reqwest error: {}", error)) } } impl From for Error { fn from(error: serde_json::Error) -> Self { Self::new(500, format!("Unknown serde_json error: {}", error)) } } impl From for Error { fn from(error: serenity::Error) -> Self { Self::new(500, format!("Unknown serenity error: {}", error)) } } impl From for Error { fn from(error: redis::RedisError) -> Self { Self::new(500, format!("Unknown redis error: {}", error)) } } impl From for Error { fn from(error: uuid::Error) -> Self { Self::new(500, format!("Unknown uuid error: {}", error)) } } impl From for Error { fn from(error: std::env::VarError) -> Self { Self::new(500, format!("Unknown env error: {}", error)) } } impl From for Error { fn from(error: songbird::error::JoinError) -> Self { Self::new(500, format!("Unable to join channel: {}", error)) } }