Skip to main content

arcly_http/realtime/
gateway.rs

1//! Gateway contracts: the `ArclyGateway` lifecycle trait and the link-time
2//! descriptor / runtime types the `#[Gateway]` macro emits.
3//!
4//! ## Why no `dyn ArclyGateway`
5//!
6//! `ArclyGateway` uses native `async fn` in trait — ergonomic, but not
7//! object-safe. We never need a trait object: the `#[Gateway]` macro generates
8//! *monomorphic* closures over the concrete gateway type (`&'static ChatGateway`)
9//! and type-erases them into [`GatewayRuntime`]. This mirrors the framework's
10//! route-thunk pattern and keeps the developer's trait impl clean.
11
12use std::collections::HashMap;
13
14use futures::future::BoxFuture;
15
16use crate::core::engine::FrozenDiContainer;
17use crate::realtime::connection::WsClient;
18use crate::web::Error;
19
20/// Connection lifecycle for a real-time gateway.
21///
22/// Implement on your gateway struct. Both hooks default to no-ops, so override
23/// only what you need. Handlers for individual events are declared with
24/// `#[Subscribe("event::name")]` inside the `#[Gateway(...)]` impl block.
25pub trait ArclyGateway: Send + Sync + 'static {
26    /// Fired after the socket has upgraded and been registered.
27    fn on_connect(&self, client: WsClient) -> impl std::future::Future<Output = ()> + Send {
28        let _ = client;
29        async {}
30    }
31
32    /// Fired after the socket has closed and just before it is unregistered.
33    fn on_disconnect(&self, client: WsClient) -> impl std::future::Future<Output = ()> + Send {
34        let _ = client;
35        async {}
36    }
37}
38
39/// A type-erased lifecycle hook closure (built by the macro over a concrete
40/// gateway instance).
41pub type LifecycleHook = Box<dyn Fn(WsClient) -> BoxFuture<'static, ()> + Send + Sync>;
42
43/// A type-erased event handler. Receives the originating client and the raw
44/// JSON `data` payload (as borrowed text), returns the handler's `Result`.
45pub type MessageHandler = std::sync::Arc<
46    dyn Fn(WsClient, std::sync::Arc<str>) -> BoxFuture<'static, Result<(), Error>> + Send + Sync,
47>;
48
49/// The fully-constructed, ready-to-serve form of a gateway. Produced once at
50/// launch by [`GatewayDescriptor::build`] and leaked to `&'static`.
51pub struct GatewayRuntime {
52    /// Mount path for the upgrade endpoint, e.g. `/chat-socket`.
53    pub path: &'static str,
54    pub on_connect: LifecycleHook,
55    pub on_disconnect: LifecycleHook,
56    /// Event-name → handler routing table. Lock-free reads (`&HashMap`) on the
57    /// frame-processing hot path.
58    pub dispatch: HashMap<&'static str, MessageHandler>,
59}
60
61impl GatewayRuntime {
62    /// Resolve the handler for an inbound event name, if subscribed.
63    #[inline]
64    pub fn handler(&self, event: &str) -> Option<&MessageHandler> {
65        self.dispatch.get(event)
66    }
67}
68
69/// Link-time gateway registration. One is emitted per `#[Gateway]` impl and
70/// collected via `inventory`. `build` constructs the gateway (wiring its
71/// `Inject<T>` fields from the frozen container) and assembles the runtime.
72pub struct GatewayDescriptor {
73    /// Gateway type name — matched against a module's `gateways(...)` list.
74    pub name: &'static str,
75    pub path: &'static str,
76    pub build: fn(&'static FrozenDiContainer) -> &'static GatewayRuntime,
77}
78
79inventory::collect!(&'static GatewayDescriptor);