noema 0.4.0

Noema IOC and DI framework for Rust
Documentation
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use super::{BackgroundErrorHandler, BackgroundSpawner, BoxDynError, NoemaResult};

/// How `subscribe!` receive functions run `EventListener::handle`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InvokeMode {
    /// Spawn each handler on [`BackgroundSpawner`] and return (default).
    #[default]
    Spawn,
    /// Await each handler before `dispatch` returns (WebSocket-style).
    /// Handler `Err` is returned from `dispatch` (not swallowed via `on_error`).
    Await,
}

/// Context passed to subscriber receive functions (spawner, errors).
pub struct DispatchContext {
    pub spawner: Arc<dyn BackgroundSpawner>,
    pub error_handler: Arc<dyn BackgroundErrorHandler>,
    pub invoke_mode: InvokeMode,
}

impl DispatchContext {
    pub fn new(
        spawner: Arc<dyn BackgroundSpawner>,
        error_handler: Arc<dyn BackgroundErrorHandler>,
    ) -> Self {
        Self {
            spawner,
            error_handler,
            invoke_mode: InvokeMode::Spawn,
        }
    }

    pub fn with_invoke_mode(mut self, invoke_mode: InvokeMode) -> Self {
        self.invoke_mode = invoke_mode;
        self
    }
}

/// Monomorphized receive function for one event type (no `Any` / `TypeId`).
pub type ReceiveFn = fn(
    payload: &[u8],
    ctx: &DispatchContext,
) -> Pin<Box<dyn Future<Output = NoemaResult<()>> + Send>>;

/// One row in the transport subscriber registry (`name` → receive fn).
pub struct SubscriberEntry {
    pub name: &'static str,
    pub receive: ReceiveFn,
}

/// Static subscriber table generated by `subscribe!`.
pub trait SubscriberRegistry: Send + Sync {
    fn entries(&self) -> &'static [SubscriberEntry];
}

/// Spawner and error handler for dispatch (implement explicitly on your transport).
pub trait EventDispatcherContext: Send + Sync {
    fn dispatch_context(&self) -> DispatchContext;
}

/// Name lookup + handler dispatch (call from your consumer loop).
#[async_trait::async_trait]
pub trait EventDispatch: SubscriberRegistry + EventDispatcherContext + Send + Sync {
    async fn dispatch(&self, name: &str, payload: &[u8]) -> NoemaResult<()> {
        let entry = match self.entries().iter().find(|e| e.name == name) {
            Some(e) => e,
            None => {
                let err: BoxDynError = format!("unknown event: {name}").into();
                self.dispatch_context()
                    .error_handler
                    .handle(err, name, "dispatch".to_string())
                    .await;
                return Err(format!("unknown event: {name}").into());
            }
        };

        // Keep the receive error (e.g. `MappedError` from a WS handler). Do not wrap it.
        (entry.receive)(payload, &self.dispatch_context()).await
    }
}

impl<T> EventDispatch for T where T: SubscriberRegistry + EventDispatcherContext + Send + Sync {}