nautilus_network/websocket/
types.rs1use std::{fmt::Debug, sync::Arc};
28
29use futures_util::stream::{SplitSink, SplitStream};
30
31use crate::{
32 error::SendError,
33 transport::{BoxedWsTransport, CloseFrame, Message},
34};
35
36pub(crate) type MessageWriter = SplitSink<BoxedWsTransport, Message>;
41
42pub type MessageReader = SplitStream<BoxedWsTransport>;
47
48pub type MessageHandler = Arc<dyn Fn(Message) + Send + Sync>;
55
56pub type EpochMessageHandler = Arc<dyn Fn(u64, Message) + Send + Sync>;
61
62pub type PingHandler = Arc<dyn Fn(Vec<u8>) + Send + Sync>;
64
65pub type EpochPingHandler = Arc<dyn Fn(u64, Vec<u8>) + Send + Sync>;
67
68pub type CloseHandler = Arc<dyn Fn(Option<CloseFrame>) -> bool + Send + Sync>;
74
75#[must_use]
80pub fn channel_message_handler() -> (
81 MessageHandler,
82 tokio::sync::mpsc::UnboundedReceiver<tokio_tungstenite::tungstenite::Message>,
83) {
84 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
85
86 let handler: MessageHandler = Arc::new(move |msg: Message| {
87 match tokio_tungstenite::tungstenite::Message::try_from(msg) {
88 Ok(legacy) => {
89 if let Err(e) = tx.send(legacy) {
90 log::debug!("Failed to send message to channel: {e}");
91 }
92 }
93 Err(e) => log::debug!("Dropping message that failed legacy conversion: {e}"),
94 }
95 });
96
97 (handler, rx)
98}
99
100#[must_use]
104pub fn channel_epoch_message_handler() -> (
105 EpochMessageHandler,
106 tokio::sync::mpsc::UnboundedReceiver<(u64, tokio_tungstenite::tungstenite::Message)>,
107) {
108 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
109
110 let handler: EpochMessageHandler =
111 Arc::new(
112 move |epoch, msg: Message| match tokio_tungstenite::tungstenite::Message::try_from(msg)
113 {
114 Ok(legacy) => {
115 if let Err(e) = tx.send((epoch, legacy)) {
116 log::debug!("Failed to send connection message to channel: {e}");
117 }
118 }
119 Err(e) => log::debug!("Dropping message that failed legacy conversion: {e}"),
120 },
121 );
122
123 (handler, rx)
124}
125
126pub(crate) enum WriterCommand {
128 Update(MessageWriter, tokio::sync::oneshot::Sender<u64>),
130 Send(Message),
132 Heartbeat(Message),
134 SendOnConnection {
136 message: Message,
137 connection_epoch: u64,
138 response_tx: tokio::sync::oneshot::Sender<Result<(), SendError>>,
139 },
140 SendPongOnConnection {
142 data: Vec<u8>,
143 connection_epoch: u64,
144 },
145}
146
147impl Debug for WriterCommand {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self {
150 Self::Update(_, _) => f.debug_tuple("Update").field(&"<writer>").finish(),
151 Self::Send(msg) => f.debug_tuple("Send").field(msg).finish(),
152 Self::Heartbeat(msg) => f.debug_tuple("Heartbeat").field(msg).finish(),
153 Self::SendOnConnection { message, .. } => {
154 f.debug_tuple("SendOnConnection").field(message).finish()
155 }
156 Self::SendPongOnConnection { data, .. } => {
157 f.debug_tuple("SendPongOnConnection").field(data).finish()
158 }
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use bytes::Bytes;
166 use rstest::rstest;
167 use tokio_tungstenite::tungstenite::Message as TgMessage;
168
169 use super::*;
170 use crate::RECONNECTED;
171
172 #[rstest]
173 fn channel_handler_drops_invalid_utf8_text_without_panic() {
174 let (handler, mut rx) = channel_message_handler();
175
176 handler(Message::Text(Bytes::from_static(&[0xFF, 0xFE])));
179 handler(Message::Binary(Bytes::from_static(b"ok")));
180
181 let received = rx.try_recv().expect("binary should arrive");
182 assert!(matches!(received, TgMessage::Binary(ref b) if b.as_ref() == b"ok"));
183 assert!(rx.try_recv().is_err(), "no further messages expected");
184 }
185
186 #[rstest]
187 fn channel_handler_forwards_valid_text() {
188 let (handler, mut rx) = channel_message_handler();
189 handler(Message::text("hello"));
190
191 let received = rx.try_recv().expect("text should arrive");
192 match received {
193 TgMessage::Text(t) => assert_eq!(t.as_str(), "hello"),
194 other => panic!("expected text, was {other:?}"),
195 }
196 }
197
198 #[rstest]
199 fn epoch_message_handler_forwards_epoch_and_text() {
200 let (handler, mut rx) = channel_epoch_message_handler();
201 handler(7, Message::text("hello"));
202
203 let (connection_epoch, received) = rx.try_recv().expect("text should arrive");
204 assert_eq!(connection_epoch, 7);
205
206 match received {
207 TgMessage::Text(text) => assert_eq!(text.as_str(), "hello"),
208 other => panic!("expected text, was {other:?}"),
209 }
210 }
211
212 #[rstest]
213 fn channel_handler_forwards_reconnected_text_to_rust_consumers() {
214 let (handler, mut rx) = channel_message_handler();
215 handler(Message::text(RECONNECTED));
216
217 let received = rx.try_recv().expect("reconnect text should arrive");
218 match received {
219 TgMessage::Text(t) => assert_eq!(t.as_str(), RECONNECTED),
220 other => panic!("expected text, was {other:?}"),
221 }
222 }
223
224 #[rstest]
225 fn writer_command_send_debug_includes_message() {
226 let cmd = WriterCommand::Send(Message::text("hi"));
227 let formatted = format!("{cmd:?}");
228 assert!(
229 formatted.starts_with("Send("),
230 "unexpected debug output: {formatted}"
231 );
232 assert!(
233 formatted.contains("Text"),
234 "debug output should retain the message variant: {formatted}"
235 );
236 assert!(
237 formatted.contains("hi"),
238 "debug output should retain the payload bytes: {formatted}"
239 );
240 }
241}