use actix_web::{HttpResponse, ResponseError}; use serde::Serialize; #[derive(Debug, Serialize)] struct ErrorResponse { error: String, status: u16, } #[derive(Debug, thiserror::Error)] pub enum ApiError { #[error("not found: {0}")] NotFound(String), #[error("bad request: {0}")] BadRequest(String), #[error("unauthorized: {0}")] Unauthorized(String), #[error("forbidden: {0}")] Forbidden(String), #[error("rate limited: {0}")] TooManyRequests(String), #[error("internal error: {0}")] Internal(String), } impl ResponseError for ApiError { fn error_response(&self) -> HttpResponse { let (status, message) = match self { ApiError::NotFound(msg) => (actix_web::http::StatusCode::NOT_FOUND, msg.clone()), ApiError::BadRequest(msg) => (actix_web::http::StatusCode::BAD_REQUEST, msg.clone()), ApiError::Unauthorized(msg) => (actix_web::http::StatusCode::UNAUTHORIZED, msg.clone()), ApiError::Forbidden(msg) => (actix_web::http::StatusCode::FORBIDDEN, msg.clone()), ApiError::TooManyRequests(msg) => { tracing::warn!(error = %msg, "rate limited"); (actix_web::http::StatusCode::TOO_MANY_REQUESTS, msg.clone()) } ApiError::Internal(msg) => { tracing::error!(error = %msg, "internal error"); ( actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, msg.clone(), ) } }; HttpResponse::build(status).json(ErrorResponse { error: message, status: status.as_u16(), }) } } impl From for ApiError { fn from(e: shanty_db::DbError) -> Self { match e { shanty_db::DbError::NotFound(msg) => ApiError::NotFound(msg), other => ApiError::Internal(other.to_string()), } } } impl From for ApiError { fn from(e: shanty_watch::WatchError) -> Self { ApiError::Internal(e.to_string()) } } impl From for ApiError { fn from(e: shanty_search::SearchError) -> Self { ApiError::Internal(e.to_string()) } } impl From for ApiError { fn from(e: shanty_dl::DlError) -> Self { match e { shanty_dl::DlError::RateLimited(msg) => ApiError::TooManyRequests(msg), other => ApiError::Internal(other.to_string()), } } } impl From for ApiError { fn from(e: shanty_index::IndexError) -> Self { ApiError::Internal(e.to_string()) } } impl From for ApiError { fn from(e: shanty_tag::TagError) -> Self { ApiError::Internal(e.to_string()) } } impl From for ApiError { fn from(e: shanty_data::DataError) -> Self { ApiError::Internal(e.to_string()) } } impl From for ApiError { fn from(e: shanty_org::OrgError) -> Self { ApiError::Internal(e.to_string()) } }