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    Internal(String),
13}
14
15impl std::fmt::Display for AppError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            AppError::UnknownColumn(c) => write!(f, "unknown column: {c}"),
19            AppError::UnknownOperator(o) => write!(f, "unknown operator: {o}"),
20            AppError::InvalidValue(v) => write!(f, "invalid value: {v}"),
21            AppError::NotFound(n) => write!(f, "not found: {n}"),
22            AppError::Unauthorized(m) => write!(f, "unauthorized: {m}"),
23            AppError::Forbidden(m) => write!(f, "forbidden: {m}"),
24            AppError::Unavailable(m) => write!(f, "service unavailable: {m}"),
25            AppError::Internal(s) => write!(f, "internal error: {s}"),
26        }
27    }
28}
29
30impl std::error::Error for AppError {}
31
32// ---------------------------------------------------------------------------
33// Backend-specific error conversions (cfg-gated so each binary only pulls in
34// what it needs).
35// ---------------------------------------------------------------------------
36
37#[cfg(feature = "duckdb")]
38impl From<duckdb::Error> for AppError {
39    fn from(e: duckdb::Error) -> Self {
40        AppError::Internal(e.to_string())
41    }
42}
43
44#[cfg(feature = "datafusion")]
45impl From<arrow::error::ArrowError> for AppError {
46    fn from(e: arrow::error::ArrowError) -> Self {
47        AppError::Internal(e.to_string())
48    }
49}
50
51#[cfg(feature = "datafusion")]
52impl From<parquet::errors::ParquetError> for AppError {
53    fn from(e: parquet::errors::ParquetError) -> Self {
54        AppError::Internal(e.to_string())
55    }
56}
57
58#[cfg(feature = "datafusion")]
59impl From<datafusion::error::DataFusionError> for AppError {
60    fn from(e: datafusion::error::DataFusionError) -> Self {
61        AppError::Internal(e.to_string())
62    }
63}
64
65impl ResponseError for AppError {
66    fn status_code(&self) -> StatusCode {
67        match self {
68            AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
69            AppError::NotFound(_) => StatusCode::NOT_FOUND,
70            AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
71            AppError::Forbidden(_) => StatusCode::FORBIDDEN,
72            AppError::Unavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
73            _ => StatusCode::BAD_REQUEST,
74        }
75    }
76
77    fn error_response(&self) -> HttpResponse {
78        if matches!(self, AppError::Internal(_)) {
79            log::error!("{self}");
80        }
81        HttpResponse::build(self.status_code())
82            .json(serde_json::json!({ "error": self.to_string() }))
83    }
84}