use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ExasolError {
#[error(transparent)]
Connection(#[from] ConnectionError),
#[error(transparent)]
Query(#[from] QueryError),
#[error(transparent)]
Conversion(#[from] ConversionError),
#[error(transparent)]
Transport(#[from] TransportError),
}
#[derive(Error, Debug)]
pub enum ConnectionError {
#[error("Failed to connect to {host}:{port}: {message}")]
ConnectionFailed {
host: String,
port: u16,
message: String,
},
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Invalid connection parameter '{parameter}': {message}")]
InvalidParameter { parameter: String, message: String },
#[error("Failed to parse connection string: {0}")]
ParseError(String),
#[error("Connection timeout after {timeout_ms}ms")]
Timeout { timeout_ms: u64 },
#[error("Connection is closed")]
ConnectionClosed,
#[error("TLS error: {0}")]
TlsError(String),
}
#[derive(Error, Debug)]
pub enum QueryError {
#[error("SQL syntax error at position {position}: {message}")]
SyntaxError { position: usize, message: String },
#[error("Query execution failed: {0}")]
ExecutionFailed(String),
#[error("Query timeout after {timeout_ms}ms")]
Timeout { timeout_ms: u64 },
#[error("Invalid query state: {0}")]
InvalidState(String),
#[error("Parameter binding error for parameter {index}: {message}")]
ParameterBindingError { index: usize, message: String },
#[error("Result set not available: {0}")]
NoResultSet(String),
#[error("Transaction error: {0}")]
TransactionError(String),
#[error("Potential SQL injection detected")]
SqlInjectionDetected,
#[error("Prepared statement has been closed")]
StatementClosed,
#[error("Expected row count but received result set")]
UnexpectedResultSet,
}
#[derive(Error, Debug)]
pub enum ConversionError {
#[error("Unsupported Exasol type: {exasol_type}")]
UnsupportedType { exasol_type: String },
#[error("Failed to convert value at row {row}, column {column}: {message}")]
ValueConversionFailed {
row: usize,
column: usize,
message: String,
},
#[error("Schema mismatch: {0}")]
SchemaMismatch(String),
#[error("Invalid data format: {0}")]
InvalidFormat(String),
#[error("Numeric overflow at row {row}, column {column}")]
NumericOverflow { row: usize, column: usize },
#[error("Invalid UTF-8 string at row {row}, column {column}")]
InvalidUtf8 { row: usize, column: usize },
#[error("Arrow error: {0}")]
ArrowError(String),
}
#[derive(Error, Debug)]
pub enum TransportError {
#[error("WebSocket error: {0}")]
WebSocketError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Deserialization error: {0}")]
DeserializationError(String),
#[error("Protocol error: {0}")]
ProtocolError(String),
#[error("Invalid server response: {0}")]
InvalidResponse(String),
#[error("Network I/O error: {0}")]
IoError(String),
#[error("Failed to send message: {0}")]
SendError(String),
#[error("Failed to receive message: {0}")]
ReceiveError(String),
#[error("TLS error: {0}")]
TlsError(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdbcErrorCode {
Unknown = 0,
Connection = 1,
Query = 2,
InvalidArgument = 3,
InvalidState = 4,
NotImplemented = 5,
Timeout = 6,
}
impl fmt::Display for AdbcErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AdbcErrorCode::Unknown => write!(f, "UNKNOWN"),
AdbcErrorCode::Connection => write!(f, "CONNECTION"),
AdbcErrorCode::Query => write!(f, "QUERY"),
AdbcErrorCode::InvalidArgument => write!(f, "INVALID_ARGUMENT"),
AdbcErrorCode::InvalidState => write!(f, "INVALID_STATE"),
AdbcErrorCode::NotImplemented => write!(f, "NOT_IMPLEMENTED"),
AdbcErrorCode::Timeout => write!(f, "TIMEOUT"),
}
}
}
impl ExasolError {
pub fn to_adbc_code(&self) -> AdbcErrorCode {
match self {
ExasolError::Connection(e) => e.to_adbc_code(),
ExasolError::Query(e) => e.to_adbc_code(),
ExasolError::Conversion(_) => AdbcErrorCode::Query,
ExasolError::Transport(_) => AdbcErrorCode::Connection,
}
}
}
impl ConnectionError {
pub fn to_adbc_code(&self) -> AdbcErrorCode {
match self {
ConnectionError::Timeout { .. } => AdbcErrorCode::Timeout,
ConnectionError::InvalidParameter { .. } => AdbcErrorCode::InvalidArgument,
_ => AdbcErrorCode::Connection,
}
}
}
impl QueryError {
pub fn to_adbc_code(&self) -> AdbcErrorCode {
match self {
QueryError::Timeout { .. } => AdbcErrorCode::Timeout,
QueryError::InvalidState(_) => AdbcErrorCode::InvalidState,
QueryError::ParameterBindingError { .. } => AdbcErrorCode::InvalidArgument,
QueryError::StatementClosed => AdbcErrorCode::InvalidState,
_ => AdbcErrorCode::Query,
}
}
}
impl From<arrow::error::ArrowError> for ConversionError {
fn from(err: arrow::error::ArrowError) -> Self {
ConversionError::ArrowError(err.to_string())
}
}
impl From<serde_json::Error> for TransportError {
fn from(err: serde_json::Error) -> Self {
TransportError::SerializationError(err.to_string())
}
}
#[cfg(feature = "websocket")]
impl From<tokio_tungstenite::tungstenite::Error> for TransportError {
fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
TransportError::WebSocketError(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_error_display() {
let err = ConnectionError::ConnectionFailed {
host: "localhost".to_string(),
port: 8563,
message: "Connection refused".to_string(),
};
assert!(err.to_string().contains("localhost"));
assert!(err.to_string().contains("8563"));
}
#[test]
fn test_query_error_display() {
let err = QueryError::SyntaxError {
position: 10,
message: "Unexpected token".to_string(),
};
assert!(err.to_string().contains("position 10"));
}
#[test]
fn test_conversion_error_display() {
let err = ConversionError::ValueConversionFailed {
row: 5,
column: 2,
message: "Invalid number format".to_string(),
};
assert!(err.to_string().contains("row 5"));
assert!(err.to_string().contains("column 2"));
}
#[test]
fn test_adbc_error_code_mapping() {
let err = ExasolError::Connection(ConnectionError::Timeout { timeout_ms: 5000 });
assert_eq!(err.to_adbc_code(), AdbcErrorCode::Timeout);
let err = ExasolError::Query(QueryError::InvalidState("Bad state".to_string()));
assert_eq!(err.to_adbc_code(), AdbcErrorCode::InvalidState);
}
#[test]
fn test_error_code_display() {
assert_eq!(AdbcErrorCode::Connection.to_string(), "CONNECTION");
assert_eq!(AdbcErrorCode::Timeout.to_string(), "TIMEOUT");
}
#[test]
fn test_transport_tls_error() {
let err = TransportError::TlsError("Certificate validation failed".to_string());
assert!(err.to_string().contains("TLS error"));
assert!(err.to_string().contains("Certificate validation failed"));
}
#[test]
fn test_statement_closed_error() {
let err = QueryError::StatementClosed;
assert!(err.to_string().contains("closed"));
assert_eq!(err.to_adbc_code(), AdbcErrorCode::InvalidState);
}
#[test]
fn test_unexpected_result_set_error() {
let err = QueryError::UnexpectedResultSet;
assert!(err.to_string().contains("result set"));
}
}