Skip to main content

exarrow_rs/
error.rs

1//! Error types for exarrow-rs.
2//!
3//! This module defines domain-specific error types organized by functional area.
4
5use std::fmt;
6use thiserror::Error;
7
8/// Top-level error type encompassing all possible errors.
9#[derive(Error, Debug)]
10pub enum ExasolError {
11    /// Connection-related errors
12    #[error(transparent)]
13    Connection(#[from] ConnectionError),
14
15    /// Query execution errors
16    #[error(transparent)]
17    Query(#[from] QueryError),
18
19    /// Data conversion errors
20    #[error(transparent)]
21    Conversion(#[from] ConversionError),
22
23    /// Transport protocol errors
24    #[error(transparent)]
25    Transport(#[from] TransportError),
26}
27
28/// Errors related to database connections.
29#[derive(Error, Debug)]
30pub enum ConnectionError {
31    /// Failed to establish connection to the database
32    #[error("Failed to connect to {host}:{port}: {message}")]
33    ConnectionFailed {
34        host: String,
35        port: u16,
36        message: String,
37    },
38
39    /// Authentication failure
40    #[error("Authentication failed: {0}")]
41    AuthenticationFailed(String),
42
43    /// Invalid connection parameters
44    #[error("Invalid connection parameter '{parameter}': {message}")]
45    InvalidParameter { parameter: String, message: String },
46
47    /// Connection string parsing error
48    #[error("Failed to parse connection string: {0}")]
49    ParseError(String),
50
51    /// Connection timeout
52    #[error("Connection timeout after {timeout_ms}ms")]
53    Timeout { timeout_ms: u64 },
54
55    /// Connection is closed
56    #[error("Connection is closed")]
57    ConnectionClosed,
58
59    /// TLS/SSL error
60    #[error("TLS error: {0}")]
61    TlsError(String),
62}
63
64/// Errors related to query execution.
65#[derive(Error, Debug)]
66pub enum QueryError {
67    /// SQL syntax error
68    #[error("SQL syntax error at position {position}: {message}")]
69    SyntaxError { position: usize, message: String },
70
71    /// Query execution failed
72    #[error("Query execution failed: {0}")]
73    ExecutionFailed(String),
74
75    /// Query timeout
76    #[error("Query timeout after {timeout_ms}ms")]
77    Timeout { timeout_ms: u64 },
78
79    /// Invalid query state
80    #[error("Invalid query state: {0}")]
81    InvalidState(String),
82
83    /// Parameter binding error
84    #[error("Parameter binding error for parameter {index}: {message}")]
85    ParameterBindingError { index: usize, message: String },
86
87    /// Result set not available
88    #[error("Result set not available: {0}")]
89    NoResultSet(String),
90
91    /// Transaction error
92    #[error("Transaction error: {0}")]
93    TransactionError(String),
94
95    /// SQL injection attempt detected
96    #[error("Potential SQL injection detected")]
97    SqlInjectionDetected,
98
99    /// Prepared statement has been closed
100    #[error("Prepared statement has been closed")]
101    StatementClosed,
102
103    /// Unexpected result set when row count was expected
104    #[error("Expected row count but received result set")]
105    UnexpectedResultSet,
106}
107
108/// Errors related to data type conversion.
109#[derive(Error, Debug)]
110pub enum ConversionError {
111    /// Unsupported Exasol type
112    #[error("Unsupported Exasol type: {exasol_type}")]
113    UnsupportedType { exasol_type: String },
114
115    /// Failed to convert value
116    #[error("Failed to convert value at row {row}, column {column}: {message}")]
117    ValueConversionFailed {
118        row: usize,
119        column: usize,
120        message: String,
121    },
122
123    /// Schema mismatch
124    #[error("Schema mismatch: {0}")]
125    SchemaMismatch(String),
126
127    /// Invalid data format
128    #[error("Invalid data format: {0}")]
129    InvalidFormat(String),
130
131    /// Overflow during conversion
132    #[error("Numeric overflow at row {row}, column {column}")]
133    NumericOverflow { row: usize, column: usize },
134
135    /// Invalid UTF-8 string
136    #[error("Invalid UTF-8 string at row {row}, column {column}")]
137    InvalidUtf8 { row: usize, column: usize },
138
139    /// Arrow error
140    #[error("Arrow error: {0}")]
141    ArrowError(String),
142}
143
144/// Errors related to transport protocol.
145#[derive(Error, Debug)]
146pub enum TransportError {
147    /// WebSocket connection error
148    #[error("WebSocket error: {0}")]
149    WebSocketError(String),
150
151    /// Message serialization error
152    #[error("Serialization error: {0}")]
153    SerializationError(String),
154
155    /// Message deserialization error
156    #[error("Deserialization error: {0}")]
157    DeserializationError(String),
158
159    /// Protocol error
160    #[error("Protocol error: {0}")]
161    ProtocolError(String),
162
163    /// Invalid response from server
164    #[error("Invalid server response: {0}")]
165    InvalidResponse(String),
166
167    /// Network I/O error
168    #[error("Network I/O error: {0}")]
169    IoError(String),
170
171    /// Message send error
172    #[error("Failed to send message: {0}")]
173    SendError(String),
174
175    /// Message receive error
176    #[error("Failed to receive message: {0}")]
177    ReceiveError(String),
178
179    /// TLS/SSL error
180    #[error("TLS error: {0}")]
181    TlsError(String),
182}
183
184/// ADBC-compatible error codes.
185///
186/// These codes map to the ADBC specification for driver interoperability.
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum AdbcErrorCode {
189    /// Unknown error
190    Unknown = 0,
191    /// Connection error
192    Connection = 1,
193    /// Query error
194    Query = 2,
195    /// Invalid argument
196    InvalidArgument = 3,
197    /// Invalid state
198    InvalidState = 4,
199    /// Not implemented
200    NotImplemented = 5,
201    /// Timeout
202    Timeout = 6,
203}
204
205impl fmt::Display for AdbcErrorCode {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        match self {
208            AdbcErrorCode::Unknown => write!(f, "UNKNOWN"),
209            AdbcErrorCode::Connection => write!(f, "CONNECTION"),
210            AdbcErrorCode::Query => write!(f, "QUERY"),
211            AdbcErrorCode::InvalidArgument => write!(f, "INVALID_ARGUMENT"),
212            AdbcErrorCode::InvalidState => write!(f, "INVALID_STATE"),
213            AdbcErrorCode::NotImplemented => write!(f, "NOT_IMPLEMENTED"),
214            AdbcErrorCode::Timeout => write!(f, "TIMEOUT"),
215        }
216    }
217}
218
219impl ExasolError {
220    /// Map to ADBC error code.
221    pub fn to_adbc_code(&self) -> AdbcErrorCode {
222        match self {
223            ExasolError::Connection(e) => e.to_adbc_code(),
224            ExasolError::Query(e) => e.to_adbc_code(),
225            ExasolError::Conversion(_) => AdbcErrorCode::Query,
226            ExasolError::Transport(_) => AdbcErrorCode::Connection,
227        }
228    }
229}
230
231impl ConnectionError {
232    /// Map to ADBC error code.
233    pub fn to_adbc_code(&self) -> AdbcErrorCode {
234        match self {
235            ConnectionError::Timeout { .. } => AdbcErrorCode::Timeout,
236            ConnectionError::InvalidParameter { .. } => AdbcErrorCode::InvalidArgument,
237            _ => AdbcErrorCode::Connection,
238        }
239    }
240}
241
242impl QueryError {
243    /// Map to ADBC error code.
244    pub fn to_adbc_code(&self) -> AdbcErrorCode {
245        match self {
246            QueryError::Timeout { .. } => AdbcErrorCode::Timeout,
247            QueryError::InvalidState(_) => AdbcErrorCode::InvalidState,
248            QueryError::ParameterBindingError { .. } => AdbcErrorCode::InvalidArgument,
249            QueryError::StatementClosed => AdbcErrorCode::InvalidState,
250            _ => AdbcErrorCode::Query,
251        }
252    }
253}
254
255// Conversions from external error types
256impl From<arrow::error::ArrowError> for ConversionError {
257    fn from(err: arrow::error::ArrowError) -> Self {
258        ConversionError::ArrowError(err.to_string())
259    }
260}
261
262impl From<serde_json::Error> for TransportError {
263    fn from(err: serde_json::Error) -> Self {
264        TransportError::SerializationError(err.to_string())
265    }
266}
267
268impl From<tokio_tungstenite::tungstenite::Error> for TransportError {
269    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
270        TransportError::WebSocketError(err.to_string())
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_connection_error_display() {
280        let err = ConnectionError::ConnectionFailed {
281            host: "localhost".to_string(),
282            port: 8563,
283            message: "Connection refused".to_string(),
284        };
285        assert!(err.to_string().contains("localhost"));
286        assert!(err.to_string().contains("8563"));
287    }
288
289    #[test]
290    fn test_query_error_display() {
291        let err = QueryError::SyntaxError {
292            position: 10,
293            message: "Unexpected token".to_string(),
294        };
295        assert!(err.to_string().contains("position 10"));
296    }
297
298    #[test]
299    fn test_conversion_error_display() {
300        let err = ConversionError::ValueConversionFailed {
301            row: 5,
302            column: 2,
303            message: "Invalid number format".to_string(),
304        };
305        assert!(err.to_string().contains("row 5"));
306        assert!(err.to_string().contains("column 2"));
307    }
308
309    #[test]
310    fn test_adbc_error_code_mapping() {
311        let err = ExasolError::Connection(ConnectionError::Timeout { timeout_ms: 5000 });
312        assert_eq!(err.to_adbc_code(), AdbcErrorCode::Timeout);
313
314        let err = ExasolError::Query(QueryError::InvalidState("Bad state".to_string()));
315        assert_eq!(err.to_adbc_code(), AdbcErrorCode::InvalidState);
316    }
317
318    #[test]
319    fn test_error_code_display() {
320        assert_eq!(AdbcErrorCode::Connection.to_string(), "CONNECTION");
321        assert_eq!(AdbcErrorCode::Timeout.to_string(), "TIMEOUT");
322    }
323
324    #[test]
325    fn test_transport_tls_error() {
326        let err = TransportError::TlsError("Certificate validation failed".to_string());
327        assert!(err.to_string().contains("TLS error"));
328        assert!(err.to_string().contains("Certificate validation failed"));
329    }
330
331    #[test]
332    fn test_statement_closed_error() {
333        let err = QueryError::StatementClosed;
334        assert!(err.to_string().contains("closed"));
335        assert_eq!(err.to_adbc_code(), AdbcErrorCode::InvalidState);
336    }
337
338    #[test]
339    fn test_unexpected_result_set_error() {
340        let err = QueryError::UnexpectedResultSet;
341        assert!(err.to_string().contains("result set"));
342    }
343}