1use crate::routing::path::{
2 join_paths, match_path_params, match_path_shape, route_shape_key, validate_route_path,
3};
4use crate::{
5 BootError, BootRequest, BoxFuture, ExecutionContext, ExecutionInterceptor, Guard, HttpMethod,
6 Result,
7};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::BTreeMap;
11use std::future::Future;
12use std::ops::Deref;
13use std::sync::Arc;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct WebSocketMessage {
18 pub event: String,
19 #[serde(default)]
20 pub data: Value,
21}
22
23impl WebSocketMessage {
24 pub fn new(event: impl Into<String>, data: impl Into<Value>) -> Self {
25 Self {
26 event: event.into(),
27 data: data.into(),
28 }
29 }
30
31 pub fn event(&self) -> &str {
32 &self.event
33 }
34
35 pub fn data(&self) -> &Value {
36 &self.data
37 }
38
39 pub fn text(event: impl Into<String>, data: impl Into<String>) -> Self {
40 Self::new(event, Value::String(data.into()))
41 }
42
43 pub fn json<T>(event: impl Into<String>, data: &T) -> Result<Self>
44 where
45 T: Serialize,
46 {
47 Ok(Self::new(
48 event,
49 serde_json::to_value(data).map_err(|err| BootError::Internal(err.to_string()))?,
50 ))
51 }
52}
53
54pub trait IntoWebSocketReply {
56 fn into_websocket_reply(self) -> Option<WebSocketMessage>;
57}
58
59impl IntoWebSocketReply for WebSocketMessage {
60 fn into_websocket_reply(self) -> Option<WebSocketMessage> {
61 Some(self)
62 }
63}
64
65impl IntoWebSocketReply for Option<WebSocketMessage> {
66 fn into_websocket_reply(self) -> Option<WebSocketMessage> {
67 self
68 }
69}
70
71impl IntoWebSocketReply for () {
72 fn into_websocket_reply(self) -> Option<WebSocketMessage> {
73 None
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct WebSocketContext {
80 pub request: BootRequest,
81 pub gateway_path: String,
82 pub event: String,
83 pub module_name: Option<String>,
84 execution_context: ExecutionContext,
85}
86
87impl WebSocketContext {
88 fn new(gateway: &WebSocketGatewayDefinition, request: BootRequest, event: &str) -> Self {
89 let gateway_path = gateway.path.clone();
90 let event = event.to_string();
91 let module_name = gateway.module_name.clone();
92 let execution_context = ExecutionContext::websocket(
93 request.clone(),
94 gateway_path.clone(),
95 event.clone(),
96 module_name.clone(),
97 );
98 Self {
99 request,
100 gateway_path,
101 event,
102 module_name,
103 execution_context,
104 }
105 }
106
107 pub fn execution_context(&self) -> &ExecutionContext {
108 &self.execution_context
109 }
110
111 pub fn into_execution_context(self) -> ExecutionContext {
112 self.execution_context
113 }
114}
115
116impl Deref for WebSocketContext {
117 type Target = ExecutionContext;
118
119 fn deref(&self) -> &Self::Target {
120 self.execution_context()
121 }
122}
123
124pub trait WebSocketPipe: Send + Sync + 'static {
126 fn transform(&self, message: WebSocketMessage) -> BoxFuture<'static, Result<WebSocketMessage>>;
127}
128
129impl<F, Fut> WebSocketPipe for F
130where
131 F: Fn(WebSocketMessage) -> Fut + Send + Sync + 'static,
132 Fut: Future<Output = Result<WebSocketMessage>> + Send + 'static,
133{
134 fn transform(&self, message: WebSocketMessage) -> BoxFuture<'static, Result<WebSocketMessage>> {
135 Box::pin(self(message))
136 }
137}
138
139pub trait WebSocketGuard: Send + Sync + 'static {
141 fn can_activate(&self, context: WebSocketContext) -> BoxFuture<'static, Result<bool>>;
142}
143
144impl<F, Fut> WebSocketGuard for F
145where
146 F: Fn(WebSocketContext) -> Fut + Send + Sync + 'static,
147 Fut: Future<Output = Result<bool>> + Send + 'static,
148{
149 fn can_activate(&self, context: WebSocketContext) -> BoxFuture<'static, Result<bool>> {
150 Box::pin(self(context))
151 }
152}
153
154struct ExecutionWebSocketGuard<G> {
155 inner: G,
156}
157
158impl<G> WebSocketGuard for ExecutionWebSocketGuard<G>
159where
160 G: Guard,
161{
162 fn can_activate(&self, context: WebSocketContext) -> BoxFuture<'static, Result<bool>> {
163 self.inner.can_activate(context.into_execution_context())
164 }
165}
166
167pub trait WebSocketInterceptor: Send + Sync + 'static {
169 fn before(&self, _context: WebSocketContext) -> BoxFuture<'static, Result<()>> {
170 Box::pin(async { Ok(()) })
171 }
172
173 fn after(
174 &self,
175 _context: WebSocketContext,
176 reply: Option<WebSocketMessage>,
177 ) -> BoxFuture<'static, Result<Option<WebSocketMessage>>> {
178 Box::pin(async move { Ok(reply) })
179 }
180}
181
182struct ExecutionWebSocketInterceptor<I> {
183 inner: I,
184}
185
186impl<I> WebSocketInterceptor for ExecutionWebSocketInterceptor<I>
187where
188 I: ExecutionInterceptor,
189{
190 fn before(&self, context: WebSocketContext) -> BoxFuture<'static, Result<()>> {
191 self.inner.before(context.into_execution_context())
192 }
193
194 fn after(
195 &self,
196 context: WebSocketContext,
197 reply: Option<WebSocketMessage>,
198 ) -> BoxFuture<'static, Result<Option<WebSocketMessage>>> {
199 let future = self.inner.after(context.into_execution_context());
200 Box::pin(async move {
201 future.await?;
202 Ok(reply)
203 })
204 }
205}
206
207type WebSocketHandlerFuture = BoxFuture<'static, Result<Option<WebSocketMessage>>>;
208
209trait WebSocketMessageHandler: Send + Sync + 'static {
210 fn call(&self, message: WebSocketMessage) -> WebSocketHandlerFuture;
211}
212
213struct WebSocketHandlerAdapter<H> {
214 handler: H,
215}
216
217impl<H, Fut, R> WebSocketMessageHandler for WebSocketHandlerAdapter<H>
218where
219 H: Fn(WebSocketMessage) -> Fut + Send + Sync + 'static,
220 Fut: Future<Output = Result<R>> + Send + 'static,
221 R: IntoWebSocketReply + Send + 'static,
222{
223 fn call(&self, message: WebSocketMessage) -> WebSocketHandlerFuture {
224 let future = (self.handler)(message);
225 Box::pin(async move { Ok(future.await?.into_websocket_reply()) })
226 }
227}
228
229#[derive(Clone)]
231pub struct WebSocketGatewayDefinition {
232 path: String,
233 handlers: BTreeMap<String, Arc<dyn WebSocketMessageHandler>>,
234 pipes: Vec<Arc<dyn WebSocketPipe>>,
235 guards: Vec<Arc<dyn WebSocketGuard>>,
236 interceptors: Vec<Arc<dyn WebSocketInterceptor>>,
237 module_name: Option<String>,
238}
239
240impl WebSocketGatewayDefinition {
241 pub fn new(path: impl Into<String>) -> Result<Self> {
242 let path = path.into();
243 validate_route_path(&path)?;
244 Ok(Self {
245 path,
246 handlers: BTreeMap::new(),
247 pipes: Vec::new(),
248 guards: Vec::new(),
249 interceptors: Vec::new(),
250 module_name: None,
251 })
252 }
253
254 pub fn path(&self) -> &str {
255 &self.path
256 }
257
258 pub fn path_shape(&self) -> String {
259 route_shape_key(&self.path)
260 }
261
262 pub fn module_name(&self) -> Option<&str> {
263 self.module_name.as_deref()
264 }
265
266 pub fn events(&self) -> Vec<&str> {
267 self.handlers.keys().map(String::as_str).collect()
268 }
269
270 pub fn matches_path(&self, path: &str) -> bool {
271 match_path_shape(&self.path, path)
272 }
273
274 pub fn path_params(&self, path: &str) -> Result<Option<BTreeMap<String, String>>> {
275 match_path_params(&self.path, path)
276 }
277
278 pub fn subscribe<H, Fut, R>(mut self, event: impl Into<String>, handler: H) -> Result<Self>
279 where
280 H: Fn(WebSocketMessage) -> Fut + Send + Sync + 'static,
281 Fut: Future<Output = Result<R>> + Send + 'static,
282 R: IntoWebSocketReply + Send + 'static,
283 {
284 let event = event.into();
285 if event.trim().is_empty() {
286 return Err(BootError::BadRequest(
287 "websocket event cannot be empty".to_string(),
288 ));
289 }
290 if self.handlers.contains_key(&event) {
291 return Err(BootError::DuplicateRoute(format!(
292 "{} {}",
293 self.path, event
294 )));
295 }
296 self.handlers
297 .insert(event, Arc::new(WebSocketHandlerAdapter { handler }));
298 Ok(self)
299 }
300
301 pub fn with_pipe<P>(mut self, pipe: P) -> Self
302 where
303 P: WebSocketPipe,
304 {
305 self.pipes.push(Arc::new(pipe));
306 self
307 }
308
309 pub fn with_guard<G>(mut self, guard: G) -> Self
310 where
311 G: WebSocketGuard,
312 {
313 self.guards.push(Arc::new(guard));
314 self
315 }
316
317 pub fn with_execution_guard<G>(mut self, guard: G) -> Self
318 where
319 G: Guard,
320 {
321 self.guards
322 .push(Arc::new(ExecutionWebSocketGuard { inner: guard }));
323 self
324 }
325
326 pub(crate) fn with_execution_pipeline_prefix(
327 mut self,
328 guards: &[Arc<dyn Guard>],
329 interceptors: &[Arc<dyn ExecutionInterceptor>],
330 ) -> Self {
331 self.guards = prepend_execution_guards(guards, self.guards);
332 self.interceptors = prepend_execution_interceptors(interceptors, self.interceptors);
333 self
334 }
335
336 pub fn with_interceptor<I>(mut self, interceptor: I) -> Self
337 where
338 I: WebSocketInterceptor,
339 {
340 self.interceptors.push(Arc::new(interceptor));
341 self
342 }
343
344 pub fn with_execution_interceptor<I>(mut self, interceptor: I) -> Self
345 where
346 I: ExecutionInterceptor,
347 {
348 self.interceptors
349 .push(Arc::new(ExecutionWebSocketInterceptor {
350 inner: interceptor,
351 }));
352 self
353 }
354
355 pub fn connect(&self, request: BootRequest) -> Result<WebSocketGatewayConnection> {
356 if request.method() != HttpMethod::Get {
357 return Err(BootError::MethodNotAllowed(format!(
358 "{} {}",
359 request.method().as_str(),
360 request.path()
361 )));
362 }
363 let Some(params) = self.path_params(request.path())? else {
364 return Err(BootError::NotFound(format!(
365 "{} {}",
366 request.method().as_str(),
367 request.path()
368 )));
369 };
370 Ok(WebSocketGatewayConnection {
371 gateway: self.clone(),
372 request: request.with_path_params(params),
373 })
374 }
375
376 pub async fn dispatch(
377 &self,
378 request: BootRequest,
379 message: WebSocketMessage,
380 ) -> Result<Option<WebSocketMessage>> {
381 self.connect(request)?.dispatch(message).await
382 }
383
384 pub(crate) fn with_path_prefix(mut self, prefix: &str) -> Result<Self> {
385 self.path = join_paths(prefix, &self.path)?;
386 Ok(self)
387 }
388
389 pub(crate) fn with_module_name(mut self, module_name: &str) -> Self {
390 self.module_name = Some(module_name.to_string());
391 self
392 }
393}
394
395fn prepend_execution_guards(
396 prefix: &[Arc<dyn Guard>],
397 values: Vec<Arc<dyn WebSocketGuard>>,
398) -> Vec<Arc<dyn WebSocketGuard>> {
399 let mut merged = prefix
400 .iter()
401 .cloned()
402 .map(|guard| Arc::new(ExecutionWebSocketGuard { inner: guard }) as Arc<dyn WebSocketGuard>)
403 .collect::<Vec<_>>();
404 merged.extend(values);
405 merged
406}
407
408fn prepend_execution_interceptors(
409 prefix: &[Arc<dyn ExecutionInterceptor>],
410 values: Vec<Arc<dyn WebSocketInterceptor>>,
411) -> Vec<Arc<dyn WebSocketInterceptor>> {
412 let mut merged = prefix
413 .iter()
414 .cloned()
415 .map(|interceptor| {
416 Arc::new(ExecutionWebSocketInterceptor { inner: interceptor })
417 as Arc<dyn WebSocketInterceptor>
418 })
419 .collect::<Vec<_>>();
420 merged.extend(values);
421 merged
422}
423
424pub trait WebSocketConnection: Send + Sync {
426 fn request(&self) -> &BootRequest;
427
428 fn dispatch(
429 &self,
430 message: WebSocketMessage,
431 ) -> BoxFuture<'static, Result<Option<WebSocketMessage>>>;
432}
433
434#[derive(Clone)]
436pub struct WebSocketGatewayConnection {
437 gateway: WebSocketGatewayDefinition,
438 request: BootRequest,
439}
440
441impl WebSocketGatewayConnection {
442 pub fn request(&self) -> &BootRequest {
443 &self.request
444 }
445
446 pub async fn dispatch(
447 &self,
448 mut message: WebSocketMessage,
449 ) -> Result<Option<WebSocketMessage>> {
450 let event = message.event.clone();
451 let handler = self.gateway.handlers.get(&event).cloned().ok_or_else(|| {
452 BootError::NotFound(format!("websocket event {} {}", self.gateway.path, event))
453 })?;
454
455 let context = WebSocketContext::new(&self.gateway, self.request.clone(), &message.event);
456 for guard in &self.gateway.guards {
457 let can_activate = guard.can_activate(context.clone()).await?;
458 if !can_activate {
459 return Err(BootError::Forbidden(format!(
460 "websocket event {} {}",
461 self.gateway.path, message.event
462 )));
463 }
464 }
465
466 for interceptor in &self.gateway.interceptors {
467 interceptor.before(context.clone()).await?;
468 }
469
470 for pipe in &self.gateway.pipes {
471 message = pipe.transform(message).await?;
472 }
473
474 let mut reply = handler.call(message).await?;
475 for interceptor in self.gateway.interceptors.iter().rev() {
476 reply = interceptor.after(context.clone(), reply).await?;
477 }
478 Ok(reply)
479 }
480}
481
482impl WebSocketConnection for WebSocketGatewayConnection {
483 fn request(&self) -> &BootRequest {
484 self.request()
485 }
486
487 fn dispatch(
488 &self,
489 message: WebSocketMessage,
490 ) -> BoxFuture<'static, Result<Option<WebSocketMessage>>> {
491 let connection = self.clone();
492 Box::pin(async move { connection.dispatch(message).await })
493 }
494}