Skip to main content

datapress_core/
errors.rs

1use actix_web::{HttpResponse, ResponseError, http::StatusCode};
2
3#[derive(Debug)]
4pub enum AppError {
5    UnknownColumn(String),
6    UnknownOperator(String),
7    InvalidValue(String),
8    NotFound(String),
9    Unauthorized(String),
10    Forbidden(String),
11    Unavailable(String),
12    /// A dataset's source resolved to no data (no matching files / no rows).
13    /// Used at startup to log-and-skip the dataset rather than aborting.
14    EmptyDataset(String),
15    Internal(String),
16}
17
18impl std::fmt::Display for AppError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            AppError::UnknownColumn(c) => write!(f, "unknown column: {c}"),
22            AppError::UnknownOperator(o) => write!(f, "unknown operator: {o}"),
23            AppError::InvalidValue(v) => write!(f, "invalid value: {v}"),
24            AppError::NotFound(n) => write!(f, "not found: {n}"),
25            AppError::Unauthorized(m) => write!(f, "unauthorized: {m}"),
26            AppError::Forbidden(m) => write!(f, "forbidden: {m}"),
27            AppError::Unavailable(m) => write!(f, "service unavailable: {m}"),
28            AppError::EmptyDataset(m) => write!(f, "empty dataset: {m}"),
29            AppError::Internal(s) => write!(f, "internal error: {s}"),
30        }
31    }
32}
33
34impl std::error::Error for AppError {}
35
36// ---------------------------------------------------------------------------
37// Backend-specific error conversions (cfg-gated so each binary only pulls in
38// what it needs).
39// ---------------------------------------------------------------------------
40
41#[cfg(feature = "duckdb")]
42impl From<duckdb::Error> for AppError {
43    fn from(e: duckdb::Error) -> Self {
44        AppError::Internal(e.to_string())
45    }
46}
47
48#[cfg(feature = "datafusion")]
49impl From<arrow::error::ArrowError> for AppError {
50    fn from(e: arrow::error::ArrowError) -> Self {
51        AppError::Internal(e.to_string())
52    }
53}
54
55#[cfg(feature = "datafusion")]
56impl From<parquet::errors::ParquetError> for AppError {
57    fn from(e: parquet::errors::ParquetError) -> Self {
58        AppError::Internal(e.to_string())
59    }
60}
61
62#[cfg(feature = "datafusion")]
63impl From<datafusion::error::DataFusionError> for AppError {
64    fn from(e: datafusion::error::DataFusionError) -> Self {
65        AppError::Internal(e.to_string())
66    }
67}
68
69impl ResponseError for AppError {
70    fn status_code(&self) -> StatusCode {
71        match self {
72            AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
73            AppError::NotFound(_) => StatusCode::NOT_FOUND,
74            AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
75            AppError::Forbidden(_) => StatusCode::FORBIDDEN,
76            AppError::Unavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
77            _ => StatusCode::BAD_REQUEST,
78        }
79    }
80
81    fn error_response(&self) -> HttpResponse {
82        if matches!(self, AppError::Internal(_)) {
83            log::error!("{self}");
84        }
85        HttpResponse::build(self.status_code())
86            .json(serde_json::json!({ "error": self.to_string() }))
87    }
88}