Skip to main content

tds_driver/tds/codec/token/
token_error.rs

1use crate::{tds::codec::TokenInfo, SqlReadBytes};
2use std::fmt;
3
4#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
5/// An error token returned from the server.
6pub struct TokenError {
7    /// ErrorCode
8    pub(crate) code: u32,
9    /// ErrorState (describing code)
10    pub(crate) state: u8,
11    /// The class (severity) of the error
12    pub(crate) class: u8,
13    /// The error message
14    pub(crate) message: String,
15    // `Box<str>` keeps `Error`, and so every `crate::Result` on the read
16    // path, small.
17    pub(crate) server: Box<str>,
18    pub(crate) procedure: Box<str>,
19    pub(crate) line: u32,
20    /// The response's other messages, attached only to the error a request
21    /// fails with. Boxed so `Error` stays small.
22    pub(crate) related: Option<Box<RelatedMessages>>,
23}
24
25/// The ERROR and INFO tokens of a response besides its first ERROR. SqlClient
26/// reports all of them too: in `SqlException.Errors`, warnings after errors.
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub(crate) struct RelatedMessages {
29    pub(crate) errors: Vec<TokenError>,
30    pub(crate) infos: Vec<TokenError>,
31}
32
33impl RelatedMessages {
34    pub(crate) fn is_empty(&self) -> bool {
35        self.errors.is_empty() && self.infos.is_empty()
36    }
37}
38
39impl From<&TokenInfo> for TokenError {
40    fn from(info: &TokenInfo) -> Self {
41        Self {
42            code: info.number,
43            state: info.state,
44            class: info.class,
45            message: info.message.clone(),
46            server: info.server.as_str().into(),
47            procedure: info.procedure.as_str().into(),
48            line: info.line,
49            related: None,
50        }
51    }
52}
53
54impl TokenError {
55    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
56    where
57        R: SqlReadBytes + Unpin,
58    {
59        let _length = src.read_u16_le().await? as usize;
60
61        let code = src.read_u32_le().await?;
62        let state = src.read_u8().await?;
63        let class = src.read_u8().await?;
64
65        let message = src.read_us_varchar().await?;
66        let server = src.read_b_varchar().await?;
67        let procedure = src.read_b_varchar().await?;
68
69        // MS-TDS 2.2.7.10: a 4-byte LONG since TDS 7.2, which LOGINACK
70        // requires. SqlClient always reads 4 bytes, also before LOGINACK.
71        let line = src.read_u32_le().await?;
72
73        let token = TokenError {
74            code,
75            state,
76            class,
77            message,
78            server: server.into(),
79            procedure: procedure.into(),
80            line,
81            related: None,
82        };
83
84        Ok(token)
85    }
86
87    /// The error code, see descriptions from [the manual].
88    ///
89    /// [the manual]: https://docs.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver15
90    pub fn code(&self) -> u32 {
91        self.code
92    }
93
94    /// The error state, used as a modifier to the error number.
95    pub fn state(&self) -> u8 {
96        self.state
97    }
98
99    /// The class (severity) of the error. A class of less than 10 indicates an
100    /// informational message.
101    pub fn class(&self) -> u8 {
102        self.class
103    }
104
105    /// The error message returned from the server.
106    pub fn message(&self) -> &str {
107        &self.message
108    }
109
110    /// The server name.
111    pub fn server(&self) -> &str {
112        &self.server
113    }
114
115    /// The name of the stored procedure causing the error.
116    pub fn procedure(&self) -> &str {
117        &self.procedure
118    }
119
120    /// The line number in the SQL batch or stored procedure that caused the
121    /// error. Line numbers begin at 1. If the line number is not applicable to
122    /// the message, the value is 0.
123    pub fn line(&self) -> u32 {
124        self.line
125    }
126
127    /// The ERROR tokens that followed this one in the same response, in
128    /// order. A batch keeps running after most errors, so later statements
129    /// can fail too. Empty on the entries themselves.
130    pub fn other_errors(&self) -> &[TokenError] {
131        self.related.as_deref().map_or(&[], |r| &r.errors)
132    }
133
134    /// The INFO messages of the same response, before and after this error,
135    /// in order: `PRINT` output, `RAISERROR` at severity 10 or less, and
136    /// server notices. [`code`](Self::code) is the message number.
137    pub fn info_messages(&self) -> &[TokenError] {
138        self.related.as_deref().map_or(&[], |r| &r.infos)
139    }
140}
141
142impl fmt::Display for TokenError {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(
145            f,
146            "'{}' on server {} executing {} on line {} (code: {}, state: {}, class: {})",
147            self.message, self.server, self.procedure, self.line, self.code, self.state, self.class
148        )
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    fn sample() -> TokenError {
157        TokenError {
158            code: 1205,
159            state: 2,
160            class: 13,
161            message: "deadlocked".to_string(),
162            server: "myserver".into(),
163            procedure: "myproc".into(),
164            line: 42,
165            related: None,
166        }
167    }
168
169    #[test]
170    fn accessors() {
171        let e = sample();
172        assert_eq!(e.code(), 1205);
173        assert_eq!(e.state(), 2);
174        assert_eq!(e.class(), 13);
175        assert_eq!(e.message(), "deadlocked");
176        assert_eq!(e.server(), "myserver");
177        assert_eq!(e.procedure(), "myproc");
178        assert_eq!(e.line(), 42);
179        assert!(e.other_errors().is_empty());
180        assert!(e.info_messages().is_empty());
181    }
182
183    #[test]
184    fn display_contains_all_fields() {
185        let rendered = format!("{}", sample());
186        assert_eq!(
187            rendered,
188            "'deadlocked' on server myserver executing myproc on line 42 (code: 1205, state: 2, class: 13)"
189        );
190    }
191
192    #[tokio::test]
193    async fn decode_reads_all_fields_with_four_byte_line_number() {
194        use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
195        use byteorder::{LittleEndian, WriteBytesExt};
196        use bytes::{BufMut, BytesMut};
197
198        fn write_us_varchar(buf: &mut Vec<u8>, s: &str) {
199            buf.write_u16::<LittleEndian>(s.encode_utf16().count() as u16)
200                .unwrap();
201            for u in s.encode_utf16() {
202                buf.write_u16::<LittleEndian>(u).unwrap();
203            }
204        }
205
206        fn write_b_varchar(buf: &mut Vec<u8>, s: &str) {
207            buf.push(s.encode_utf16().count() as u8);
208            for u in s.encode_utf16() {
209                buf.write_u16::<LittleEndian>(u).unwrap();
210            }
211        }
212
213        let mut body = Vec::new();
214        body.write_u32::<LittleEndian>(1205).unwrap(); // code
215        body.push(2); // state
216        body.push(13); // class
217        write_us_varchar(&mut body, "deadlocked");
218        write_b_varchar(&mut body, "myserver");
219        write_b_varchar(&mut body, "myproc");
220        body.write_u32::<LittleEndian>(42).unwrap(); // line
221
222        let mut buf = BytesMut::new();
223        buf.put_u16_le(body.len() as u16); // length prefix, ignored by decode
224        buf.put_slice(&body);
225
226        let decoded = TokenError::decode(&mut buf.into_sql_read_bytes())
227            .await
228            .unwrap();
229
230        assert_eq!(decoded, sample());
231    }
232
233    #[tokio::test]
234    async fn decode_reads_full_four_byte_line_number_on_tds72_plus() {
235        // 0x0001_0001 reads as 1 from 2 bytes, as 65537 from all 4.
236        use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
237        use bytes::{BufMut, BytesMut};
238
239        let mut body = BytesMut::new();
240        body.put_u32_le(1205); // code
241        body.put_u8(2); // state
242        body.put_u8(13); // class
243        body.put_u16_le(0); // message: us_varchar, length 0
244        body.put_u8(0); // server: b_varchar, length 0
245        body.put_u8(0); // procedure: b_varchar, length 0
246        body.put_u32_le(0x0001_0001); // line number, 4 bytes
247
248        let mut buf = BytesMut::new();
249        buf.put_u16_le(body.len() as u16); // length prefix, ignored by decode
250        buf.put_slice(&body);
251
252        let decoded = TokenError::decode(&mut buf.into_sql_read_bytes())
253            .await
254            .unwrap();
255
256        assert_eq!(decoded.line(), 0x0001_0001);
257    }
258}