use axum::extract::ws::{Message, WebSocket};
use futures_util::stream::SplitSink;
use futures_util::SinkExt;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
pub struct WsWriter {
pub tx: mpsc::UnboundedSender<String>,
handle: JoinHandle<()>,
}
impl WsWriter {
pub fn abort(&self) {
self.handle.abort();
}
}
pub fn spawn_ws_writer(mut sink: SplitSink<WebSocket, Message>) -> WsWriter {
let (tx, mut rx) = mpsc::unbounded_channel::<String>();
let handle = tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
if sink.send(Message::Text(msg.into())).await.is_err() {
break;
}
}
});
WsWriter { tx, handle }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InboundFrame {
Text(String),
Ignore,
Close,
}
pub fn classify_frame(msg: Message) -> InboundFrame {
match msg {
Message::Text(t) => InboundFrame::Text(t.to_string()),
Message::Close(_) => InboundFrame::Close,
Message::Ping(_) | Message::Pong(_) | Message::Binary(_) => InboundFrame::Ignore,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_frame_matches_expected_variants() {
assert_eq!(
classify_frame(Message::Text("hi".into())),
InboundFrame::Text("hi".to_string())
);
assert_eq!(classify_frame(Message::Close(None)), InboundFrame::Close);
assert_eq!(
classify_frame(Message::Ping(Default::default())),
InboundFrame::Ignore
);
assert_eq!(
classify_frame(Message::Pong(Default::default())),
InboundFrame::Ignore
);
}
}