dmsc/hooks/mod.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Hooks System
21//!
22//! This module provides an event bus system for DMSC, enabling communication between components
23//! during various lifecycle events. It supports both synchronous and asynchronous module lifecycle
24//! phases, and allows for custom event handlers to be registered.
25//!
26//! ## Key Components
27//!
28//! - **DMSCHookKind**: Enum defining the different types of hooks
29//! - **DMSCModulePhase**: Enum defining the different module lifecycle phases
30//! - **DMSCHookEvent**: Struct representing a hook event
31//! - **DMSCHookBus**: Event bus for registering and emitting hooks
32//!
33//! ## Design Principles
34//!
35//! 1. **Event-Driven Architecture**: Uses an event bus pattern for loose coupling between components
36//! 2. **Lifecycle Support**: Covers all stages of module lifecycle, both synchronous and asynchronous
37//! 3. **Type Safety**: Uses enums for hook kinds and phases to ensure type safety
38//! 4. **Flexibility**: Allows registering multiple handlers for the same hook
39//! 5. **Contextual Information**: Events carry contextual information about the module and phase
40//! 6. **Error Propagation**: Hook handlers can return errors that propagate up the call stack
41//!
42//! ## Usage
43//!
44//! ```rust
45//! use dmsc::prelude::*;
46//!
47//! fn example() -> DMSCResult<()> {
48//! // Create a hook bus
49//! let mut hook_bus = DMSCHookBus::new();
50//!
51//! // Register a hook handler
52//! hook_bus.register(DMSCHookKind::Startup, "example.startup".to_string(), |ctx, event| {
53//! // Handle startup event
54//! Ok(())
55//! });
56//!
57//! // Create a service context (usually provided by the runtime)
58//! let ctx = DMSCServiceContext::new();
59//!
60//! // Emit a hook event
61//! hook_bus.emit(&DMSCHookKind::Startup, &ctx)?;
62//!
63//! Ok(())
64//! }
65
66use std::collections::HashMap;
67
68use crate::core::{DMSCResult, DMSCServiceContext};
69
70// Type aliases for complex types
71/// Type alias for a hook handler function
72pub type DMSCHookHandler = Box<dyn Fn(&DMSCServiceContext, &DMSCHookEvent) -> DMSCResult<()> + Send + Sync>;
73
74/// Type alias for a hook handler entry (ID + handler)
75pub type DMSCHookHandlerEntry = (DMSCHookId, DMSCHookHandler);
76
77/// Type alias for a collection of hook handlers grouped by hook kind
78pub type DMSCHookHandlersMap = HashMap<DMSCHookKind, Vec<DMSCHookHandlerEntry>>;
79
80/// Hook kind definition.
81///
82/// This enum defines the different types of hooks that can be emitted during the application lifecycle.
83#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
84#[derive(Eq, Hash, PartialEq, Clone, Copy, Debug)]
85pub enum DMSCHookKind {
86 /// Emitted when the application starts up
87 Startup,
88 /// Emitted when the application shuts down
89 Shutdown,
90 /// Emitted before modules are initialized
91 BeforeModulesInit,
92 /// Emitted after modules are initialized
93 AfterModulesInit,
94 /// Emitted before modules are started
95 BeforeModulesStart,
96 /// Emitted after modules are started
97 AfterModulesStart,
98 /// Emitted before modules are shut down
99 BeforeModulesShutdown,
100 /// Emitted after modules are shut down
101 AfterModulesShutdown,
102 /// Emitted when configuration is reloaded
103 ConfigReload,
104}
105
106/// Module lifecycle phase definition.
107///
108/// This enum defines the different phases a module can go through during its lifecycle,
109/// including both synchronous and asynchronous phases.
110#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
111#[derive(Eq, Hash, PartialEq, Clone, Copy, Debug)]
112pub enum DMSCModulePhase {
113 /// Synchronous initialization phase
114 Init,
115 /// Synchronous phase before starting
116 BeforeStart,
117 /// Synchronous start phase
118 Start,
119 /// Synchronous phase after starting
120 AfterStart,
121 /// Synchronous phase before shutting down
122 BeforeShutdown,
123 /// Synchronous shutdown phase
124 Shutdown,
125 /// Synchronous phase after shutting down
126 AfterShutdown,
127 /// Asynchronous initialization phase
128 AsyncInit,
129 /// Asynchronous phase before starting
130 AsyncBeforeStart,
131 /// Asynchronous start phase
132 AsyncStart,
133 /// Asynchronous phase after starting
134 AsyncAfterStart,
135 /// Asynchronous phase before shutting down
136 AsyncBeforeShutdown,
137 /// Asynchronous shutdown phase
138 AsyncShutdown,
139 /// Asynchronous phase after shutting down
140 AsyncAfterShutdown,
141}
142
143impl DMSCModulePhase {
144 /// Returns the string representation of the module phase.
145 ///
146 /// # Returns
147 ///
148 /// A static string representing the module phase (e.g., "init", "start", "async_shutdown")
149 pub fn as_str(&self) -> &'static str {
150 match self {
151 DMSCModulePhase::Init => "init",
152 DMSCModulePhase::BeforeStart => "before_start",
153 DMSCModulePhase::Start => "start",
154 DMSCModulePhase::AfterStart => "after_start",
155 DMSCModulePhase::BeforeShutdown => "before_shutdown",
156 DMSCModulePhase::Shutdown => "shutdown",
157 DMSCModulePhase::AfterShutdown => "after_shutdown",
158 DMSCModulePhase::AsyncInit => "async_init",
159 DMSCModulePhase::AsyncBeforeStart => "async_before_start",
160 DMSCModulePhase::AsyncStart => "async_start",
161 DMSCModulePhase::AsyncAfterStart => "async_after_start",
162 DMSCModulePhase::AsyncBeforeShutdown => "async_before_shutdown",
163 DMSCModulePhase::AsyncShutdown => "async_shutdown",
164 DMSCModulePhase::AsyncAfterShutdown => "async_after_shutdown",
165 }
166 }
167}
168
169/// Hook event structure.
170///
171/// This struct represents an event that is emitted when a hook is triggered. It contains
172/// information about the hook kind, the module (if applicable), and the module phase (if applicable).
173#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
174#[derive(Clone, Debug)]
175pub struct DMSCHookEvent {
176 /// The kind of hook that was triggered
177 pub kind: DMSCHookKind,
178 /// The name of the module associated with the event (if any)
179 pub module: Option<String>,
180 /// The module phase associated with the event (if any)
181 pub phase: Option<DMSCModulePhase>,
182}
183
184impl DMSCHookEvent {
185 /// Creates a new hook event.
186 pub fn new(kind: DMSCHookKind, module: Option<String>, phase: Option<DMSCModulePhase>) -> Self {
187 Self { kind, module, phase }
188 }
189
190 /// Creates a config reload event.
191 pub fn config_reload(_path: String, _timestamp: chrono::DateTime<chrono::Utc>) -> Self {
192 Self {
193 kind: DMSCHookKind::ConfigReload,
194 module: Some("config_manager".to_string()),
195 phase: None,
196 }
197 }
198}
199
200/// Type alias for hook IDs.
201///
202/// Hook IDs are used to identify hook handlers and can be used for debugging and logging purposes.
203pub type DMSCHookId = String;
204
205/// Hook bus for registering and emitting hooks.
206///
207/// This struct manages the registration of hook handlers and the emission of hook events.
208/// It allows multiple handlers to be registered for the same hook kind.
209#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
210pub struct DMSCHookBus {
211 /// Internal storage for hook handlers, organized by hook kind
212 handlers: DMSCHookHandlersMap,
213}
214
215impl Default for DMSCHookBus {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl DMSCHookBus {
222 /// Creates a new hook bus instance.
223 ///
224 /// Returns a new `DMSCHookBus` instance with no registered handlers.
225 pub fn new() -> Self {
226 DMSCHookBus { handlers: HashMap::new() }
227 }
228
229 /// Registers a hook handler for a specific hook kind.
230 ///
231 /// # Parameters
232 ///
233 /// - `kind`: The hook kind to register the handler for
234 /// - `id`: A unique ID for the hook handler
235 /// - `handler`: The handler function to execute when the hook is emitted
236 ///
237 /// The handler function takes a `DMSCServiceContext` and a `DMSCHookEvent` and returns a `DMSCResult<()>`.
238 pub fn register<F>(&mut self, kind: DMSCHookKind, id: DMSCHookId, handler: F)
239 where
240 F: Fn(&DMSCServiceContext, &DMSCHookEvent) -> DMSCResult<()> + Send + Sync + 'static,
241 {
242 self.handlers.entry(kind).or_default().push((id, Box::new(handler)));
243 }
244
245 /// Emits a hook event of the specified kind.
246 ///
247 /// # Parameters
248 ///
249 /// - `kind`: The hook kind to emit
250 /// - `ctx`: The service context to pass to the hook handlers
251 ///
252 /// # Returns
253 ///
254 /// A `DMSCResult<()>` indicating success or failure
255 pub fn emit(&self, kind: &DMSCHookKind, ctx: &DMSCServiceContext) -> DMSCResult<()> {
256 self.emit_with(kind, ctx, None, None)
257 }
258
259 /// Emits a hook event with additional contextual information.
260 ///
261 /// # Parameters
262 ///
263 /// - `kind`: The hook kind to emit
264 /// - `ctx`: The service context to pass to the hook handlers
265 /// - `module`: The name of the module associated with the event (if any)
266 /// - `phase`: The module phase associated with the event (if any)
267 ///
268 /// # Returns
269 ///
270 /// A `DMSCResult<()>` indicating success or failure
271 pub fn emit_with(
272 &self,
273 kind: &DMSCHookKind,
274 ctx: &DMSCServiceContext,
275 module: Option<&str>,
276 phase: Option<DMSCModulePhase>,
277 ) -> DMSCResult<()> {
278 let event = DMSCHookEvent {
279 kind: *kind,
280 module: module.map(|s| s.to_string()),
281 phase,
282 };
283 if let Some(list) = self.handlers.get(kind) {
284 for (_id, handler) in list {
285 handler(ctx, &event)?;
286 }
287 }
288 Ok(())
289 }
290}
291
292#[cfg(feature = "pyo3")]
293#[pyo3::prelude::pymethods]
294impl DMSCHookBus {
295 #[new]
296 fn py_new() -> Self {
297 Self::new()
298 }
299}