use crate::{Response, StatusCode};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Rate limit exceeded")]
RateLimitExceeded,
#[error("Database error: {0}")]
Database(String),
#[error("Internal server error: {0}")]
Internal(String),
}
impl AppError {
pub fn status_code(&self) -> StatusCode {
match self {
AppError::NotFound(_) => StatusCode::NOT_FOUND,
AppError::BadRequest(_) | AppError::Validation(_) => StatusCode::BAD_REQUEST,
AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
AppError::Forbidden(_) => StatusCode::FORBIDDEN,
AppError::Conflict(_) => StatusCode::CONFLICT,
AppError::RateLimitExceeded => StatusCode::TOO_MANY_REQUESTS,
AppError::Database(_) | AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
pub fn into_response(self) -> Response {
let status = self.status_code();
let body = serde_json::json!({
"error": {
"code": status.as_u16(),
"message": self.to_string()
}
});
Response::new()
.status(status)
.header("Content-Type", "application/json")
.body(hyper::Body::from(body.to_string()))
}
}
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => AppError::NotFound("Resource not found".to_string()),
_ => AppError::Database(err.to_string()),
}
}
}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
AppError::Internal(err.to_string())
}
}
impl From<serde_json::Error> for AppError {
fn from(err: serde_json::Error) -> Self {
AppError::BadRequest(format!("JSON error: {}", err))
}
}
pub type AppResult<T> = Result<T, AppError>;