use std::io; use actix_web::{HttpResponse, ResponseError, http::StatusCode}; use serde::Serialize; use thiserror::Error; pub type AppResult = Result; #[derive(Debug, Error)] pub enum AppError { #[error("I/O error: {0}")] Io(#[from] io::Error), #[error("database error: {0}")] Db(#[from] rusqlite::Error), #[error("database pool error: {0}")] Pool(String), #[error("configuration parse error: {0}")] Config(toml::de::Error), #[error("validation error: {0}")] Validation(String), #[error("conflict: {0}")] Conflict(String), #[error("not found: {0}")] NotFound(String), #[error("unauthorized: {0}")] Unauthorized(String), #[error("forbidden: {0}")] Forbidden(String), #[error("rate limited")] RateLimited, #[error("git error: {0}")] Git(String), } #[derive(Serialize)] struct ErrorBody { code: &'static str, message: String, status: u16, } impl ResponseError for AppError { fn status_code(&self) -> StatusCode { match self { AppError::Validation(_) => StatusCode::BAD_REQUEST, AppError::Conflict(_) => StatusCode::CONFLICT, AppError::NotFound(_) => StatusCode::NOT_FOUND, AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED, AppError::Forbidden(_) => StatusCode::FORBIDDEN, AppError::RateLimited => StatusCode::TOO_MANY_REQUESTS, AppError::Io(_) | AppError::Db(_) | AppError::Pool(_) | AppError::Config(_) | AppError::Git(_) => { StatusCode::INTERNAL_SERVER_ERROR } } } fn error_response(&self) -> HttpResponse { HttpResponse::build(self.status_code()).json(ErrorBody { code: self.code(), message: self.public_message(), status: self.status_code().as_u16(), }) } } impl AppError { fn code(&self) -> &'static str { match self { AppError::Validation(_) => "validation_error", AppError::Conflict(_) => "conflict", AppError::NotFound(_) => "not_found", AppError::Unauthorized(_) => "unauthorized", AppError::Forbidden(_) => "forbidden", AppError::RateLimited => "rate_limited", AppError::Io(_) | AppError::Db(_) | AppError::Pool(_) | AppError::Config(_) | AppError::Git(_) => { "internal_error" } } } fn public_message(&self) -> String { match self { AppError::Validation(message) | AppError::Conflict(message) | AppError::NotFound(message) | AppError::Unauthorized(message) | AppError::Forbidden(message) => message.clone(), AppError::RateLimited => "too many requests".to_string(), AppError::Io(_) | AppError::Db(_) | AppError::Pool(_) | AppError::Config(_) | AppError::Git(_) => { "internal server error".to_string() } } } }