rainier_websocket/
socket.rs1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
4use std::sync::Arc;
5
6use serde::Serialize;
7use tokio::sync::mpsc;
8
9use rainier_support::{Error, Result};
10
11use crate::message::Message;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct SocketId(u64);
16
17impl SocketId {
18 pub fn next() -> Self {
20 static NEXT: AtomicU64 = AtomicU64::new(1);
21 Self(NEXT.fetch_add(1, Ordering::Relaxed))
22 }
23
24 pub fn get(self) -> u64 {
26 self.0
27 }
28}
29
30impl std::fmt::Display for SocketId {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(f, "{}", self.0)
33 }
34}
35
36#[derive(Debug, Clone)]
48pub struct Socket {
49 id: SocketId,
50 path: Arc<str>,
51 params: Arc<Vec<(String, String)>>,
52 outbound: mpsc::UnboundedSender<Outbound>,
53 closed: Arc<AtomicBool>,
54}
55
56#[derive(Debug)]
58pub enum Outbound {
59 Send(Message),
61 Close(Option<String>),
63}
64
65impl Socket {
66 pub fn new(
71 id: SocketId,
72 path: impl Into<Arc<str>>,
73 params: Vec<(String, String)>,
74 outbound: mpsc::UnboundedSender<Outbound>,
75 ) -> Self {
76 Self {
77 id,
78 path: path.into(),
79 params: Arc::new(params),
80 outbound,
81 closed: Arc::new(AtomicBool::new(false)),
82 }
83 }
84
85 pub fn id(&self) -> SocketId {
87 self.id
88 }
89
90 pub fn path(&self) -> &str {
92 &self.path
93 }
94
95 pub fn param(&self, name: &str) -> Option<&str> {
98 self.params.iter().find(|(key, _)| key == name).map(|(_, value)| value.as_str())
99 }
100
101 pub fn parse_param<T>(&self, name: &str) -> Result<T>
106 where
107 T: std::str::FromStr,
108 T::Err: std::fmt::Display,
109 {
110 let raw = self
111 .param(name)
112 .ok_or_else(|| Error::internal(format!("the route captured no `{name}`")))?;
113
114 raw.parse::<T>()
115 .map_err(|e| Error::bad_request(format!("`{name}` in the path is invalid: {e}")))
116 }
117
118 pub fn params(&self) -> &[(String, String)] {
120 &self.params
121 }
122
123 pub fn send(&self, message: impl Into<Message>) -> Result<()> {
129 self.push(Outbound::Send(message.into()))
130 }
131
132 pub fn send_json(&self, value: &impl Serialize) -> Result<()> {
134 self.send(Message::json(value)?)
135 }
136
137 pub fn close(&self) -> Result<()> {
142 if self.closed.swap(true, Ordering::SeqCst) {
143 return Ok(());
144 }
145 self.push(Outbound::Close(None))
146 }
147
148 pub fn close_with(&self, reason: impl Into<String>) -> Result<()> {
150 if self.closed.swap(true, Ordering::SeqCst) {
151 return Ok(());
152 }
153 self.push(Outbound::Close(Some(reason.into())))
154 }
155
156 pub fn is_closed(&self) -> bool {
159 self.closed.load(Ordering::SeqCst) || self.outbound.is_closed()
160 }
161
162 fn push(&self, outbound: Outbound) -> Result<()> {
163 self.outbound.send(outbound).map_err(|_| {
164 Error::internal(format!("socket {} has gone away", self.id))
168 })
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn socket() -> (Socket, mpsc::UnboundedReceiver<Outbound>) {
177 let (tx, rx) = mpsc::unbounded_channel();
178 (Socket::new(SocketId::next(), "/ws/rooms/7", vec![("room".into(), "7".into())], tx), rx)
179 }
180
181 #[test]
182 fn a_send_is_queued_not_written() {
183 let (socket, mut rx) = socket();
184
185 socket.send("hello").unwrap();
186
187 match rx.try_recv().unwrap() {
188 Outbound::Send(Message::Text(text)) => assert_eq!(text, "hello"),
189 other => panic!("{other:?}"),
190 }
191 }
192
193 #[test]
194 fn route_parameters_come_through() {
195 let (socket, _rx) = socket();
196
197 assert_eq!(socket.param("room"), Some("7"));
198 assert_eq!(socket.parse_param::<u64>("room").unwrap(), 7);
199 assert_eq!(socket.param("nope"), None);
200 }
201
202 #[test]
203 fn a_parameter_that_will_not_parse_is_a_bad_request() {
204 let (tx, _rx) = mpsc::unbounded_channel();
205 let socket =
206 Socket::new(SocketId::next(), "/ws/rooms/x", vec![("room".into(), "x".into())], tx);
207
208 assert_eq!(socket.parse_param::<u64>("room").unwrap_err().status(), 400);
209 }
210
211 #[test]
212 fn closing_twice_is_not_an_error() {
213 let (socket, mut rx) = socket();
214
215 socket.close().unwrap();
216 socket.close().unwrap();
217
218 assert!(matches!(rx.try_recv(), Ok(Outbound::Close(None))));
219 assert!(rx.try_recv().is_err(), "only one close should be queued");
220 assert!(socket.is_closed());
221 }
222
223 #[test]
224 fn sending_to_a_socket_that_went_away_is_an_error_not_a_panic() {
225 let (socket, rx) = socket();
226 drop(rx);
227
228 let err = socket.send("hello").unwrap_err();
229 assert!(err.message().contains("gone away"), "{}", err.message());
230 assert!(socket.is_closed());
231 }
232
233 #[test]
234 fn ids_are_unique_and_ordered() {
235 let first = SocketId::next();
236 let second = SocketId::next();
237
238 assert!(second > first);
239 }
240}