#![allow(non_snake_case)]
use std::collections::HashMap as FxHashMap;
use crate::core::{RiResult, RiServiceContext};
pub type RiHookHandler = Box<dyn Fn(&RiServiceContext, &RiHookEvent) -> RiResult<()> + Send + Sync>;
pub type RiHookHandlerEntry = (RiHookId, RiHookHandler);
pub type RiHookHandlersMap = FxHashMap<RiHookKind, Vec<RiHookHandlerEntry>>;
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
#[derive(Eq, Hash, PartialEq, Clone, Copy, Debug)]
pub enum RiHookKind {
Startup,
Shutdown,
BeforeModulesInit,
AfterModulesInit,
BeforeModulesStart,
AfterModulesStart,
BeforeModulesShutdown,
AfterModulesShutdown,
ConfigReload,
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
#[derive(Eq, Hash, PartialEq, Clone, Copy, Debug)]
pub enum RiModulePhase {
Init,
BeforeStart,
Start,
AfterStart,
BeforeShutdown,
Shutdown,
AfterShutdown,
AsyncInit,
AsyncBeforeStart,
AsyncStart,
AsyncAfterStart,
AsyncBeforeShutdown,
AsyncShutdown,
AsyncAfterShutdown,
}
impl RiModulePhase {
pub fn as_str(&self) -> &'static str {
match self {
RiModulePhase::Init => "init",
RiModulePhase::BeforeStart => "before_start",
RiModulePhase::Start => "start",
RiModulePhase::AfterStart => "after_start",
RiModulePhase::BeforeShutdown => "before_shutdown",
RiModulePhase::Shutdown => "shutdown",
RiModulePhase::AfterShutdown => "after_shutdown",
RiModulePhase::AsyncInit => "async_init",
RiModulePhase::AsyncBeforeStart => "async_before_start",
RiModulePhase::AsyncStart => "async_start",
RiModulePhase::AsyncAfterStart => "async_after_start",
RiModulePhase::AsyncBeforeShutdown => "async_before_shutdown",
RiModulePhase::AsyncShutdown => "async_shutdown",
RiModulePhase::AsyncAfterShutdown => "async_after_shutdown",
}
}
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
#[derive(Clone, Debug)]
pub struct RiHookEvent {
pub kind: RiHookKind,
pub module: Option<String>,
pub phase: Option<RiModulePhase>,
}
impl RiHookEvent {
pub fn new(kind: RiHookKind, module: Option<String>, phase: Option<RiModulePhase>) -> Self {
Self { kind, module, phase }
}
pub fn config_reload(_path: String, _timestamp: chrono::DateTime<chrono::Utc>) -> Self {
Self {
kind: RiHookKind::ConfigReload,
module: Some("config_manager".to_string()),
phase: None,
}
}
}
pub type RiHookId = String;
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiHookBus {
handlers: RiHookHandlersMap,
}
impl Default for RiHookBus {
fn default() -> Self {
Self::new()
}
}
impl RiHookBus {
pub fn new() -> Self {
RiHookBus { handlers: FxHashMap::default() }
}
pub fn register<F>(&mut self, kind: RiHookKind, id: RiHookId, handler: F)
where
F: Fn(&RiServiceContext, &RiHookEvent) -> RiResult<()> + Send + Sync + 'static,
{
if let Err(e) = Self::validate_hook_id(&id) {
log::warn!("[Ri.Hooks] Invalid hook ID '{}': {}", id, e);
return;
}
self.handlers.entry(kind).or_default().push((id, Box::new(handler)));
}
fn validate_hook_id(id: &str) -> RiResult<()> {
if id.is_empty() || id.len() > 128 {
return Err(crate::core::RiError::Other(
"Hook ID must be 1-128 characters".to_string()
));
}
let chars: Vec<char> = id.chars().collect();
if !chars[0].is_ascii_alphabetic() && chars[0] != '_' {
return Err(crate::core::RiError::Other(
"Hook ID must start with a letter or underscore".to_string()
));
}
for c in &chars {
if !c.is_ascii_alphanumeric() && *c != '-' && *c != '_' {
return Err(crate::core::RiError::Other(
"Hook ID can only contain alphanumeric characters, dashes, and underscores".to_string()
));
}
}
Ok(())
}
pub fn emit(&self, kind: &RiHookKind, ctx: &RiServiceContext) -> RiResult<()> {
self.emit_with(kind, ctx, None, None)
}
pub fn emit_with(
&self,
kind: &RiHookKind,
ctx: &RiServiceContext,
module: Option<&str>,
phase: Option<RiModulePhase>,
) -> RiResult<()> {
let event = RiHookEvent {
kind: *kind,
module: module.map(|s| s.to_string()),
phase,
};
if let Some(list) = self.handlers.get(kind) {
for (_id, handler) in list {
handler(ctx, &event)?;
}
}
Ok(())
}
pub fn emit_simple(
&self,
kind: &RiHookKind,
module: Option<&str>,
phase: Option<RiModulePhase>,
) -> RiResult<()> {
let event = RiHookEvent {
kind: *kind,
module: module.map(|s| s.to_string()),
phase,
};
if let Some(list) = self.handlers.get(kind) {
for (_id, handler) in list {
if let Ok(ctx) = RiServiceContext::new_default() {
handler(&ctx, &event)?;
}
}
}
Ok(())
}
pub fn has_handlers(&self, kind: &RiHookKind) -> bool {
self.handlers.get(kind).map_or(false, |list| !list.is_empty())
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiHookBus {
#[new]
fn py_new() -> Self {
Self::new()
}
}