flowcat_core/transport/
ws_media.rs1use std::fmt::Display;
13
14use async_trait::async_trait;
15use futures_util::{SinkExt, StreamExt};
16use tokio_tungstenite::tungstenite::Message;
17
18use crate::error::FlowcatError;
19use crate::transport::socket::MediaSocket;
20use crate::types::WsIn;
21
22pub struct WsMediaTransport<S> {
33 socket: S,
34}
35
36impl<S> WsMediaTransport<S> {
37 pub fn new(socket: S) -> Self {
40 Self { socket }
41 }
42
43 pub fn into_inner(self) -> S {
45 self.socket
46 }
47}
48
49type ClientSocket =
51 tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
52
53impl WsMediaTransport<ClientSocket> {
54 pub async fn connect(url: &str) -> Result<Self, FlowcatError> {
59 let (socket, _resp) = tokio_tungstenite::connect_async(url)
60 .await
61 .map_err(|e| FlowcatError::Transport(format!("connect_async({url}): {e}")))?;
62 Ok(Self::new(socket))
63 }
64}
65
66#[async_trait]
67impl<S, E> MediaSocket for WsMediaTransport<S>
68where
69 S: futures_util::Stream<Item = Result<Message, E>>
70 + futures_util::Sink<Message, Error = E>
71 + Unpin
72 + Send,
73 E: Display + Send,
74{
75 async fn recv(&mut self) -> Option<WsIn> {
76 loop {
80 match self.socket.next().await {
81 Some(Ok(Message::Text(t))) => return Some(WsIn::Text(t.as_str().to_owned())),
82 Some(Ok(Message::Binary(b))) => return Some(WsIn::Binary(b.to_vec())),
83 Some(Ok(Message::Close(_))) => return Some(WsIn::Close),
84 Some(Ok(Message::Ping(_)))
86 | Some(Ok(Message::Pong(_)))
87 | Some(Ok(Message::Frame(_))) => continue,
88 Some(Err(_)) => return Some(WsIn::Close),
91 None => return None,
92 }
93 }
94 }
95
96 async fn send_text(&mut self, s: String) -> Result<(), FlowcatError> {
97 self.socket
98 .send(Message::text(s))
99 .await
100 .map_err(|e| FlowcatError::Transport(format!("send_text: {e}")))
101 }
102
103 async fn send_binary(&mut self, b: Vec<u8>) -> Result<(), FlowcatError> {
104 self.socket
105 .send(Message::binary(b))
106 .await
107 .map_err(|e| FlowcatError::Transport(format!("send_binary: {e}")))
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use futures_util::stream;
115 use std::collections::VecDeque;
116 use std::pin::Pin;
117 use std::task::{Context, Poll};
118
119 struct MockSocket {
123 inbound: stream::Iter<std::vec::IntoIter<Result<Message, String>>>,
124 sent: std::sync::Arc<std::sync::Mutex<VecDeque<Message>>>,
125 }
126
127 impl MockSocket {
128 fn new(
129 inbound: Vec<Result<Message, String>>,
130 ) -> (Self, std::sync::Arc<std::sync::Mutex<VecDeque<Message>>>) {
131 let sent = std::sync::Arc::new(std::sync::Mutex::new(VecDeque::new()));
132 (
133 Self {
134 inbound: stream::iter(inbound),
135 sent: sent.clone(),
136 },
137 sent,
138 )
139 }
140 }
141
142 impl futures_util::Stream for MockSocket {
143 type Item = Result<Message, String>;
144 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
145 Pin::new(&mut self.inbound).poll_next(cx)
146 }
147 }
148
149 impl futures_util::Sink<Message> for MockSocket {
150 type Error = String;
151 fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), String>> {
152 Poll::Ready(Ok(()))
153 }
154 fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), String> {
155 self.sent.lock().unwrap().push_back(item);
156 Ok(())
157 }
158 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), String>> {
159 Poll::Ready(Ok(()))
160 }
161 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), String>> {
162 Poll::Ready(Ok(()))
163 }
164 }
165
166 #[tokio::test]
167 async fn recv_maps_text_binary_close_and_skips_keepalives() {
168 let inbound = vec![
169 Ok(Message::Ping(bytes::Bytes::from_static(b"hb"))), Ok(Message::text("hello")),
171 Ok(Message::Pong(bytes::Bytes::new())), Ok(Message::binary(vec![1u8, 2, 3])),
173 Ok(Message::Close(None)),
174 ];
175 let (mock, _sent) = MockSocket::new(inbound);
176 let mut t = WsMediaTransport::new(mock);
177
178 assert_eq!(t.recv().await, Some(WsIn::Text("hello".to_string())));
179 assert_eq!(t.recv().await, Some(WsIn::Binary(vec![1, 2, 3])));
180 assert_eq!(t.recv().await, Some(WsIn::Close));
181 assert_eq!(t.recv().await, None);
183 }
184
185 #[tokio::test]
186 async fn recv_treats_stream_error_as_close() {
187 let inbound = vec![Err("boom".to_string()), Ok(Message::text("unreached"))];
188 let (mock, _sent) = MockSocket::new(inbound);
189 let mut t = WsMediaTransport::new(mock);
190 assert_eq!(t.recv().await, Some(WsIn::Close));
191 }
192
193 #[tokio::test]
194 async fn send_text_and_binary_emit_matching_messages() {
195 let (mock, sent) = MockSocket::new(vec![]);
196 let mut t = WsMediaTransport::new(mock);
197
198 t.send_text("ping".to_string()).await.unwrap();
199 t.send_binary(vec![9, 8, 7]).await.unwrap();
200
201 let q = sent.lock().unwrap();
202 match &q[0] {
203 Message::Text(s) => assert_eq!(s.as_str(), "ping"),
204 other => panic!("expected Text, got {other:?}"),
205 }
206 match &q[1] {
207 Message::Binary(b) => assert_eq!(b.as_ref(), &[9u8, 8, 7]),
208 other => panic!("expected Binary, got {other:?}"),
209 }
210 }
211}