1use axum::extract::ws::{Message as AxumMessage, WebSocket as AxumWebSocket};
2
3#[derive(Debug)]
5pub enum WsError {
6 SendError(String),
8 RecvError(String),
10}
11
12impl std::fmt::Display for WsError {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 match self {
15 WsError::SendError(err) => write!(f, "WebSocket send error: {}", err),
16 WsError::RecvError(err) => write!(f, "WebSocket receive error: {}", err),
17 }
18 }
19}
20
21impl std::error::Error for WsError {}
22
23pub struct WebSocket {
25 inner: AxumWebSocket,
26}
27
28impl WebSocket {
29 pub fn new(inner: AxumWebSocket) -> Self {
31 WebSocket { inner }
32 }
33
34 #[cfg_attr(mutants, mutants::skip)]
36 pub async fn send_text(&mut self, text: impl Into<String>) -> Result<(), WsError> {
37 self.inner
38 .send(AxumMessage::Text(text.into().into()))
39 .await
40 .map_err(|e| WsError::SendError(e.to_string()))
41 }
42
43 #[cfg_attr(mutants, mutants::skip)]
45 pub async fn send_html(&mut self, html_content: String) -> Result<(), WsError> {
46 self.send_text(html_content).await
47 }
48
49 #[cfg_attr(mutants, mutants::skip)]
55 pub async fn recv(&mut self) -> Option<Result<String, WsError>> {
56 loop {
57 return match self.inner.recv().await {
58 Some(Ok(AxumMessage::Text(t))) => Some(Ok(t.to_string())),
59 Some(Ok(AxumMessage::Binary(b))) => {
60 Some(Ok(String::from_utf8_lossy(&b).to_string()))
61 }
62 Some(Ok(AxumMessage::Ping(_) | AxumMessage::Pong(_))) => continue,
63 Some(Ok(AxumMessage::Close(_))) | None => None,
64 Some(Err(e)) => Some(Err(WsError::RecvError(e.to_string()))),
65 };
66 }
67 }
68}
69
70#[cfg(test)]
71#[allow(clippy::unwrap_used, clippy::expect_used)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_ws_error_display() {
77 let err1 = WsError::SendError("timeout".to_string());
78 let err2 = WsError::RecvError("closed".to_string());
79
80 assert_eq!(format!("{}", err1), "WebSocket send error: timeout");
81 assert_eq!(format!("{}", err2), "WebSocket receive error: closed");
82 }
83}