Skip to main content

voltdb_client_rust/
error.rs

1//! Canonical error type for the VoltDB client.
2
3use std::sync::PoisonError;
4
5use crate::response::VoltResponseInfo;
6
7/// The single error type for all VoltDB client operations.
8#[derive(Debug, thiserror::Error)]
9pub enum VoltError {
10    #[error("I/O error: {0}")]
11    Io(#[from] std::io::Error),
12
13    #[error("Recv error: {0}")]
14    RecvError(#[from] std::sync::mpsc::RecvError),
15
16    #[error("volt execute failed: {0:?}")]
17    ExecuteFail(VoltResponseInfo),
18
19    #[error("InvalidColumnType {0}")]
20    InvalidColumnType(i8),
21
22    #[error("MessageTooLarge {0}")]
23    MessageTooLarge(usize),
24
25    #[error("Error {0}")]
26    NoValue(String),
27
28    #[error("Error {0}")]
29    Other(String),
30
31    #[error("Error {0}")]
32    NegativeNumTables(i16),
33
34    #[error("Utf8 error: {0}")]
35    Utf8Error(#[from] std::str::Utf8Error),
36
37    #[error("Error {0}")]
38    PoisonError(String),
39
40    #[error("Error {0}")]
41    BadReturnStatusOnTable(i8),
42
43    #[error("Auth failed")]
44    AuthFailed,
45
46    #[error("Connection lost")]
47    ConnectionNotAvailable,
48
49    #[error("Invalid Config")]
50    InvalidConfig,
51
52    #[error("Operation timeout")]
53    Timeout,
54
55    #[error("Connection Closed")]
56    ConnectionClosed,
57
58    /// Returned when a non-Option type encounters a NULL value.
59    /// Use `Option<T>` if the column may contain NULL values.
60    #[error("Unexpected NULL value in column '{0}'. Use Option<T> for nullable columns.")]
61    UnexpectedNull(String),
62
63    #[error("Pool is shutting down")]
64    PoolShutdown,
65
66    #[error("Circuit breaker is open")]
67    CircuitOpen,
68
69    #[error("Pool exhausted, no healthy connections")]
70    PoolExhausted,
71}
72
73impl<T> From<PoisonError<T>> for VoltError {
74    fn from(p: PoisonError<T>) -> VoltError {
75        VoltError::PoisonError(p.to_string().to_owned())
76    }
77}
78
79impl VoltError {
80    /// Returns true if this error indicates a fatal connection problem
81    /// that requires the connection to be replaced/healed.
82    pub fn is_connection_fatal(&self) -> bool {
83        match self {
84            VoltError::Io(io_err) => matches!(
85                io_err.kind(),
86                std::io::ErrorKind::ConnectionReset
87                    | std::io::ErrorKind::ConnectionRefused
88                    | std::io::ErrorKind::ConnectionAborted
89                    | std::io::ErrorKind::BrokenPipe
90                    | std::io::ErrorKind::NotConnected
91                    | std::io::ErrorKind::UnexpectedEof
92            ),
93            VoltError::ConnectionNotAvailable => true,
94            VoltError::ConnectionClosed => true,
95            VoltError::Timeout => true,
96            VoltError::RecvError(_) => true,
97            VoltError::PoolShutdown => true,
98            _ => false,
99        }
100    }
101
102    pub fn message_too_large(size: usize) -> Self {
103        VoltError::MessageTooLarge(size)
104    }
105
106    pub fn connection_closed() -> Self {
107        VoltError::ConnectionClosed
108    }
109
110    pub fn timeout() -> Self {
111        VoltError::Timeout
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::io;
119
120    #[test]
121    fn test_volt_error_display() {
122        let err = VoltError::AuthFailed;
123        assert_eq!(format!("{}", err), "Auth failed");
124
125        let err = VoltError::ConnectionNotAvailable;
126        assert_eq!(format!("{}", err), "Connection lost");
127
128        let err = VoltError::Timeout;
129        assert_eq!(format!("{}", err), "Operation timeout");
130    }
131
132    #[test]
133    fn test_volt_error_from_io() {
134        let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "refused");
135        let volt_err: VoltError = io_err.into();
136        assert!(matches!(volt_err, VoltError::Io(_)));
137    }
138
139    #[test]
140    fn test_volt_error_unexpected_null() {
141        let err = VoltError::UnexpectedNull("my_column".to_string());
142        let msg = format!("{}", err);
143        assert!(msg.contains("my_column"));
144        assert!(msg.contains("Option<T>"));
145    }
146
147    #[test]
148    fn test_pool_shutdown_display() {
149        assert_eq!(
150            format!("{}", VoltError::PoolShutdown),
151            "Pool is shutting down"
152        );
153    }
154
155    #[test]
156    fn test_circuit_open_display() {
157        assert_eq!(
158            format!("{}", VoltError::CircuitOpen),
159            "Circuit breaker is open"
160        );
161    }
162
163    #[test]
164    fn test_pool_exhausted_display() {
165        assert_eq!(
166            format!("{}", VoltError::PoolExhausted),
167            "Pool exhausted, no healthy connections"
168        );
169    }
170
171    #[test]
172    fn test_is_connection_fatal_pool_shutdown() {
173        let err = VoltError::PoolShutdown;
174        assert!(err.is_connection_fatal());
175    }
176
177    #[test]
178    fn test_is_connection_fatal_io_connection_reset() {
179        let err = VoltError::Io(io::Error::new(io::ErrorKind::ConnectionReset, "reset"));
180        assert!(err.is_connection_fatal());
181    }
182
183    #[test]
184    fn test_is_connection_fatal_no_value_not_fatal() {
185        let err = VoltError::NoValue("test".to_string());
186        assert!(!err.is_connection_fatal());
187    }
188
189    #[test]
190    fn test_convenience_constructors() {
191        assert!(matches!(
192            VoltError::message_too_large(100),
193            VoltError::MessageTooLarge(100)
194        ));
195        assert!(matches!(
196            VoltError::connection_closed(),
197            VoltError::ConnectionClosed
198        ));
199        assert!(matches!(VoltError::timeout(), VoltError::Timeout));
200    }
201}