Skip to main content

malbox_plugin_sdk/plugin/
host.rs

1//! The [`HostPlugin`] trait for plugins that run on the daemon host.
2//!
3//! Host plugins process tasks via [`on_task`](HostPlugin::on_task) and
4//! can subscribe to system events via
5//! [`on_event`](HostPlugin::on_event). The `#[malbox::handlers]` macro
6//! generates this impl from annotated methods on your plugin struct.
7
8use crate::context::Context;
9use crate::error::Result;
10use malbox_plugin_transport::messages::events::Event;
11use std::collections::HashMap;
12
13use super::Plugin;
14
15/// The trait that all malbox host plugins implement.
16///
17/// All methods have default implementations, but a useful plugin should
18/// at minimum implement `on_task`. The `#[malbox::handlers]` macro generates
19/// this impl from annotated methods on your plugin struct.
20pub trait HostPlugin: Plugin {
21    /// Process an analysis task.
22    ///
23    /// Emit results via [`Context::results().push()`](crate::context::ResultSink::push).
24    /// The runtime sends a final marker automatically once this method returns.
25    fn on_task(&self, ctx: &Context) -> Result<()> {
26        let _ = ctx;
27        Ok(())
28    }
29
30    /// Called once when the plugin process starts. `config` contains any
31    /// key-value settings the daemon passes to this plugin.
32    fn on_start(&self, config: HashMap<String, String>) -> Result<()> {
33        let _ = config;
34        Ok(())
35    }
36
37    /// Called once when the daemon is shutting down this plugin. Use it
38    /// to flush buffers, close connections, or clean up resources.
39    fn on_stop(&self) -> Result<()> {
40        Ok(())
41    }
42
43    /// Handle a system or plugin event.
44    ///
45    /// All events (task lifecycle, plugin lifecycle, system events, and
46    /// plugin result availability) are delivered through this single handler.
47    /// Use `#[malbox::on_event(...)]` with filters in the macro to route
48    /// specific events to specific methods.
49    fn on_event(&self, event: Event) -> Result<()> {
50        let _ = event;
51        Ok(())
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::result::PluginResult;
59    use std::collections::HashMap;
60    use std::sync::Arc;
61
62    struct MinimalPlugin;
63    impl Plugin for MinimalPlugin {}
64    impl HostPlugin for MinimalPlugin {}
65
66    #[test]
67    fn minimal_plugin_defaults_work() {
68        let plugin = MinimalPlugin;
69
70        assert!(plugin.on_start(HashMap::new()).is_ok());
71        assert!(plugin.on_stop().is_ok());
72
73        let health = plugin.health_check();
74        assert!(health.is_ready());
75    }
76
77    struct TaskOnlyPlugin;
78    impl Plugin for TaskOnlyPlugin {}
79    impl HostPlugin for TaskOnlyPlugin {
80        fn on_task(&self, ctx: &Context) -> Result<()> {
81            ctx.results()
82                .push(PluginResult::bytes("hello", vec![1, 2, 3]))?;
83            Ok(())
84        }
85    }
86
87    fn noop_emitter() -> Arc<dyn malbox_plugin_transport::traits::TransportEmitter + Send + Sync> {
88        Arc::new(())
89    }
90
91    #[test]
92    fn task_only_plugin_override_works() {
93        let plugin = TaskOnlyPlugin;
94        let (tx, mut rx) = tokio::sync::mpsc::channel(4);
95        let ctx = Context::new(
96            1,
97            std::path::PathBuf::new(),
98            HashMap::new(),
99            noop_emitter(),
100            Some(tx),
101            #[cfg(feature = "guest")]
102            None,
103        );
104
105        plugin.on_task(&ctx).expect("on_task should succeed");
106
107        let msg = rx.try_recv().expect("one result expected");
108        assert_eq!(msg.result_name, "hello");
109        assert_eq!(msg.data, vec![1, 2, 3]);
110    }
111
112    #[test]
113    fn on_event_default_is_noop() {
114        let plugin = MinimalPlugin;
115        let event = Event::TaskCreated { task_id: 42 };
116
117        let result = plugin.on_event(event);
118        assert!(result.is_ok());
119    }
120}