1use std::sync::Arc;
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum Error {
10 #[error("connection failed: {0}")]
12 Connection(String),
13
14 #[error("connection closed")]
16 ConnectionClosed,
17
18 #[error("authentication failed: {0}")]
20 Authentication(#[from] mssql_auth::AuthError),
21
22 #[error("TLS error: {0}")]
24 Tls(String),
25
26 #[error("protocol error: {0}")]
28 Protocol(String),
29
30 #[error("codec error: {0}")]
32 Codec(#[from] mssql_codec::CodecError),
33
34 #[error("type error: {0}")]
36 Type(#[from] mssql_types::TypeError),
37
38 #[error("query error: {0}")]
40 Query(String),
41
42 #[error("server error {number}: {message}")]
44 Server {
45 number: i32,
47 class: u8,
49 state: u8,
51 message: String,
53 server: Option<String>,
55 procedure: Option<String>,
57 line: u32,
59 },
60
61 #[error("transaction error: {0}")]
63 Transaction(String),
64
65 #[error("configuration error: {0}")]
67 Config(String),
68
69 #[error("connection timed out")]
71 ConnectTimeout,
72
73 #[error("TLS handshake timed out")]
75 TlsTimeout,
76
77 #[error("connection timed out")]
79 ConnectionTimeout,
80
81 #[error("command timed out")]
83 CommandTimeout,
84
85 #[error("routing required to {host}:{port}")]
87 Routing {
88 host: String,
90 port: u16,
92 },
93
94 #[error("too many redirects (max {max})")]
96 TooManyRedirects {
97 max: u8,
99 },
100
101 #[error("IO error: {0}")]
103 Io(Arc<std::io::Error>),
104
105 #[error("invalid identifier: {0}")]
107 InvalidIdentifier(String),
108
109 #[error("connection pool exhausted")]
111 PoolExhausted,
112}
113
114impl From<mssql_tls::TlsError> for Error {
115 fn from(e: mssql_tls::TlsError) -> Self {
116 Error::Tls(e.to_string())
117 }
118}
119
120impl From<tds_protocol::ProtocolError> for Error {
121 fn from(e: tds_protocol::ProtocolError) -> Self {
122 Error::Protocol(e.to_string())
123 }
124}
125
126impl From<std::io::Error> for Error {
127 fn from(e: std::io::Error) -> Self {
128 Error::Io(Arc::new(e))
129 }
130}
131
132impl Error {
133 #[must_use]
149 pub fn is_transient(&self) -> bool {
150 match self {
151 Self::ConnectTimeout
152 | Self::TlsTimeout
153 | Self::ConnectionTimeout
154 | Self::CommandTimeout
155 | Self::ConnectionClosed
156 | Self::Routing { .. }
157 | Self::PoolExhausted
158 | Self::Io(_) => true,
159 Self::Server { number, .. } => Self::is_transient_server_error(*number),
160 _ => false,
161 }
162 }
163
164 #[must_use]
168 pub fn is_transient_server_error(number: i32) -> bool {
169 matches!(
170 number,
171 1205 | -2 | 10928 | 10929 | 40197 | 40501 | 40613 | 49918 | 49919 | 49920 | 4060 | 18456 )
184 }
185
186 #[must_use]
199 pub fn is_terminal(&self) -> bool {
200 match self {
201 Self::Config(_) | Self::InvalidIdentifier(_) => true,
202 Self::Server { number, .. } => Self::is_terminal_server_error(*number),
203 _ => false,
204 }
205 }
206
207 #[must_use]
211 pub fn is_terminal_server_error(number: i32) -> bool {
212 matches!(
213 number,
214 102 | 207 | 208 | 547 | 2627 | 2601 )
221 }
222
223 #[must_use]
228 pub fn is_protocol_error(&self) -> bool {
229 matches!(self, Self::Protocol(_))
230 }
231
232 #[must_use]
234 pub fn is_server_error(&self, number: i32) -> bool {
235 matches!(self, Self::Server { number: n, .. } if *n == number)
236 }
237
238 #[must_use]
246 pub fn class(&self) -> Option<u8> {
247 match self {
248 Self::Server { class, .. } => Some(*class),
249 _ => None,
250 }
251 }
252
253 #[must_use]
255 pub fn severity(&self) -> Option<u8> {
256 self.class()
257 }
258}
259
260pub type Result<T> = std::result::Result<T, Error>;
262
263#[cfg(test)]
264#[allow(clippy::unwrap_used)]
265mod tests {
266 use super::*;
267 use std::sync::Arc;
268
269 fn make_server_error(number: i32) -> Error {
270 Error::Server {
271 number,
272 class: 16,
273 state: 1,
274 message: "Test error".to_string(),
275 server: None,
276 procedure: None,
277 line: 1,
278 }
279 }
280
281 #[test]
282 fn test_is_transient_connection_errors() {
283 assert!(Error::ConnectionTimeout.is_transient());
284 assert!(Error::CommandTimeout.is_transient());
285 assert!(Error::ConnectionClosed.is_transient());
286 assert!(Error::PoolExhausted.is_transient());
287 assert!(
288 Error::Routing {
289 host: "test".into(),
290 port: 1433,
291 }
292 .is_transient()
293 );
294 }
295
296 #[test]
297 fn test_is_transient_io_error() {
298 let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "reset");
299 assert!(Error::Io(Arc::new(io_err)).is_transient());
300 }
301
302 #[test]
303 fn test_is_transient_server_errors_deadlock() {
304 assert!(make_server_error(1205).is_transient());
306 }
307
308 #[test]
309 fn test_is_transient_server_errors_timeout() {
310 assert!(make_server_error(-2).is_transient());
312 }
313
314 #[test]
315 fn test_is_transient_server_errors_azure() {
316 assert!(make_server_error(10928).is_transient()); assert!(make_server_error(10929).is_transient()); assert!(make_server_error(40197).is_transient()); assert!(make_server_error(40501).is_transient()); assert!(make_server_error(40613).is_transient()); assert!(make_server_error(49918).is_transient()); assert!(make_server_error(49919).is_transient()); assert!(make_server_error(49920).is_transient()); }
326
327 #[test]
328 fn test_is_transient_server_errors_other() {
329 assert!(make_server_error(4060).is_transient()); assert!(make_server_error(18456).is_transient()); }
333
334 #[test]
335 fn test_is_not_transient() {
336 assert!(!Error::Config("bad config".into()).is_transient());
338 assert!(!Error::Query("syntax error".into()).is_transient());
339 assert!(!Error::InvalidIdentifier("bad id".into()).is_transient());
340 assert!(!make_server_error(102).is_transient()); }
342
343 #[test]
344 fn test_is_terminal_server_errors() {
345 assert!(make_server_error(102).is_terminal()); assert!(make_server_error(207).is_terminal()); assert!(make_server_error(208).is_terminal()); assert!(make_server_error(547).is_terminal()); assert!(make_server_error(2627).is_terminal()); assert!(make_server_error(2601).is_terminal()); }
353
354 #[test]
355 fn test_is_terminal_config_errors() {
356 assert!(Error::Config("bad config".into()).is_terminal());
357 assert!(Error::InvalidIdentifier("bad id".into()).is_terminal());
358 }
359
360 #[test]
361 fn test_is_not_terminal() {
362 assert!(!Error::ConnectionTimeout.is_terminal());
364 assert!(!make_server_error(1205).is_terminal()); assert!(!make_server_error(40501).is_terminal()); }
367
368 #[test]
369 fn test_transient_server_error_static() {
370 assert!(Error::is_transient_server_error(1205));
372 assert!(Error::is_transient_server_error(40501));
373 assert!(!Error::is_transient_server_error(102));
374 }
375
376 #[test]
377 fn test_terminal_server_error_static() {
378 assert!(Error::is_terminal_server_error(102));
380 assert!(Error::is_terminal_server_error(2627));
381 assert!(!Error::is_terminal_server_error(1205));
382 }
383
384 #[test]
385 fn test_error_class() {
386 let err = make_server_error(102);
387 assert_eq!(err.class(), Some(16));
388 assert_eq!(err.severity(), Some(16));
389
390 assert_eq!(Error::ConnectionTimeout.class(), None);
391 }
392
393 #[test]
394 fn test_is_server_error() {
395 let err = make_server_error(102);
396 assert!(err.is_server_error(102));
397 assert!(!err.is_server_error(103));
398
399 assert!(!Error::ConnectionTimeout.is_server_error(102));
400 }
401}