use std::io;
use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_json::json;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("forbidden: {0}")]
Forbidden(String),
#[error("invalid range: {0}")]
InvalidRange(String),
#[error("invalid path: {0}")]
InvalidPath(String),
#[error(transparent)]
Io(#[from] io::Error),
#[error("storage backend error: {0}")]
Backend(String),
#[error("storage unavailable: {0}")]
StorageInvalid(String),
#[error("unsupported operation: {0}")]
Unsupported(String),
#[error("conflict: {0}")]
Conflict(String),
#[cfg(feature = "duckdb")]
#[error("query error: {0}")]
Query(String),
#[cfg(feature = "duckdb")]
#[error("query rejected: {0}")]
QueryRejected(String),
#[cfg(feature = "duckdb")]
#[error("query timed out after {0}s")]
QueryTimeout(u64),
#[cfg(feature = "duckdb")]
#[error("duckdb: {0}")]
DuckDbRaw(String),
#[cfg(feature = "duckdb")]
#[error("{message}")]
QueryDiagnosed { message: String, hint: String },
}
impl AppError {
fn status(&self) -> StatusCode {
match self {
AppError::NotFound(_) => StatusCode::NOT_FOUND,
AppError::Forbidden(_) => StatusCode::FORBIDDEN,
AppError::InvalidRange(_) => StatusCode::RANGE_NOT_SATISFIABLE,
AppError::InvalidPath(_) | AppError::Unsupported(_) => StatusCode::BAD_REQUEST,
AppError::Io(e) if e.kind() == io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
AppError::StorageInvalid(_) => StatusCode::SERVICE_UNAVAILABLE,
AppError::Io(_) | AppError::Backend(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::Conflict(_) => StatusCode::CONFLICT,
#[cfg(feature = "duckdb")]
AppError::Query(_) | AppError::QueryRejected(_) => StatusCode::BAD_REQUEST,
#[cfg(feature = "duckdb")]
AppError::QueryTimeout(_) => StatusCode::REQUEST_TIMEOUT,
#[cfg(feature = "duckdb")]
AppError::DuckDbRaw(_) => StatusCode::INTERNAL_SERVER_ERROR,
#[cfg(feature = "duckdb")]
AppError::QueryDiagnosed { .. } => StatusCode::BAD_REQUEST,
}
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let status = self.status();
#[cfg(feature = "duckdb")]
if let AppError::QueryDiagnosed {
ref message,
ref hint,
} = self
{
let body = Json(json!({
"error": status.canonical_reason().unwrap_or("error"),
"message": message,
"hint": hint,
}));
return (status, body).into_response();
}
let body = Json(json!({
"error": status.canonical_reason().unwrap_or("error"),
"message": self.to_string(),
}));
(status, body).into_response()
}
}