fusabi_plugin_runtime/
lifecycle.rs

1//! Plugin lifecycle management.
2
3use std::time::Instant;
4
5/// Plugin lifecycle state.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum LifecycleState {
8    /// Plugin has been created but not initialized.
9    Created,
10    /// Plugin has been initialized with an engine.
11    Initialized,
12    /// Plugin is running and accepting calls.
13    Running,
14    /// Plugin has been stopped.
15    Stopped,
16    /// Plugin has been unloaded.
17    Unloaded,
18    /// Plugin is in an error state.
19    Error,
20}
21
22impl LifecycleState {
23    /// Check if the plugin can be started.
24    pub fn can_start(&self) -> bool {
25        matches!(self, Self::Initialized)
26    }
27
28    /// Check if the plugin can be stopped.
29    pub fn can_stop(&self) -> bool {
30        matches!(self, Self::Running)
31    }
32
33    /// Check if the plugin can be called.
34    pub fn can_call(&self) -> bool {
35        matches!(self, Self::Running)
36    }
37
38    /// Check if the plugin can be reloaded.
39    pub fn can_reload(&self) -> bool {
40        matches!(self, Self::Initialized | Self::Running | Self::Stopped | Self::Error)
41    }
42
43    /// Check if the plugin is in a terminal state.
44    pub fn is_terminal(&self) -> bool {
45        matches!(self, Self::Unloaded)
46    }
47
48    /// Get a human-readable description.
49    pub fn description(&self) -> &'static str {
50        match self {
51            Self::Created => "Plugin created but not initialized",
52            Self::Initialized => "Plugin initialized and ready to start",
53            Self::Running => "Plugin running and accepting calls",
54            Self::Stopped => "Plugin stopped",
55            Self::Unloaded => "Plugin unloaded",
56            Self::Error => "Plugin in error state",
57        }
58    }
59}
60
61impl std::fmt::Display for LifecycleState {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        let name = match self {
64            Self::Created => "created",
65            Self::Initialized => "initialized",
66            Self::Running => "running",
67            Self::Stopped => "stopped",
68            Self::Unloaded => "unloaded",
69            Self::Error => "error",
70        };
71        write!(f, "{}", name)
72    }
73}
74
75/// Trait for plugin lifecycle management.
76pub trait PluginLifecycle {
77    /// Initialize the plugin.
78    fn on_init(&mut self) -> crate::Result<()> {
79        Ok(())
80    }
81
82    /// Start the plugin.
83    fn on_start(&mut self) -> crate::Result<()> {
84        Ok(())
85    }
86
87    /// Stop the plugin.
88    fn on_stop(&mut self) -> crate::Result<()> {
89        Ok(())
90    }
91
92    /// Unload the plugin.
93    fn on_unload(&mut self) -> crate::Result<()> {
94        Ok(())
95    }
96
97    /// Called before a reload.
98    fn on_before_reload(&mut self) -> crate::Result<()> {
99        Ok(())
100    }
101
102    /// Called after a reload.
103    fn on_after_reload(&mut self) -> crate::Result<()> {
104        Ok(())
105    }
106
107    /// Called when an error occurs.
108    fn on_error(&mut self, error: &crate::Error) {
109        tracing::error!("Plugin error: {}", error);
110    }
111}
112
113/// Lifecycle event for hooks.
114#[derive(Debug, Clone)]
115pub enum LifecycleEvent {
116    /// Plugin was created.
117    Created {
118        /// Plugin name.
119        name: String,
120        /// Creation time.
121        at: Instant,
122    },
123    /// Plugin was initialized.
124    Initialized {
125        /// Plugin name.
126        name: String,
127        /// Initialization time.
128        at: Instant,
129    },
130    /// Plugin was started.
131    Started {
132        /// Plugin name.
133        name: String,
134        /// Start time.
135        at: Instant,
136    },
137    /// Plugin was stopped.
138    Stopped {
139        /// Plugin name.
140        name: String,
141        /// Stop time.
142        at: Instant,
143    },
144    /// Plugin was reloaded.
145    Reloaded {
146        /// Plugin name.
147        name: String,
148        /// Reload time.
149        at: Instant,
150        /// Reload count.
151        count: u64,
152    },
153    /// Plugin was unloaded.
154    Unloaded {
155        /// Plugin name.
156        name: String,
157        /// Unload time.
158        at: Instant,
159    },
160    /// Plugin encountered an error.
161    Error {
162        /// Plugin name.
163        name: String,
164        /// Error message.
165        message: String,
166        /// Error time.
167        at: Instant,
168    },
169}
170
171impl LifecycleEvent {
172    /// Get the plugin name.
173    pub fn plugin_name(&self) -> &str {
174        match self {
175            Self::Created { name, .. } => name,
176            Self::Initialized { name, .. } => name,
177            Self::Started { name, .. } => name,
178            Self::Stopped { name, .. } => name,
179            Self::Reloaded { name, .. } => name,
180            Self::Unloaded { name, .. } => name,
181            Self::Error { name, .. } => name,
182        }
183    }
184
185    /// Get the event timestamp.
186    pub fn timestamp(&self) -> Instant {
187        match self {
188            Self::Created { at, .. } => *at,
189            Self::Initialized { at, .. } => *at,
190            Self::Started { at, .. } => *at,
191            Self::Stopped { at, .. } => *at,
192            Self::Reloaded { at, .. } => *at,
193            Self::Unloaded { at, .. } => *at,
194            Self::Error { at, .. } => *at,
195        }
196    }
197
198    /// Get the event name.
199    pub fn event_name(&self) -> &'static str {
200        match self {
201            Self::Created { .. } => "created",
202            Self::Initialized { .. } => "initialized",
203            Self::Started { .. } => "started",
204            Self::Stopped { .. } => "stopped",
205            Self::Reloaded { .. } => "reloaded",
206            Self::Unloaded { .. } => "unloaded",
207            Self::Error { .. } => "error",
208        }
209    }
210}
211
212/// Hooks for lifecycle events.
213pub struct LifecycleHooks {
214    handlers: Vec<Box<dyn Fn(&LifecycleEvent) + Send + Sync>>,
215}
216
217impl LifecycleHooks {
218    /// Create new lifecycle hooks.
219    pub fn new() -> Self {
220        Self {
221            handlers: Vec::new(),
222        }
223    }
224
225    /// Add a lifecycle event handler.
226    pub fn on_event<F>(&mut self, handler: F)
227    where
228        F: Fn(&LifecycleEvent) + Send + Sync + 'static,
229    {
230        self.handlers.push(Box::new(handler));
231    }
232
233    /// Emit a lifecycle event.
234    pub fn emit(&self, event: LifecycleEvent) {
235        for handler in &self.handlers {
236            handler(&event);
237        }
238    }
239
240    /// Emit a created event.
241    pub fn emit_created(&self, name: &str) {
242        self.emit(LifecycleEvent::Created {
243            name: name.to_string(),
244            at: Instant::now(),
245        });
246    }
247
248    /// Emit an initialized event.
249    pub fn emit_initialized(&self, name: &str) {
250        self.emit(LifecycleEvent::Initialized {
251            name: name.to_string(),
252            at: Instant::now(),
253        });
254    }
255
256    /// Emit a started event.
257    pub fn emit_started(&self, name: &str) {
258        self.emit(LifecycleEvent::Started {
259            name: name.to_string(),
260            at: Instant::now(),
261        });
262    }
263
264    /// Emit a stopped event.
265    pub fn emit_stopped(&self, name: &str) {
266        self.emit(LifecycleEvent::Stopped {
267            name: name.to_string(),
268            at: Instant::now(),
269        });
270    }
271
272    /// Emit a reloaded event.
273    pub fn emit_reloaded(&self, name: &str, count: u64) {
274        self.emit(LifecycleEvent::Reloaded {
275            name: name.to_string(),
276            at: Instant::now(),
277            count,
278        });
279    }
280
281    /// Emit an unloaded event.
282    pub fn emit_unloaded(&self, name: &str) {
283        self.emit(LifecycleEvent::Unloaded {
284            name: name.to_string(),
285            at: Instant::now(),
286        });
287    }
288
289    /// Emit an error event.
290    pub fn emit_error(&self, name: &str, message: &str) {
291        self.emit(LifecycleEvent::Error {
292            name: name.to_string(),
293            message: message.to_string(),
294            at: Instant::now(),
295        });
296    }
297}
298
299impl Default for LifecycleHooks {
300    fn default() -> Self {
301        Self::new()
302    }
303}
304
305impl std::fmt::Debug for LifecycleHooks {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        f.debug_struct("LifecycleHooks")
308            .field("handler_count", &self.handlers.len())
309            .finish()
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use std::sync::atomic::{AtomicUsize, Ordering};
317    use std::sync::Arc;
318
319    #[test]
320    fn test_lifecycle_state_transitions() {
321        assert!(LifecycleState::Initialized.can_start());
322        assert!(!LifecycleState::Created.can_start());
323
324        assert!(LifecycleState::Running.can_stop());
325        assert!(!LifecycleState::Stopped.can_stop());
326
327        assert!(LifecycleState::Running.can_call());
328        assert!(!LifecycleState::Stopped.can_call());
329
330        assert!(LifecycleState::Running.can_reload());
331        assert!(!LifecycleState::Unloaded.can_reload());
332
333        assert!(LifecycleState::Unloaded.is_terminal());
334        assert!(!LifecycleState::Running.is_terminal());
335    }
336
337    #[test]
338    fn test_lifecycle_hooks() {
339        let counter = Arc::new(AtomicUsize::new(0));
340        let counter_clone = counter.clone();
341
342        let mut hooks = LifecycleHooks::new();
343        hooks.on_event(move |_| {
344            counter_clone.fetch_add(1, Ordering::Relaxed);
345        });
346
347        hooks.emit_created("test");
348        hooks.emit_started("test");
349        hooks.emit_stopped("test");
350
351        assert_eq!(counter.load(Ordering::Relaxed), 3);
352    }
353
354    #[test]
355    fn test_lifecycle_event_info() {
356        let event = LifecycleEvent::Started {
357            name: "test-plugin".to_string(),
358            at: Instant::now(),
359        };
360
361        assert_eq!(event.plugin_name(), "test-plugin");
362        assert_eq!(event.event_name(), "started");
363    }
364}