use std::fmt::Display;
use async_trait::async_trait;
use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::tungstenite::Message;
use crate::error::FlowcatError;
use crate::transport::socket::MediaSocket;
use crate::types::WsIn;
pub struct WsMediaTransport<S> {
socket: S,
}
impl<S> WsMediaTransport<S> {
pub fn new(socket: S) -> Self {
Self { socket }
}
pub fn into_inner(self) -> S {
self.socket
}
}
type ClientSocket =
tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
impl WsMediaTransport<ClientSocket> {
pub async fn connect(url: &str) -> Result<Self, FlowcatError> {
let (socket, _resp) = tokio_tungstenite::connect_async(url)
.await
.map_err(|e| FlowcatError::Transport(format!("connect_async({url}): {e}")))?;
Ok(Self::new(socket))
}
}
#[async_trait]
impl<S, E> MediaSocket for WsMediaTransport<S>
where
S: futures_util::Stream<Item = Result<Message, E>>
+ futures_util::Sink<Message, Error = E>
+ Unpin
+ Send,
E: Display + Send,
{
async fn recv(&mut self) -> Option<WsIn> {
loop {
match self.socket.next().await {
Some(Ok(Message::Text(t))) => return Some(WsIn::Text(t.as_str().to_owned())),
Some(Ok(Message::Binary(b))) => return Some(WsIn::Binary(b.to_vec())),
Some(Ok(Message::Close(_))) => return Some(WsIn::Close),
Some(Ok(Message::Ping(_)))
| Some(Ok(Message::Pong(_)))
| Some(Ok(Message::Frame(_))) => continue,
Some(Err(_)) => return Some(WsIn::Close),
None => return None,
}
}
}
async fn send_text(&mut self, s: String) -> Result<(), FlowcatError> {
self.socket
.send(Message::text(s))
.await
.map_err(|e| FlowcatError::Transport(format!("send_text: {e}")))
}
async fn send_binary(&mut self, b: Vec<u8>) -> Result<(), FlowcatError> {
self.socket
.send(Message::binary(b))
.await
.map_err(|e| FlowcatError::Transport(format!("send_binary: {e}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::stream;
use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};
struct MockSocket {
inbound: stream::Iter<std::vec::IntoIter<Result<Message, String>>>,
sent: std::sync::Arc<std::sync::Mutex<VecDeque<Message>>>,
}
impl MockSocket {
fn new(
inbound: Vec<Result<Message, String>>,
) -> (Self, std::sync::Arc<std::sync::Mutex<VecDeque<Message>>>) {
let sent = std::sync::Arc::new(std::sync::Mutex::new(VecDeque::new()));
(
Self {
inbound: stream::iter(inbound),
sent: sent.clone(),
},
sent,
)
}
}
impl futures_util::Stream for MockSocket {
type Item = Result<Message, String>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.inbound).poll_next(cx)
}
}
impl futures_util::Sink<Message> for MockSocket {
type Error = String;
fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), String>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), String> {
self.sent.lock().unwrap().push_back(item);
Ok(())
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), String>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), String>> {
Poll::Ready(Ok(()))
}
}
#[tokio::test]
async fn recv_maps_text_binary_close_and_skips_keepalives() {
let inbound = vec![
Ok(Message::Ping(bytes::Bytes::from_static(b"hb"))), Ok(Message::text("hello")),
Ok(Message::Pong(bytes::Bytes::new())), Ok(Message::binary(vec![1u8, 2, 3])),
Ok(Message::Close(None)),
];
let (mock, _sent) = MockSocket::new(inbound);
let mut t = WsMediaTransport::new(mock);
assert_eq!(t.recv().await, Some(WsIn::Text("hello".to_string())));
assert_eq!(t.recv().await, Some(WsIn::Binary(vec![1, 2, 3])));
assert_eq!(t.recv().await, Some(WsIn::Close));
assert_eq!(t.recv().await, None);
}
#[tokio::test]
async fn recv_treats_stream_error_as_close() {
let inbound = vec![Err("boom".to_string()), Ok(Message::text("unreached"))];
let (mock, _sent) = MockSocket::new(inbound);
let mut t = WsMediaTransport::new(mock);
assert_eq!(t.recv().await, Some(WsIn::Close));
}
#[tokio::test]
async fn send_text_and_binary_emit_matching_messages() {
let (mock, sent) = MockSocket::new(vec![]);
let mut t = WsMediaTransport::new(mock);
t.send_text("ping".to_string()).await.unwrap();
t.send_binary(vec![9, 8, 7]).await.unwrap();
let q = sent.lock().unwrap();
match &q[0] {
Message::Text(s) => assert_eq!(s.as_str(), "ping"),
other => panic!("expected Text, got {other:?}"),
}
match &q[1] {
Message::Binary(b) => assert_eq!(b.as_ref(), &[9u8, 8, 7]),
other => panic!("expected Binary, got {other:?}"),
}
}
}