1use std::sync::Arc;
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9#[non_exhaustive]
10pub enum Error {
11 #[error("connection failed: {0}")]
13 Connection(String),
14
15 #[error("connection closed")]
17 ConnectionClosed,
18
19 #[error("authentication failed: {0}")]
21 Authentication(#[from] mssql_auth::AuthError),
22
23 #[error("TLS error: {0}")]
25 Tls(String),
26
27 #[error("protocol error: {0}")]
29 Protocol(String),
30
31 #[error("codec error: {0}")]
33 Codec(#[from] mssql_codec::CodecError),
34
35 #[error("type error: {0}")]
37 Type(#[from] mssql_types::TypeError),
38
39 #[error("query error: {0}")]
41 Query(String),
42
43 #[error("server error {number}: {message}")]
45 Server {
46 number: i32,
48 class: u8,
50 state: u8,
52 message: String,
54 server: Option<String>,
56 procedure: Option<String>,
58 line: u32,
60 },
61
62 #[error("transaction error: {0}")]
64 Transaction(String),
65
66 #[error("configuration error: {0}")]
68 Config(String),
69
70 #[error("connection timed out")]
72 ConnectTimeout,
73
74 #[error("TLS handshake timed out")]
76 TlsTimeout,
77
78 #[error("connection timed out")]
80 ConnectionTimeout,
81
82 #[error("command timed out")]
84 CommandTimeout,
85
86 #[error("routing required to {host}:{port}")]
88 Routing {
89 host: String,
91 port: u16,
93 },
94
95 #[error("too many redirects (max {max})")]
97 TooManyRedirects {
98 max: u8,
100 },
101
102 #[error("IO error: {0}")]
104 Io(Arc<std::io::Error>),
105
106 #[error("invalid identifier: {0}")]
108 InvalidIdentifier(String),
109
110 #[error("connection pool exhausted")]
112 PoolExhausted,
113
114 #[error("query cancellation failed: {0}")]
116 Cancel(String),
117
118 #[error("query cancelled")]
120 Cancelled,
121}
122
123#[cfg(feature = "tls")]
124impl From<mssql_tls::TlsError> for Error {
125 fn from(e: mssql_tls::TlsError) -> Self {
126 Error::Tls(e.to_string())
127 }
128}
129
130impl From<tds_protocol::ProtocolError> for Error {
131 fn from(e: tds_protocol::ProtocolError) -> Self {
132 Error::Protocol(e.to_string())
133 }
134}
135
136impl From<std::io::Error> for Error {
137 fn from(e: std::io::Error) -> Self {
138 Error::Io(Arc::new(e))
139 }
140}
141
142impl Error {
143 #[must_use]
159 pub fn is_transient(&self) -> bool {
160 match self {
161 Self::ConnectTimeout
162 | Self::TlsTimeout
163 | Self::ConnectionTimeout
164 | Self::CommandTimeout
165 | Self::ConnectionClosed
166 | Self::Routing { .. }
167 | Self::PoolExhausted
168 | Self::Io(_) => true,
169 Self::Server { number, .. } => Self::is_transient_server_error(*number),
170 _ => false,
171 }
172 }
173
174 #[must_use]
178 pub fn is_transient_server_error(number: i32) -> bool {
179 matches!(
180 number,
181 1205 | -2 | 10928 | 10929 | 40197 | 40501 | 40613 | 49918 | 49919 | 49920 | 4060 | 18456 )
194 }
195
196 #[must_use]
209 pub fn is_terminal(&self) -> bool {
210 match self {
211 Self::Config(_) | Self::InvalidIdentifier(_) => true,
212 Self::Server { number, .. } => Self::is_terminal_server_error(*number),
213 _ => false,
214 }
215 }
216
217 #[must_use]
221 pub fn is_terminal_server_error(number: i32) -> bool {
222 matches!(
223 number,
224 102 | 207 | 208 | 547 | 2627 | 2601 )
231 }
232
233 #[must_use]
238 pub fn is_protocol_error(&self) -> bool {
239 matches!(self, Self::Protocol(_))
240 }
241
242 #[must_use]
244 pub fn is_server_error(&self, number: i32) -> bool {
245 matches!(self, Self::Server { number: n, .. } if *n == number)
246 }
247
248 #[must_use]
256 pub fn class(&self) -> Option<u8> {
257 match self {
258 Self::Server { class, .. } => Some(*class),
259 _ => None,
260 }
261 }
262
263 #[must_use]
265 pub fn severity(&self) -> Option<u8> {
266 self.class()
267 }
268}
269
270pub type Result<T> = std::result::Result<T, Error>;
272
273#[cfg(test)]
274#[allow(clippy::unwrap_used)]
275mod tests {
276 use super::*;
277 use std::sync::Arc;
278
279 fn make_server_error(number: i32) -> Error {
280 Error::Server {
281 number,
282 class: 16,
283 state: 1,
284 message: "Test error".to_string(),
285 server: None,
286 procedure: None,
287 line: 1,
288 }
289 }
290
291 #[test]
292 fn test_is_transient_connection_errors() {
293 assert!(Error::ConnectionTimeout.is_transient());
294 assert!(Error::CommandTimeout.is_transient());
295 assert!(Error::ConnectionClosed.is_transient());
296 assert!(Error::PoolExhausted.is_transient());
297 assert!(
298 Error::Routing {
299 host: "test".into(),
300 port: 1433,
301 }
302 .is_transient()
303 );
304 }
305
306 #[test]
307 fn test_is_transient_io_error() {
308 let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "reset");
309 assert!(Error::Io(Arc::new(io_err)).is_transient());
310 }
311
312 #[test]
313 fn test_is_transient_server_errors_deadlock() {
314 assert!(make_server_error(1205).is_transient());
316 }
317
318 #[test]
319 fn test_is_transient_server_errors_timeout() {
320 assert!(make_server_error(-2).is_transient());
322 }
323
324 #[test]
325 fn test_is_transient_server_errors_azure() {
326 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()); }
336
337 #[test]
338 fn test_is_transient_server_errors_other() {
339 assert!(make_server_error(4060).is_transient()); assert!(make_server_error(18456).is_transient()); }
343
344 #[test]
345 fn test_is_not_transient() {
346 assert!(!Error::Config("bad config".into()).is_transient());
348 assert!(!Error::Query("syntax error".into()).is_transient());
349 assert!(!Error::InvalidIdentifier("bad id".into()).is_transient());
350 assert!(!make_server_error(102).is_transient()); }
352
353 #[test]
354 fn test_is_terminal_server_errors() {
355 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()); }
363
364 #[test]
365 fn test_is_terminal_config_errors() {
366 assert!(Error::Config("bad config".into()).is_terminal());
367 assert!(Error::InvalidIdentifier("bad id".into()).is_terminal());
368 }
369
370 #[test]
371 fn test_is_not_terminal() {
372 assert!(!Error::ConnectionTimeout.is_terminal());
374 assert!(!make_server_error(1205).is_terminal()); assert!(!make_server_error(40501).is_terminal()); }
377
378 #[test]
379 fn test_transient_server_error_static() {
380 assert!(Error::is_transient_server_error(1205));
382 assert!(Error::is_transient_server_error(40501));
383 assert!(!Error::is_transient_server_error(102));
384 }
385
386 #[test]
387 fn test_terminal_server_error_static() {
388 assert!(Error::is_terminal_server_error(102));
390 assert!(Error::is_terminal_server_error(2627));
391 assert!(!Error::is_terminal_server_error(1205));
392 }
393
394 #[test]
395 fn test_error_class() {
396 let err = make_server_error(102);
397 assert_eq!(err.class(), Some(16));
398 assert_eq!(err.severity(), Some(16));
399
400 assert_eq!(Error::ConnectionTimeout.class(), None);
401 }
402
403 #[test]
404 fn test_is_server_error() {
405 let err = make_server_error(102);
406 assert!(err.is_server_error(102));
407 assert!(!err.is_server_error(103));
408
409 assert!(!Error::ConnectionTimeout.is_server_error(102));
410 }
411}