use bytes::Bytes;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::message::Message;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Error)]
#[repr(u32)]
#[non_exhaustive]
pub enum StatusCode {
#[error("Ok")]
#[default]
Ok = 0,
#[error("Eof")]
Eof = 1,
#[error("NoSuchFile")]
NoSuchFile = 2,
#[error("PermissionDenied")]
PermissionDenied = 3,
#[error("Failure")]
Failure = 4,
#[error("BadMessage")]
BadMessage = 5,
#[error("NoConnection")]
NoConnection = 6,
#[error("ConnectionLost")]
ConnectionLost = 7,
#[error("OpUnsupported")]
OpUnsupported = 8,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Status {
pub code: StatusCode,
pub error: Bytes,
pub language: Bytes,
}
impl Status {
pub fn is_ok(&self) -> bool {
self.code == StatusCode::Ok
}
pub fn is_err(&self) -> bool {
self.code != StatusCode::Ok
}
pub fn to_result<T>(self, value: T) -> Result<T, Self> {
if self.is_ok() {
Ok(value)
} else {
Err(self)
}
}
}
impl StatusCode {
pub fn to_status(self, msg: Bytes) -> Status {
let msg = if msg.is_empty() {
self.to_string().into()
} else {
msg
};
Status {
code: self,
error: msg,
language: "en".into(),
}
}
pub fn to_message(self, msg: Bytes) -> Message {
Message::Status(self.to_status(msg))
}
}
impl std::fmt::Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.error.is_empty() {
write!(f, "{}", self.code)
} else {
write!(
f,
"{}: {}",
self.code,
String::from_utf8_lossy(self.error.as_ref())
)
}
}
}
impl TryFrom<u32> for StatusCode {
type Error = u32;
fn try_from(value: u32) -> Result<Self, Self::Error> {
if value == Self::Ok as u32 {
Ok(Self::Ok)
} else if value == Self::Eof as u32 {
Ok(Self::Eof)
} else if value == Self::NoSuchFile as u32 {
Ok(Self::NoSuchFile)
} else if value == Self::PermissionDenied as u32 {
Ok(Self::PermissionDenied)
} else if value == Self::Failure as u32 {
Ok(Self::Failure)
} else if value == Self::BadMessage as u32 {
Ok(Self::BadMessage)
} else if value == Self::NoConnection as u32 {
Ok(Self::NoConnection)
} else if value == Self::ConnectionLost as u32 {
Ok(Self::ConnectionLost)
} else if value == Self::OpUnsupported as u32 {
Ok(Self::OpUnsupported)
} else {
Err(value)
}
}
}
impl std::error::Error for Status {}
#[cfg(test)]
mod test {
use bytes::Bytes;
use crate::{
message::test_utils::{encode_decode, fail_decode},
wire::Error,
};
use super::{Status, StatusCode};
const STATUS_VALID: &[u8] = b"\0\0\0\x01\0\0\0\x03eof\0\0\0\x02en";
#[test]
fn encode_success() {
for (code, encoded) in [
(StatusCode::Ok, b"\0\0\0\x00"),
(StatusCode::Eof, b"\0\0\0\x01"),
(StatusCode::NoSuchFile, b"\0\0\0\x02"),
(StatusCode::PermissionDenied, b"\0\0\0\x03"),
(StatusCode::Failure, b"\0\0\0\x04"),
(StatusCode::BadMessage, b"\0\0\0\x05"),
(StatusCode::NoConnection, b"\0\0\0\x06"),
(StatusCode::ConnectionLost, b"\0\0\0\x07"),
(StatusCode::OpUnsupported, b"\0\0\0\x08"),
] {
encode_decode(code, encoded);
}
encode_decode(
Status {
code: StatusCode::Eof,
error: Bytes::from_static(b"eof"),
language: Bytes::from_static(b"en"),
},
STATUS_VALID,
);
}
#[test]
fn decode_failure() {
for i in 0..STATUS_VALID.len() {
assert_eq!(
fail_decode::<Status>(&STATUS_VALID[..i]),
Error::NotEnoughData
);
}
}
}