use crate::{tds::codec::FeatureLevel, SqlReadBytes};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub struct TokenError {
pub(crate) code: u32,
pub(crate) state: u8,
pub(crate) class: u8,
pub(crate) message: String,
pub(crate) server: String,
pub(crate) procedure: String,
pub(crate) line: u32,
}
impl TokenError {
pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
where
R: SqlReadBytes + Unpin,
{
let _length = src.read_u16_le().await? as usize;
let code = src.read_u32_le().await?;
let state = src.read_u8().await?;
let class = src.read_u8().await?;
let message = src.read_us_varchar().await?;
let server = src.read_b_varchar().await?;
let procedure = src.read_b_varchar().await?;
let line = if src.context().version() >= FeatureLevel::SqlServer2005 {
src.read_u32_le().await?
} else {
src.read_u16_le().await? as u32
};
let token = TokenError {
code,
state,
class,
message,
server,
procedure,
line,
};
Ok(token)
}
pub fn code(&self) -> u32 {
self.code
}
pub fn state(&self) -> u8 {
self.state
}
pub fn class(&self) -> u8 {
self.class
}
pub fn message(&self) -> &str {
&self.message
}
pub fn server(&self) -> &str {
&self.server
}
pub fn procedure(&self) -> &str {
&self.procedure
}
pub fn line(&self) -> u32 {
self.line
}
}
impl fmt::Display for TokenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"'{}' on server {} executing {} on line {} (code: {}, state: {}, class: {})",
self.message, self.server, self.procedure, self.line, self.code, self.state, self.class
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> TokenError {
TokenError {
code: 1205,
state: 2,
class: 13,
message: "deadlocked".to_string(),
server: "myserver".to_string(),
procedure: "myproc".to_string(),
line: 42,
}
}
#[test]
fn accessors() {
let e = sample();
assert_eq!(e.code(), 1205);
assert_eq!(e.state(), 2);
assert_eq!(e.class(), 13);
assert_eq!(e.message(), "deadlocked");
assert_eq!(e.server(), "myserver");
assert_eq!(e.procedure(), "myproc");
assert_eq!(e.line(), 42);
}
#[test]
fn display_contains_all_fields() {
let rendered = format!("{}", sample());
assert_eq!(
rendered,
"'deadlocked' on server myserver executing myproc on line 42 (code: 1205, state: 2, class: 13)"
);
}
#[tokio::test]
async fn decode_reads_all_fields_with_four_byte_line_number() {
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use byteorder::{LittleEndian, WriteBytesExt};
use bytes::{BufMut, BytesMut};
fn write_us_varchar(buf: &mut Vec<u8>, s: &str) {
buf.write_u16::<LittleEndian>(s.encode_utf16().count() as u16)
.unwrap();
for u in s.encode_utf16() {
buf.write_u16::<LittleEndian>(u).unwrap();
}
}
fn write_b_varchar(buf: &mut Vec<u8>, s: &str) {
buf.push(s.encode_utf16().count() as u8);
for u in s.encode_utf16() {
buf.write_u16::<LittleEndian>(u).unwrap();
}
}
let mut body = Vec::new();
body.write_u32::<LittleEndian>(1205).unwrap(); body.push(2); body.push(13); write_us_varchar(&mut body, "deadlocked");
write_b_varchar(&mut body, "myserver");
write_b_varchar(&mut body, "myproc");
body.write_u32::<LittleEndian>(42).unwrap();
let mut buf = BytesMut::new();
buf.put_u16_le(body.len() as u16); buf.put_slice(&body);
let decoded = TokenError::decode(&mut buf.into_sql_read_bytes())
.await
.unwrap();
assert_eq!(decoded, sample());
}
#[tokio::test]
async fn decode_reads_full_four_byte_line_number_on_tds72_plus() {
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::{BufMut, BytesMut};
let mut body = BytesMut::new();
body.put_u32_le(1205); body.put_u8(2); body.put_u8(13); body.put_u16_le(0); body.put_u8(0); body.put_u8(0); body.put_u32_le(0x0001_0001);
let mut buf = BytesMut::new();
buf.put_u16_le(body.len() as u16); buf.put_slice(&body);
let decoded = TokenError::decode(&mut buf.into_sql_read_bytes())
.await
.unwrap();
assert_eq!(decoded.line(), 0x0001_0001);
}
}