use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; /// Errors from individual volume HTTP requests — used for logging, not HTTP responses. #[derive(Debug)] pub enum VolumeError { Request { url: String, source: reqwest::Error }, BadStatus { url: String, status: reqwest::StatusCode }, } impl std::fmt::Display for VolumeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { VolumeError::Request { url, source } => { write!(f, "volume request to {url} failed: {source}") } VolumeError::BadStatus { url, status } => { write!(f, "volume {url} returned status {status}") } } } } /// Application-level errors that map to HTTP responses. #[derive(Debug)] pub enum AppError { NotFound, CorruptRecord { key: String }, Db(rusqlite::Error), InsufficientVolumes { need: usize, have: usize }, PartialWrite, } impl From for AppError { fn from(e: rusqlite::Error) -> Self { match e { rusqlite::Error::QueryReturnedNoRows => AppError::NotFound, other => AppError::Db(other), } } } impl std::fmt::Display for AppError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AppError::NotFound => write!(f, "not found"), AppError::CorruptRecord { key } => { write!(f, "corrupt record for key {key}: no volumes") } AppError::Db(e) => write!(f, "database error: {e}"), AppError::InsufficientVolumes { need, have } => { write!(f, "need {need} volumes but only {have} available") } AppError::PartialWrite => write!(f, "not all volume writes succeeded"), } } } impl IntoResponse for AppError { fn into_response(self) -> Response { let status = match &self { AppError::NotFound => StatusCode::NOT_FOUND, AppError::CorruptRecord { .. } => StatusCode::INTERNAL_SERVER_ERROR, AppError::Db(_) => StatusCode::INTERNAL_SERVER_ERROR, AppError::InsufficientVolumes { .. } => StatusCode::SERVICE_UNAVAILABLE, AppError::PartialWrite => StatusCode::BAD_GATEWAY, }; (status, self.to_string()).into_response() } }