use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LifecycleState {
Created,
Initialized,
Running,
Stopped,
Unloaded,
Error,
}
impl LifecycleState {
pub fn can_start(&self) -> bool {
matches!(self, Self::Initialized)
}
pub fn can_stop(&self) -> bool {
matches!(self, Self::Running)
}
pub fn can_call(&self) -> bool {
matches!(self, Self::Running)
}
pub fn can_reload(&self) -> bool {
matches!(self, Self::Initialized | Self::Running | Self::Stopped | Self::Error)
}
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Unloaded)
}
pub fn description(&self) -> &'static str {
match self {
Self::Created => "Plugin created but not initialized",
Self::Initialized => "Plugin initialized and ready to start",
Self::Running => "Plugin running and accepting calls",
Self::Stopped => "Plugin stopped",
Self::Unloaded => "Plugin unloaded",
Self::Error => "Plugin in error state",
}
}
}
impl std::fmt::Display for LifecycleState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Self::Created => "created",
Self::Initialized => "initialized",
Self::Running => "running",
Self::Stopped => "stopped",
Self::Unloaded => "unloaded",
Self::Error => "error",
};
write!(f, "{}", name)
}
}
pub trait PluginLifecycle {
fn on_init(&mut self) -> crate::Result<()> {
Ok(())
}
fn on_start(&mut self) -> crate::Result<()> {
Ok(())
}
fn on_stop(&mut self) -> crate::Result<()> {
Ok(())
}
fn on_unload(&mut self) -> crate::Result<()> {
Ok(())
}
fn on_before_reload(&mut self) -> crate::Result<()> {
Ok(())
}
fn on_after_reload(&mut self) -> crate::Result<()> {
Ok(())
}
fn on_error(&mut self, error: &crate::Error) {
tracing::error!("Plugin error: {}", error);
}
}
#[derive(Debug, Clone)]
pub enum LifecycleEvent {
Created {
name: String,
at: Instant,
},
Initialized {
name: String,
at: Instant,
},
Started {
name: String,
at: Instant,
},
Stopped {
name: String,
at: Instant,
},
Reloaded {
name: String,
at: Instant,
count: u64,
},
Unloaded {
name: String,
at: Instant,
},
Error {
name: String,
message: String,
at: Instant,
},
}
impl LifecycleEvent {
pub fn plugin_name(&self) -> &str {
match self {
Self::Created { name, .. } => name,
Self::Initialized { name, .. } => name,
Self::Started { name, .. } => name,
Self::Stopped { name, .. } => name,
Self::Reloaded { name, .. } => name,
Self::Unloaded { name, .. } => name,
Self::Error { name, .. } => name,
}
}
pub fn timestamp(&self) -> Instant {
match self {
Self::Created { at, .. } => *at,
Self::Initialized { at, .. } => *at,
Self::Started { at, .. } => *at,
Self::Stopped { at, .. } => *at,
Self::Reloaded { at, .. } => *at,
Self::Unloaded { at, .. } => *at,
Self::Error { at, .. } => *at,
}
}
pub fn event_name(&self) -> &'static str {
match self {
Self::Created { .. } => "created",
Self::Initialized { .. } => "initialized",
Self::Started { .. } => "started",
Self::Stopped { .. } => "stopped",
Self::Reloaded { .. } => "reloaded",
Self::Unloaded { .. } => "unloaded",
Self::Error { .. } => "error",
}
}
}
pub struct LifecycleHooks {
handlers: Vec<Box<dyn Fn(&LifecycleEvent) + Send + Sync>>,
}
impl LifecycleHooks {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
}
}
pub fn on_event<F>(&mut self, handler: F)
where
F: Fn(&LifecycleEvent) + Send + Sync + 'static,
{
self.handlers.push(Box::new(handler));
}
pub fn emit(&self, event: LifecycleEvent) {
for handler in &self.handlers {
handler(&event);
}
}
pub fn emit_created(&self, name: &str) {
self.emit(LifecycleEvent::Created {
name: name.to_string(),
at: Instant::now(),
});
}
pub fn emit_initialized(&self, name: &str) {
self.emit(LifecycleEvent::Initialized {
name: name.to_string(),
at: Instant::now(),
});
}
pub fn emit_started(&self, name: &str) {
self.emit(LifecycleEvent::Started {
name: name.to_string(),
at: Instant::now(),
});
}
pub fn emit_stopped(&self, name: &str) {
self.emit(LifecycleEvent::Stopped {
name: name.to_string(),
at: Instant::now(),
});
}
pub fn emit_reloaded(&self, name: &str, count: u64) {
self.emit(LifecycleEvent::Reloaded {
name: name.to_string(),
at: Instant::now(),
count,
});
}
pub fn emit_unloaded(&self, name: &str) {
self.emit(LifecycleEvent::Unloaded {
name: name.to_string(),
at: Instant::now(),
});
}
pub fn emit_error(&self, name: &str, message: &str) {
self.emit(LifecycleEvent::Error {
name: name.to_string(),
message: message.to_string(),
at: Instant::now(),
});
}
}
impl Default for LifecycleHooks {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for LifecycleHooks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LifecycleHooks")
.field("handler_count", &self.handlers.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[test]
fn test_lifecycle_state_transitions() {
assert!(LifecycleState::Initialized.can_start());
assert!(!LifecycleState::Created.can_start());
assert!(LifecycleState::Running.can_stop());
assert!(!LifecycleState::Stopped.can_stop());
assert!(LifecycleState::Running.can_call());
assert!(!LifecycleState::Stopped.can_call());
assert!(LifecycleState::Running.can_reload());
assert!(!LifecycleState::Unloaded.can_reload());
assert!(LifecycleState::Unloaded.is_terminal());
assert!(!LifecycleState::Running.is_terminal());
}
#[test]
fn test_lifecycle_hooks() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let mut hooks = LifecycleHooks::new();
hooks.on_event(move |_| {
counter_clone.fetch_add(1, Ordering::Relaxed);
});
hooks.emit_created("test");
hooks.emit_started("test");
hooks.emit_stopped("test");
assert_eq!(counter.load(Ordering::Relaxed), 3);
}
#[test]
fn test_lifecycle_event_info() {
let event = LifecycleEvent::Started {
name: "test-plugin".to_string(),
at: Instant::now(),
};
assert_eq!(event.plugin_name(), "test-plugin");
assert_eq!(event.event_name(), "started");
}
}