arcly-http 0.1.1

Enterprise-grade NestJS-inspired web framework on axum: zero-lock DI, declarative controllers, multi-tenant data routing, transactional outbox, ABAC, and a self-documenting OpenAPI surface
Documentation
//! Gateway contracts: the `ArclyGateway` lifecycle trait and the link-time
//! descriptor / runtime types the `#[Gateway]` macro emits.
//!
//! ## Why no `dyn ArclyGateway`
//!
//! `ArclyGateway` uses native `async fn` in trait — ergonomic, but not
//! object-safe. We never need a trait object: the `#[Gateway]` macro generates
//! *monomorphic* closures over the concrete gateway type (`&'static ChatGateway`)
//! and type-erases them into [`GatewayRuntime`]. This mirrors the framework's
//! route-thunk pattern and keeps the developer's trait impl clean.

use std::collections::HashMap;

use futures::future::BoxFuture;

use crate::core::engine::FrozenDiContainer;
use crate::realtime::connection::WsClient;
use crate::web::Error;

/// Connection lifecycle for a real-time gateway.
///
/// Implement on your gateway struct. Both hooks default to no-ops, so override
/// only what you need. Handlers for individual events are declared with
/// `#[Subscribe("event::name")]` inside the `#[Gateway(...)]` impl block.
pub trait ArclyGateway: Send + Sync + 'static {
    /// Fired after the socket has upgraded and been registered.
    fn on_connect(&self, client: WsClient) -> impl std::future::Future<Output = ()> + Send {
        let _ = client;
        async {}
    }

    /// Fired after the socket has closed and just before it is unregistered.
    fn on_disconnect(&self, client: WsClient) -> impl std::future::Future<Output = ()> + Send {
        let _ = client;
        async {}
    }
}

/// A type-erased lifecycle hook closure (built by the macro over a concrete
/// gateway instance).
pub type LifecycleHook = Box<dyn Fn(WsClient) -> BoxFuture<'static, ()> + Send + Sync>;

/// A type-erased event handler. Receives the originating client and the raw
/// JSON `data` payload (as borrowed text), returns the handler's `Result`.
pub type MessageHandler = std::sync::Arc<
    dyn Fn(WsClient, std::sync::Arc<str>) -> BoxFuture<'static, Result<(), Error>> + Send + Sync,
>;

/// The fully-constructed, ready-to-serve form of a gateway. Produced once at
/// launch by [`GatewayDescriptor::build`] and leaked to `&'static`.
pub struct GatewayRuntime {
    /// Mount path for the upgrade endpoint, e.g. `/chat-socket`.
    pub path: &'static str,
    pub on_connect: LifecycleHook,
    pub on_disconnect: LifecycleHook,
    /// Event-name → handler routing table. Lock-free reads (`&HashMap`) on the
    /// frame-processing hot path.
    pub dispatch: HashMap<&'static str, MessageHandler>,
}

impl GatewayRuntime {
    /// Resolve the handler for an inbound event name, if subscribed.
    #[inline]
    pub fn handler(&self, event: &str) -> Option<&MessageHandler> {
        self.dispatch.get(event)
    }
}

/// Link-time gateway registration. One is emitted per `#[Gateway]` impl and
/// collected via `inventory`. `build` constructs the gateway (wiring its
/// `Inject<T>` fields from the frozen container) and assembles the runtime.
pub struct GatewayDescriptor {
    /// Gateway type name — matched against a module's `gateways(...)` list.
    pub name: &'static str,
    pub path: &'static str,
    pub build: fn(&'static FrozenDiContainer) -> &'static GatewayRuntime,
}

inventory::collect!(&'static GatewayDescriptor);