Skip to main content

tds_driver/
error.rs

1//! Error module
2pub use crate::tds::codec::TokenError;
3pub use std::io::ErrorKind as IoErrorKind;
4use std::{borrow::Cow, convert::Infallible, io};
5use thiserror::Error;
6
7/// A unified error enum that contains several errors that might occurr during
8/// the lifecycle of this driver
9#[derive(Debug, Clone, Error, PartialEq, Eq)]
10pub enum Error {
11    #[error("An error occurred during the attempt of performing I/O: {}", message)]
12    /// An error occurred when performing I/O to the server.
13    Io {
14        /// A list specifying general categories of I/O error.
15        kind: IoErrorKind,
16        /// The error description.
17        message: String,
18    },
19    #[error("Protocol error: {}", _0)]
20    /// An error happened during the request or response parsing.
21    Protocol(Cow<'static, str>),
22    #[error("Encoding error: {}", _0)]
23    /// Server responded with encoding not supported.
24    Encoding(Cow<'static, str>),
25    #[error("Conversion error: {}", _0)]
26    /// Conversion failure from one type to another.
27    Conversion(Cow<'static, str>),
28    #[error("UTF-8 error")]
29    /// Tried to convert data to UTF-8 that was not valid.
30    Utf8,
31    #[error("UTF-16 error")]
32    /// Tried to convert data to UTF-16 that was not valid.
33    Utf16,
34    #[error("Error parsing an integer: {}", _0)]
35    /// Tried to parse an integer that was not an integer.
36    ParseInt(std::num::ParseIntError),
37    #[error("Token error: {}", _0)]
38    /// The first error the server returned for the request. The response's
39    /// other errors and info messages are on it: see
40    /// [`TokenError::other_errors`] and [`TokenError::info_messages`].
41    Server(TokenError),
42    #[error("Error forming TLS connection: {}", _0)]
43    /// An error in the TLS handshake.
44    Tls(String),
45    #[cfg(any(all(unix, feature = "integrated-auth-gssapi"), doc))]
46    #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "integrated-auth-gssapi"))))]
47    /// An error from the GSSAPI library.
48    #[error("GSSAPI Error: {}", _0)]
49    Gssapi(String),
50    #[cfg(any(all(unix, feature = "sspi-rs"), doc))]
51    #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "sspi-rs"))))]
52    /// An error from the `sspi` (sspi-rs) library.
53    #[error("sspi-rs Error: {}", _0)]
54    SspiRs(String),
55    #[error(
56        "Server requested a connection to an alternative address: `{}:{}`",
57        host,
58        port
59    )]
60    /// Server requested a connection to an alternative address.
61    Routing {
62        /// The requested hostname
63        host: String,
64        /// The requested port.
65        port: u16,
66    },
67    #[error("BULK UPLOAD input failure: {0}")]
68    /// Invalid input in Bulk Upload
69    BulkInput(Cow<'static, str>),
70}
71
72impl Error {
73    /// True, if the error was caused by a deadlock.
74    pub fn is_deadlock(&self) -> bool {
75        self.code().map(|c| c == 1205).unwrap_or(false)
76    }
77
78    /// Returns the error code, if the error originates from the
79    /// server.
80    pub fn code(&self) -> Option<u32> {
81        match self {
82            Error::Server(e) => Some(e.code()),
83            _ => None,
84        }
85    }
86}
87
88impl From<uuid::Error> for Error {
89    fn from(e: uuid::Error) -> Self {
90        Self::Conversion(format!("Error converting a Guid value {}", e).into())
91    }
92}
93
94#[cfg(feature = "native-tls")]
95impl From<async_native_tls::Error> for Error {
96    fn from(v: async_native_tls::Error) -> Self {
97        Error::Tls(format!("{}", v))
98    }
99}
100
101#[cfg(feature = "vendored-openssl")]
102impl From<opentls::Error> for Error {
103    fn from(v: opentls::Error) -> Self {
104        Error::Tls(format!("{}", v))
105    }
106}
107
108impl From<Infallible> for Error {
109    fn from(_: Infallible) -> Self {
110        unreachable!()
111    }
112}
113
114impl From<io::Error> for Error {
115    fn from(err: io::Error) -> Error {
116        // One of ours carried through an `AsyncRead` comes back as itself
117        // instead of wrapped in a second I/O error.
118        if let Some(inner) = err.get_ref().and_then(|e| e.downcast_ref::<Error>()) {
119            return inner.clone();
120        }
121
122        Self::Io {
123            kind: err.kind(),
124            message: format!("{}", err),
125        }
126    }
127}
128
129impl From<std::num::ParseIntError> for Error {
130    fn from(err: std::num::ParseIntError) -> Error {
131        Error::ParseInt(err)
132    }
133}
134
135impl From<std::str::Utf8Error> for Error {
136    fn from(_: std::str::Utf8Error) -> Error {
137        Error::Utf8
138    }
139}
140
141impl From<std::string::FromUtf8Error> for Error {
142    fn from(_err: std::string::FromUtf8Error) -> Error {
143        Error::Utf8
144    }
145}
146
147impl From<std::string::FromUtf16Error> for Error {
148    fn from(_err: std::string::FromUtf16Error) -> Error {
149        Error::Utf16
150    }
151}
152
153impl From<connection_string::Error> for Error {
154    fn from(err: connection_string::Error) -> Error {
155        let err = Cow::Owned(format!("{}", err));
156        Error::Conversion(err)
157    }
158}
159
160#[cfg(all(unix, feature = "integrated-auth-gssapi"))]
161#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "integrated-auth-gssapi"))))]
162impl From<libgssapi::error::Error> for Error {
163    fn from(err: libgssapi::error::Error) -> Error {
164        Error::Gssapi(format!("{}", err))
165    }
166}
167
168#[cfg(all(unix, feature = "sspi-rs"))]
169#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "sspi-rs"))))]
170impl From<sspi::Error> for Error {
171    fn from(err: sspi::Error) -> Error {
172        Error::SspiRs(format!("{}", err))
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    fn token_error(code: u32) -> TokenError {
181        TokenError {
182            code,
183            state: 1,
184            class: 16,
185            message: "boom".to_string(),
186            server: "srv".into(),
187            procedure: "proc".into(),
188            line: 3,
189            related: None,
190        }
191    }
192
193    #[test]
194    fn code_and_is_deadlock() {
195        let deadlock = Error::Server(token_error(1205));
196        assert_eq!(deadlock.code(), Some(1205));
197        assert!(deadlock.is_deadlock());
198
199        let other = Error::Server(token_error(500));
200        assert_eq!(other.code(), Some(500));
201        assert!(!other.is_deadlock());
202
203        let non_server = Error::Utf8;
204        assert_eq!(non_server.code(), None);
205        assert!(!non_server.is_deadlock());
206    }
207
208    #[test]
209    fn display_variants() {
210        assert_eq!(
211            format!("{}", Error::Protocol("bad".into())),
212            "Protocol error: bad"
213        );
214        assert_eq!(
215            format!("{}", Error::Encoding("bad".into())),
216            "Encoding error: bad"
217        );
218        assert_eq!(
219            format!("{}", Error::Conversion("bad".into())),
220            "Conversion error: bad"
221        );
222        assert_eq!(format!("{}", Error::Utf8), "UTF-8 error");
223        assert_eq!(format!("{}", Error::Utf16), "UTF-16 error");
224        assert_eq!(
225            format!("{}", Error::BulkInput("bad".into())),
226            "BULK UPLOAD input failure: bad"
227        );
228
229        let routing = Error::Routing {
230            host: "host".to_string(),
231            port: 1234,
232        };
233        assert!(format!("{}", routing).contains("host:1234"));
234    }
235
236    #[test]
237    fn from_io_error() {
238        let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "eof");
239        let err: Error = io_err.into();
240        match err {
241            Error::Io { kind, message } => {
242                assert_eq!(kind, io::ErrorKind::UnexpectedEof);
243                assert!(message.contains("eof"));
244            }
245            _ => panic!("expected Io"),
246        }
247    }
248
249    #[test]
250    fn an_error_carried_through_an_io_error_comes_back_as_itself() {
251        let ours = Error::Io {
252            kind: io::ErrorKind::ConnectionReset,
253            message: "reset by peer".into(),
254        };
255        let io_err = io::Error::new(io::ErrorKind::BrokenPipe, ours.clone());
256        assert_eq!(Error::from(io_err), ours);
257
258        let ours = Error::Protocol("bad token".into());
259        let io_err = io::Error::other(ours.clone());
260        assert_eq!(Error::from(io_err), ours);
261    }
262
263    #[test]
264    fn from_parse_int_error() {
265        let parse_err = "not-a-number".parse::<i32>().unwrap_err();
266        let err: Error = parse_err.into();
267        assert!(matches!(err, Error::ParseInt(_)));
268    }
269
270    #[test]
271    #[allow(invalid_from_utf8)] // intentionally-invalid bytes to exercise the error path
272    fn from_utf8_and_utf16_errors() {
273        let utf8_err = String::from_utf8(vec![0xff, 0xfe]).unwrap_err();
274        assert!(matches!(Error::from(utf8_err), Error::Utf8));
275
276        let invalid: &[u8] = &[0xff, 0xfe];
277        let str_utf8 = std::str::from_utf8(invalid).unwrap_err();
278        assert!(matches!(Error::from(str_utf8), Error::Utf8));
279
280        let utf16_err = String::from_utf16(&[0xd800]).unwrap_err();
281        assert!(matches!(Error::from(utf16_err), Error::Utf16));
282    }
283
284    #[test]
285    fn from_uuid_error() {
286        let uuid_err = uuid::Uuid::parse_str("not-a-uuid").unwrap_err();
287        assert!(matches!(Error::from(uuid_err), Error::Conversion(_)));
288    }
289
290    #[test]
291    fn equality_between_errors() {
292        assert_eq!(Error::Utf8, Error::Utf8);
293        assert_ne!(Error::Utf8, Error::Utf16);
294    }
295
296    #[test]
297    fn from_connection_string_error() {
298        let cs_err = connection_string::Error::new("bad connection string");
299        let err: Error = cs_err.into();
300        match err {
301            Error::Conversion(msg) => assert!(msg.contains("bad connection string")),
302            _ => panic!("expected Conversion"),
303        }
304    }
305
306    #[cfg(all(unix, feature = "sspi-rs"))]
307    #[test]
308    fn from_sspi_error() {
309        let sspi_err = sspi::Error::new(sspi::ErrorKind::InternalError, "sspi boom");
310        assert!(matches!(Error::from(sspi_err), Error::SspiRs(_)));
311    }
312
313    #[cfg(all(unix, feature = "integrated-auth-gssapi"))]
314    #[test]
315    fn from_gssapi_error() {
316        let gss_err = libgssapi::error::Error {
317            major: libgssapi::error::MajorFlags::empty(),
318            minor: 0,
319        };
320        assert!(matches!(Error::from(gss_err), Error::Gssapi(_)));
321    }
322}