1use 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
36pub fn instance_id() -> &'static str {
48 static INSTANCE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
49
50 INSTANCE.get_or_init(|| {
51 let micros = std::time::SystemTime::now()
52 .duration_since(std::time::UNIX_EPOCH)
53 .map(|since| since.as_micros())
54 .unwrap_or_default();
55
56 format!("{micros:x}")
57 })
58}
59
60pub fn socket_from_identity(identity: &str) -> Option<SocketId> {
74 let (instance, id) = identity.rsplit_once('.')?;
75
76 if instance != instance_id() {
77 return None;
78 }
79
80 id.parse().ok().map(SocketId)
81}
82
83#[derive(Debug, Clone)]
95pub struct Socket {
96 id: SocketId,
97 path: Arc<str>,
98 params: Arc<Vec<(String, String)>>,
99 outbound: mpsc::UnboundedSender<Outbound>,
100 closed: Arc<AtomicBool>,
101}
102
103#[derive(Debug)]
105pub enum Outbound {
106 Send(Message),
108 Close(Option<String>),
110}
111
112impl Socket {
113 pub fn new(
118 id: SocketId,
119 path: impl Into<Arc<str>>,
120 params: Vec<(String, String)>,
121 outbound: mpsc::UnboundedSender<Outbound>,
122 ) -> Self {
123 Self {
124 id,
125 path: path.into(),
126 params: Arc::new(params),
127 outbound,
128 closed: Arc::new(AtomicBool::new(false)),
129 }
130 }
131
132 pub fn id(&self) -> SocketId {
134 self.id
135 }
136
137 pub fn identity(&self) -> String {
153 format!("{}.{}", instance_id(), self.id.get())
154 }
155
156 pub fn path(&self) -> &str {
158 &self.path
159 }
160
161 pub fn param(&self, name: &str) -> Option<&str> {
164 self.params.iter().find(|(key, _)| key == name).map(|(_, value)| value.as_str())
165 }
166
167 pub fn parse_param<T>(&self, name: &str) -> Result<T>
172 where
173 T: std::str::FromStr,
174 T::Err: std::fmt::Display,
175 {
176 let raw = self
177 .param(name)
178 .ok_or_else(|| Error::internal(format!("the route captured no `{name}`")))?;
179
180 raw.parse::<T>()
181 .map_err(|e| Error::bad_request(format!("`{name}` in the path is invalid: {e}")))
182 }
183
184 pub fn params(&self) -> &[(String, String)] {
186 &self.params
187 }
188
189 pub fn send(&self, message: impl Into<Message>) -> Result<()> {
195 self.push(Outbound::Send(message.into()))
196 }
197
198 pub fn send_json(&self, value: &impl Serialize) -> Result<()> {
200 self.send(Message::json(value)?)
201 }
202
203 pub fn close(&self) -> Result<()> {
208 if self.closed.swap(true, Ordering::SeqCst) {
209 return Ok(());
210 }
211 self.push(Outbound::Close(None))
212 }
213
214 pub fn close_with(&self, reason: impl Into<String>) -> Result<()> {
216 if self.closed.swap(true, Ordering::SeqCst) {
217 return Ok(());
218 }
219 self.push(Outbound::Close(Some(reason.into())))
220 }
221
222 pub fn is_closed(&self) -> bool {
225 self.closed.load(Ordering::SeqCst) || self.outbound.is_closed()
226 }
227
228 fn push(&self, outbound: Outbound) -> Result<()> {
229 self.outbound.send(outbound).map_err(|_| {
230 Error::internal(format!("socket {} has gone away", self.id))
234 })
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 fn socket() -> (Socket, mpsc::UnboundedReceiver<Outbound>) {
243 let (tx, rx) = mpsc::unbounded_channel();
244 (Socket::new(SocketId::next(), "/ws/rooms/7", vec![("room".into(), "7".into())], tx), rx)
245 }
246
247 #[test]
248 fn an_identity_is_the_process_and_the_socket() {
249 let (first, _rx) = socket();
250 let (second, _rx2) = socket();
251
252 assert_ne!(first.identity(), second.identity(), "two sockets, two identities");
253
254 assert!(first.identity().ends_with(&format!(".{}", first.id().get())));
258 assert_eq!(
259 first.identity().split('.').next(),
260 second.identity().split('.').next(),
261 "one process, one instance id"
262 );
263 }
264
265 #[test]
266 fn a_send_is_queued_not_written() {
267 let (socket, mut rx) = socket();
268
269 socket.send("hello").unwrap();
270
271 match rx.try_recv().unwrap() {
272 Outbound::Send(Message::Text(text)) => assert_eq!(text, "hello"),
273 other => panic!("{other:?}"),
274 }
275 }
276
277 #[test]
278 fn route_parameters_come_through() {
279 let (socket, _rx) = socket();
280
281 assert_eq!(socket.param("room"), Some("7"));
282 assert_eq!(socket.parse_param::<u64>("room").unwrap(), 7);
283 assert_eq!(socket.param("nope"), None);
284 }
285
286 #[test]
287 fn a_parameter_that_will_not_parse_is_a_bad_request() {
288 let (tx, _rx) = mpsc::unbounded_channel();
289 let socket =
290 Socket::new(SocketId::next(), "/ws/rooms/x", vec![("room".into(), "x".into())], tx);
291
292 assert_eq!(socket.parse_param::<u64>("room").unwrap_err().status(), 400);
293 }
294
295 #[test]
296 fn closing_twice_is_not_an_error() {
297 let (socket, mut rx) = socket();
298
299 socket.close().unwrap();
300 socket.close().unwrap();
301
302 assert!(matches!(rx.try_recv(), Ok(Outbound::Close(None))));
303 assert!(rx.try_recv().is_err(), "only one close should be queued");
304 assert!(socket.is_closed());
305 }
306
307 #[test]
308 fn sending_to_a_socket_that_went_away_is_an_error_not_a_panic() {
309 let (socket, rx) = socket();
310 drop(rx);
311
312 let err = socket.send("hello").unwrap_err();
313 assert!(err.message().contains("gone away"), "{}", err.message());
314 assert!(socket.is_closed());
315 }
316
317 #[test]
318 fn ids_are_unique_and_ordered() {
319 let first = SocketId::next();
320 let second = SocketId::next();
321
322 assert!(second > first);
323 }
324}