skade-flight/src/error.rs
//! One error type, one mapping to the wire.
//!
//! DataFusion's planner errors (bad SQL, unknown table) are the CLIENT's
//! fault and go out as `INVALID_ARGUMENT` so an ODBC/ADBC driver shows the
//! parser's message verbatim. Everything else (catalog I/O, object store,
//! IPC encoding) is `INTERNAL`.
use datafusion::error::DataFusionError;
use tonic::Status;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("sql: {0}")]
Sql(String),
/// A write (or session-view DDL) on an engine built with
/// `EngineOptions.read_only` — the lake's own writer owns every change.
#[error("read-only: {0}")]
ReadOnly(String),
#[error("datafusion: {0}")]
DataFusion(#[from] DataFusionError),
#[error("skade: {0}")]
Skade(#[from] skade::SkadeError),
#[error("iceberg: {0}")]
Iceberg(#[from] iceberg::Error),
#[error("arrow: {0}")]
Arrow(#[from] arrow_schema::ArrowError),
#[error("flight: {0}")]
Flight(#[from] arrow_flight::error::FlightError),
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
/// Classify a DataFusion error: planner / SQL / schema errors are the
/// caller's, and are reported as such.
pub fn from_df(e: DataFusionError) -> Self {
match e {
DataFusionError::SQL(..) | DataFusionError::Plan(..) | DataFusionError::SchemaError(..) => {
Error::Sql(e.strip_backtrace())
}
other => Error::DataFusion(other),
}
}
}
impl From<Error> for Status {
fn from(e: Error) -> Status {
match &e {
Error::Sql(m) => Status::invalid_argument(m.clone()),
Error::ReadOnly(m) => Status::permission_denied(format!("read-only: {m}")),
_ => Status::internal(e.to_string()),
}
}
}