use crate::{sql_read_bytes::SqlReadBytes, time::DateTimeOffset, ColumnData};
pub(crate) async fn decode<R>(src: &mut R, len: usize) -> crate::Result<ColumnData<'static>>
where
R: SqlReadBytes + Unpin,
{
let rlen = src.read_u8().await?;
let dto = match rlen {
0 => ColumnData::DateTimeOffset(None),
_ => {
let time_len = rlen.checked_sub(5).ok_or_else(|| {
crate::Error::Protocol(
format!("datetimeoffset: invalid value length {rlen}").into(),
)
})?;
let dto = DateTimeOffset::decode(src, len, time_len).await?;
ColumnData::DateTimeOffset(Some(dto))
}
};
Ok(dto)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::BytesMut;
#[tokio::test]
async fn rejects_underlength_value_instead_of_panicking() {
let mut buf = BytesMut::new();
buf.extend_from_slice(&[4u8]);
let err = decode(&mut buf.into_sql_read_bytes(), 8)
.await
.expect_err("rlen < 5 must be rejected");
assert!(matches!(err, crate::Error::Protocol(_)));
}
#[cfg(all(feature = "tds73", feature = "time"))]
fn wire_bytes(dto: DateTimeOffset) -> BytesMut {
use crate::tds::codec::Encode;
let mut inner = BytesMut::new();
dto.encode(&mut inner).unwrap();
let mut buf = BytesMut::new();
buf.extend_from_slice(&[inner.len() as u8]);
buf.extend_from_slice(&inner);
buf
}
#[cfg(all(feature = "tds73", feature = "time"))]
#[tokio::test]
async fn rejects_unrepresentable_offset_conversion_instead_of_panicking() {
use crate::tds::time::{Date, DateTime2, Time};
use crate::FromSql;
let dto = DateTimeOffset::new(
DateTime2::new(Date::new(0x00ff_ffff), Time::new(828_000_000_000, 7)),
840,
);
let buf = wire_bytes(dto);
let column = decode(&mut buf.into_sql_read_bytes(), 7).await.unwrap();
let res = <time::OffsetDateTime as FromSql>::from_sql(&column);
assert!(
matches!(res, Err(crate::Error::Protocol(_))),
"expected a protocol error, got {res:?}"
);
}
#[cfg(all(feature = "tds73", feature = "time"))]
#[tokio::test]
async fn rejects_out_of_range_offset_field() {
use crate::tds::time::{Date, DateTime2, Time};
use crate::FromSql;
let dto = DateTimeOffset::new(DateTime2::new(Date::new(730_000), Time::new(0, 7)), 2000);
let buf = wire_bytes(dto);
let column = decode(&mut buf.into_sql_read_bytes(), 7).await.unwrap();
let res = <time::OffsetDateTime as FromSql>::from_sql(&column);
assert!(
matches!(res, Err(crate::Error::Protocol(_))),
"expected a protocol error, got {res:?}"
);
}
}