Skip to main content

tiberius/
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    /// An error returned by the server.
39    Server(TokenError),
40    #[error("Error forming TLS connection: {}", _0)]
41    /// An error in the TLS handshake.
42    Tls(String),
43    #[cfg(any(all(unix, feature = "integrated-auth-gssapi"), doc))]
44    #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "integrated-auth-gssapi"))))]
45    /// An error from the GSSAPI library.
46    #[error("GSSAPI Error: {}", _0)]
47    Gssapi(String),
48    #[cfg(any(all(unix, feature = "sspi-rs"), doc))]
49    #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "sspi-rs"))))]
50    /// An error from the `sspi` (sspi-rs) library.
51    #[error("sspi-rs Error: {}", _0)]
52    SspiRs(String),
53    #[error(
54        "Server requested a connection to an alternative address: `{}:{}`",
55        host,
56        port
57    )]
58    /// Server requested a connection to an alternative address.
59    Routing {
60        /// The requested hostname
61        host: String,
62        /// The requested port.
63        port: u16,
64    },
65    #[error("BULK UPLOAD input failure: {0}")]
66    /// Invalid input in Bulk Upload
67    BulkInput(Cow<'static, str>),
68}
69
70impl Error {
71    /// True, if the error was caused by a deadlock.
72    pub fn is_deadlock(&self) -> bool {
73        self.code().map(|c| c == 1205).unwrap_or(false)
74    }
75
76    /// Returns the error code, if the error originates from the
77    /// server.
78    pub fn code(&self) -> Option<u32> {
79        match self {
80            Error::Server(e) => Some(e.code()),
81            _ => None,
82        }
83    }
84}
85
86impl From<uuid::Error> for Error {
87    fn from(e: uuid::Error) -> Self {
88        Self::Conversion(format!("Error converting a Guid value {}", e).into())
89    }
90}
91
92#[cfg(feature = "native-tls")]
93impl From<async_native_tls::Error> for Error {
94    fn from(v: async_native_tls::Error) -> Self {
95        Error::Tls(format!("{}", v))
96    }
97}
98
99#[cfg(feature = "vendored-openssl")]
100impl From<opentls::Error> for Error {
101    fn from(v: opentls::Error) -> Self {
102        Error::Tls(format!("{}", v))
103    }
104}
105
106impl From<Infallible> for Error {
107    fn from(_: Infallible) -> Self {
108        unreachable!()
109    }
110}
111
112impl From<io::Error> for Error {
113    fn from(err: io::Error) -> Error {
114        Self::Io {
115            kind: err.kind(),
116            message: format!("{}", err),
117        }
118    }
119}
120
121impl From<std::num::ParseIntError> for Error {
122    fn from(err: std::num::ParseIntError) -> Error {
123        Error::ParseInt(err)
124    }
125}
126
127impl From<std::str::Utf8Error> for Error {
128    fn from(_: std::str::Utf8Error) -> Error {
129        Error::Utf8
130    }
131}
132
133impl From<std::string::FromUtf8Error> for Error {
134    fn from(_err: std::string::FromUtf8Error) -> Error {
135        Error::Utf8
136    }
137}
138
139impl From<std::string::FromUtf16Error> for Error {
140    fn from(_err: std::string::FromUtf16Error) -> Error {
141        Error::Utf16
142    }
143}
144
145impl From<connection_string::Error> for Error {
146    fn from(err: connection_string::Error) -> Error {
147        let err = Cow::Owned(format!("{}", err));
148        Error::Conversion(err)
149    }
150}
151
152#[cfg(all(unix, feature = "integrated-auth-gssapi"))]
153#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "integrated-auth-gssapi"))))]
154impl From<libgssapi::error::Error> for Error {
155    fn from(err: libgssapi::error::Error) -> Error {
156        Error::Gssapi(format!("{}", err))
157    }
158}
159
160#[cfg(all(unix, feature = "sspi-rs"))]
161#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "sspi-rs"))))]
162impl From<sspi::Error> for Error {
163    fn from(err: sspi::Error) -> Error {
164        Error::SspiRs(format!("{}", err))
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    fn token_error(code: u32) -> TokenError {
173        TokenError {
174            code,
175            state: 1,
176            class: 16,
177            message: "boom".to_string(),
178            server: "srv".to_string(),
179            procedure: "proc".to_string(),
180            line: 3,
181        }
182    }
183
184    #[test]
185    fn code_and_is_deadlock() {
186        let deadlock = Error::Server(token_error(1205));
187        assert_eq!(deadlock.code(), Some(1205));
188        assert!(deadlock.is_deadlock());
189
190        let other = Error::Server(token_error(500));
191        assert_eq!(other.code(), Some(500));
192        assert!(!other.is_deadlock());
193
194        let non_server = Error::Utf8;
195        assert_eq!(non_server.code(), None);
196        assert!(!non_server.is_deadlock());
197    }
198
199    #[test]
200    fn display_variants() {
201        assert_eq!(
202            format!("{}", Error::Protocol("bad".into())),
203            "Protocol error: bad"
204        );
205        assert_eq!(
206            format!("{}", Error::Encoding("bad".into())),
207            "Encoding error: bad"
208        );
209        assert_eq!(
210            format!("{}", Error::Conversion("bad".into())),
211            "Conversion error: bad"
212        );
213        assert_eq!(format!("{}", Error::Utf8), "UTF-8 error");
214        assert_eq!(format!("{}", Error::Utf16), "UTF-16 error");
215        assert_eq!(
216            format!("{}", Error::BulkInput("bad".into())),
217            "BULK UPLOAD input failure: bad"
218        );
219
220        let routing = Error::Routing {
221            host: "host".to_string(),
222            port: 1234,
223        };
224        assert!(format!("{}", routing).contains("host:1234"));
225    }
226
227    #[test]
228    fn from_io_error() {
229        let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "eof");
230        let err: Error = io_err.into();
231        match err {
232            Error::Io { kind, message } => {
233                assert_eq!(kind, io::ErrorKind::UnexpectedEof);
234                assert!(message.contains("eof"));
235            }
236            _ => panic!("expected Io"),
237        }
238    }
239
240    #[test]
241    fn from_parse_int_error() {
242        let parse_err = "not-a-number".parse::<i32>().unwrap_err();
243        let err: Error = parse_err.into();
244        assert!(matches!(err, Error::ParseInt(_)));
245    }
246
247    #[test]
248    #[allow(invalid_from_utf8)] // intentionally-invalid bytes to exercise the error path
249    fn from_utf8_and_utf16_errors() {
250        let utf8_err = String::from_utf8(vec![0xff, 0xfe]).unwrap_err();
251        assert!(matches!(Error::from(utf8_err), Error::Utf8));
252
253        let invalid: &[u8] = &[0xff, 0xfe];
254        let str_utf8 = std::str::from_utf8(invalid).unwrap_err();
255        assert!(matches!(Error::from(str_utf8), Error::Utf8));
256
257        let utf16_err = String::from_utf16(&[0xd800]).unwrap_err();
258        assert!(matches!(Error::from(utf16_err), Error::Utf16));
259    }
260
261    #[test]
262    fn from_uuid_error() {
263        let uuid_err = uuid::Uuid::parse_str("not-a-uuid").unwrap_err();
264        assert!(matches!(Error::from(uuid_err), Error::Conversion(_)));
265    }
266
267    #[test]
268    fn equality_between_errors() {
269        assert_eq!(Error::Utf8, Error::Utf8);
270        assert_ne!(Error::Utf8, Error::Utf16);
271    }
272
273    #[test]
274    fn from_connection_string_error() {
275        let cs_err = connection_string::Error::new("bad connection string");
276        let err: Error = cs_err.into();
277        match err {
278            Error::Conversion(msg) => assert!(msg.contains("bad connection string")),
279            _ => panic!("expected Conversion"),
280        }
281    }
282
283    #[cfg(all(unix, feature = "sspi-rs"))]
284    #[test]
285    fn from_sspi_error() {
286        let sspi_err = sspi::Error::new(sspi::ErrorKind::InternalError, "sspi boom");
287        assert!(matches!(Error::from(sspi_err), Error::SspiRs(_)));
288    }
289
290    #[cfg(all(unix, feature = "integrated-auth-gssapi"))]
291    #[test]
292    fn from_gssapi_error() {
293        let gss_err = libgssapi::error::Error {
294            major: libgssapi::error::MajorFlags::empty(),
295            minor: 0,
296        };
297        assert!(matches!(Error::from(gss_err), Error::Gssapi(_)));
298    }
299}