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 thiserror::Error;
6
7/// Top-level error type encompassing all possible errors.
8#[derive(Error, Debug)]
9pub enum ExasolError {
10    /// Connection-related errors
11    #[error(transparent)]
12    Connection(#[from] ConnectionError),
13
14    /// Query execution errors
15    #[error(transparent)]
16    Query(#[from] QueryError),
17
18    /// Data conversion errors
19    #[error(transparent)]
20    Conversion(#[from] ConversionError),
21
22    /// Transport protocol errors
23    #[error(transparent)]
24    Transport(#[from] TransportError),
25}
26
27/// Errors related to database connections.
28#[derive(Error, Debug)]
29pub enum ConnectionError {
30    /// Failed to establish connection to the database
31    #[error("Failed to connect to {host}:{port}: {message}")]
32    ConnectionFailed {
33        host: String,
34        port: u16,
35        message: String,
36    },
37
38    /// Authentication failure
39    #[error("Authentication failed: {0}")]
40    AuthenticationFailed(String),
41
42    /// Invalid connection parameters
43    #[error("Invalid connection parameter '{parameter}': {message}")]
44    InvalidParameter { parameter: String, message: String },
45
46    /// Connection string parsing error
47    #[error("Failed to parse connection string: {0}")]
48    ParseError(String),
49
50    /// Connection timeout
51    #[error("Connection timeout after {timeout_ms}ms")]
52    Timeout { timeout_ms: u64 },
53
54    /// Connection is closed
55    #[error("Connection is closed")]
56    ConnectionClosed,
57
58    /// TLS/SSL error
59    #[error("TLS error: {0}")]
60    TlsError(String),
61}
62
63/// Errors related to query execution.
64#[derive(Error, Debug)]
65pub enum QueryError {
66    /// SQL syntax error
67    #[error("SQL syntax error at position {position}: {message}")]
68    SyntaxError { position: usize, message: String },
69
70    /// Query execution failed
71    #[error("Query execution failed: {0}")]
72    ExecutionFailed(String),
73
74    /// Query timeout
75    #[error("Query timeout after {timeout_ms}ms")]
76    Timeout { timeout_ms: u64 },
77
78    /// Invalid query state
79    #[error("Invalid query state: {0}")]
80    InvalidState(String),
81
82    /// Parameter binding error
83    #[error("Parameter binding error for parameter {index}: {message}")]
84    ParameterBindingError { index: usize, message: String },
85
86    /// Result set not available
87    #[error("Result set not available: {0}")]
88    NoResultSet(String),
89
90    /// Transaction error
91    #[error("Transaction error: {0}")]
92    TransactionError(String),
93
94    /// SQL injection attempt detected
95    #[error("Potential SQL injection detected")]
96    SqlInjectionDetected,
97
98    /// Prepared statement has been closed
99    #[error("Prepared statement has been closed")]
100    StatementClosed,
101
102    /// Unexpected result set when row count was expected
103    #[error("Expected row count but received result set")]
104    UnexpectedResultSet,
105}
106
107/// Errors related to data type conversion.
108#[derive(Error, Debug)]
109pub enum ConversionError {
110    /// Unsupported Exasol type
111    #[error("Unsupported Exasol type: {exasol_type}")]
112    UnsupportedType { exasol_type: String },
113
114    /// Failed to convert value
115    #[error("Failed to convert value at row {row}, column {column}: {message}")]
116    ValueConversionFailed {
117        row: usize,
118        column: usize,
119        message: String,
120    },
121
122    /// Schema mismatch
123    #[error("Schema mismatch: {0}")]
124    SchemaMismatch(String),
125
126    /// Invalid data format
127    #[error("Invalid data format: {0}")]
128    InvalidFormat(String),
129
130    /// Overflow during conversion
131    #[error("Numeric overflow at row {row}, column {column}")]
132    NumericOverflow { row: usize, column: usize },
133
134    /// Invalid UTF-8 string
135    #[error("Invalid UTF-8 string at row {row}, column {column}")]
136    InvalidUtf8 { row: usize, column: usize },
137
138    /// Arrow error
139    #[error("Arrow error: {0}")]
140    ArrowError(String),
141}
142
143/// Errors related to transport protocol.
144#[derive(Error, Debug)]
145pub enum TransportError {
146    /// WebSocket connection error
147    #[error("WebSocket error: {0}")]
148    WebSocketError(String),
149
150    /// Message serialization error
151    #[error("Serialization error: {0}")]
152    SerializationError(String),
153
154    /// Message deserialization error
155    #[error("Deserialization error: {0}")]
156    DeserializationError(String),
157
158    /// Protocol error
159    #[error("Protocol error: {0}")]
160    ProtocolError(String),
161
162    /// Invalid response from server
163    #[error("Invalid server response: {0}")]
164    InvalidResponse(String),
165
166    /// Network I/O error
167    #[error("Network I/O error: {0}")]
168    IoError(String),
169
170    /// Message send error
171    #[error("Failed to send message: {0}")]
172    SendError(String),
173
174    /// Message receive error
175    #[error("Failed to receive message: {0}")]
176    ReceiveError(String),
177
178    /// TLS/SSL error
179    #[error("TLS error: {0}")]
180    TlsError(String),
181}
182
183// Conversions from external error types
184impl From<arrow::error::ArrowError> for ConversionError {
185    fn from(err: arrow::error::ArrowError) -> Self {
186        ConversionError::ArrowError(err.to_string())
187    }
188}
189
190impl From<serde_json::Error> for TransportError {
191    fn from(err: serde_json::Error) -> Self {
192        TransportError::SerializationError(err.to_string())
193    }
194}
195
196#[cfg(feature = "websocket")]
197impl From<tokio_tungstenite::tungstenite::Error> for TransportError {
198    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
199        TransportError::WebSocketError(err.to_string())
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_connection_error_display() {
209        let err = ConnectionError::ConnectionFailed {
210            host: "localhost".to_string(),
211            port: 8563,
212            message: "Connection refused".to_string(),
213        };
214        assert!(err.to_string().contains("localhost"));
215        assert!(err.to_string().contains("8563"));
216    }
217
218    #[test]
219    fn test_query_error_display() {
220        let err = QueryError::SyntaxError {
221            position: 10,
222            message: "Unexpected token".to_string(),
223        };
224        assert!(err.to_string().contains("position 10"));
225    }
226
227    #[test]
228    fn test_conversion_error_display() {
229        let err = ConversionError::ValueConversionFailed {
230            row: 5,
231            column: 2,
232            message: "Invalid number format".to_string(),
233        };
234        assert!(err.to_string().contains("row 5"));
235        assert!(err.to_string().contains("column 2"));
236    }
237
238    #[test]
239    fn test_transport_tls_error() {
240        let err = TransportError::TlsError("Certificate validation failed".to_string());
241        assert!(err.to_string().contains("TLS error"));
242        assert!(err.to_string().contains("Certificate validation failed"));
243    }
244
245    #[test]
246    fn test_statement_closed_error() {
247        let err = QueryError::StatementClosed;
248        assert!(err.to_string().contains("closed"));
249    }
250
251    #[test]
252    fn test_unexpected_result_set_error() {
253        let err = QueryError::UnexpectedResultSet;
254        assert!(err.to_string().contains("result set"));
255    }
256}