use nutype::nutype;
#[nutype(derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, AsRef, Deref
))]
pub struct StatusCode(u16);
impl StatusCode {
#[inline]
#[must_use]
pub fn as_u16(&self) -> u16 {
self.into_inner()
}
#[inline]
#[must_use]
pub fn is_success(&self) -> bool {
let code = self.into_inner();
(200..400).contains(&code)
}
#[inline]
#[must_use]
pub fn is_error(&self) -> bool {
let code = self.into_inner();
(400..600).contains(&code)
}
#[inline]
#[must_use]
pub fn is_continuation(&self) -> bool {
let code = self.into_inner();
(300..400).contains(&code)
}
#[inline]
#[must_use]
pub fn is_informational(&self) -> bool {
let code = self.into_inner();
(100..200).contains(&code)
}
#[inline]
#[must_use]
pub fn is_greeting(&self) -> bool {
matches!(self.into_inner(), 200 | 201)
}
#[inline]
#[must_use]
pub fn requires_auth_credentials(&self) -> bool {
matches!(self.into_inner(), 381 | 480)
}
#[inline]
#[must_use]
pub fn is_auth_accepted(&self) -> bool {
self.into_inner() == 281
}
#[inline]
#[must_use]
pub fn is_article_missing(&self) -> bool {
self.into_inner() == 430
}
}
impl StatusCode {
#[inline]
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 3 {
return None;
}
let d0 = data[0].wrapping_sub(b'0');
let d1 = data[1].wrapping_sub(b'0');
let d2 = data[2].wrapping_sub(b'0');
if d0 > 9 || d1 > 9 || d2 > 9 {
return None;
}
let code = u16::from(d0) * 100 + u16::from(d1) * 10 + u16::from(d2);
Some(Self::new(code))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_code_categories() {
assert!(StatusCode::new(100).is_informational());
assert!(StatusCode::new(200).is_success());
assert!(StatusCode::new(381).is_success()); assert!(StatusCode::new(400).is_error());
assert!(StatusCode::new(500).is_error());
assert!(!StatusCode::new(200).is_error());
}
#[test]
fn test_status_code_parsing() {
assert_eq!(StatusCode::parse(b"200"), Some(StatusCode::new(200)));
assert_eq!(
StatusCode::parse(b"200 Ready\r\n"),
Some(StatusCode::new(200))
);
assert_eq!(
StatusCode::parse(b"381 Password required\r\n"),
Some(StatusCode::new(381))
);
assert_eq!(
StatusCode::parse(b"500 Error\r\n"),
Some(StatusCode::new(500))
);
assert_eq!(StatusCode::parse(b""), None);
assert_eq!(StatusCode::parse(b"XX"), None);
assert_eq!(StatusCode::parse(b"ABC Invalid\r\n"), None);
assert_eq!(StatusCode::parse(b"20"), None);
assert_eq!(StatusCode::parse(b"2X0 Error\r\n"), None);
}
#[test]
fn test_status_code_setup_helpers() {
assert!(StatusCode::new(200).is_greeting());
assert!(StatusCode::new(201).is_greeting());
assert!(!StatusCode::new(205).is_greeting());
assert!(StatusCode::new(381).requires_auth_credentials());
assert!(StatusCode::new(480).requires_auth_credentials());
assert!(!StatusCode::new(281).requires_auth_credentials());
assert!(StatusCode::new(281).is_auth_accepted());
assert!(!StatusCode::new(381).is_auth_accepted());
assert!(StatusCode::new(430).is_article_missing());
assert!(!StatusCode::new(400).is_article_missing());
}
#[test]
fn test_edge_cases() {
let utf8_response = "200 Привет мир\r\n".as_bytes();
assert_eq!(StatusCode::parse(utf8_response), Some(StatusCode::new(200)));
let with_null = b"200 Test\x00Message\r\n";
assert_eq!(StatusCode::parse(with_null), Some(StatusCode::new(200)));
assert_eq!(
StatusCode::parse(b"100 Info\r\n"),
Some(StatusCode::new(100))
);
assert_eq!(
StatusCode::parse(b"599 Error\r\n"),
Some(StatusCode::new(599))
);
}
}