Skip to main content

rustbridge_core/
plugin.rs

1//! Plugin trait and context types
2
3use crate::{LifecycleState, PluginConfig, PluginError, PluginResult};
4use async_trait::async_trait;
5
6/// Context provided to plugin operations
7pub struct PluginContext {
8    /// Plugin configuration
9    pub config: PluginConfig,
10    /// Current lifecycle state
11    state: std::sync::atomic::AtomicU8,
12}
13
14impl PluginContext {
15    /// Create a new plugin context
16    pub fn new(config: PluginConfig) -> Self {
17        Self {
18            config,
19            state: std::sync::atomic::AtomicU8::new(LifecycleState::Installed as u8),
20        }
21    }
22
23    /// Get current lifecycle state
24    pub fn state(&self) -> LifecycleState {
25        let value = self.state.load(std::sync::atomic::Ordering::SeqCst);
26        match value {
27            0 => LifecycleState::Installed,
28            1 => LifecycleState::Starting,
29            2 => LifecycleState::Active,
30            3 => LifecycleState::Stopping,
31            4 => LifecycleState::Stopped,
32            _ => LifecycleState::Failed,
33        }
34    }
35
36    /// Set lifecycle state directly (bypassing transition validation)
37    ///
38    /// Use this for error recovery scenarios where normal transitions don't apply.
39    pub fn set_state(&self, state: LifecycleState) {
40        let value = match state {
41            LifecycleState::Installed => 0,
42            LifecycleState::Starting => 1,
43            LifecycleState::Active => 2,
44            LifecycleState::Stopping => 3,
45            LifecycleState::Stopped => 4,
46            LifecycleState::Failed => 5,
47        };
48        self.state.store(value, std::sync::atomic::Ordering::SeqCst);
49    }
50
51    /// Attempt to transition to a new state
52    pub fn transition_to(&self, target: LifecycleState) -> PluginResult<()> {
53        let current = self.state();
54        if current.can_transition_to(target) {
55            self.set_state(target);
56            Ok(())
57        } else {
58            Err(PluginError::InvalidState {
59                expected: format!("state that can transition to {}", target),
60                actual: current.to_string(),
61            })
62        }
63    }
64}
65
66/// Main trait for implementing rustbridge plugins
67///
68/// Plugins must implement this trait to define their behavior.
69/// The async methods are executed on the Tokio runtime.
70///
71/// # Example
72///
73/// ```ignore
74/// use rustbridge_core::prelude::*;
75///
76/// struct MyPlugin;
77///
78/// #[async_trait::async_trait]
79/// impl Plugin for MyPlugin {
80///     async fn on_start(&self, ctx: &PluginContext) -> PluginResult<()> {
81///         // Initialize resources
82///         Ok(())
83///     }
84///
85///     async fn handle_request(
86///         &self,
87///         ctx: &PluginContext,
88///         type_tag: &str,
89///         payload: &[u8],
90///     ) -> PluginResult<Vec<u8>> {
91///         match type_tag {
92///             "echo" => Ok(payload.to_vec()),
93///             _ => Err(PluginError::UnknownMessageType(type_tag.to_string())),
94///         }
95///     }
96///
97///     async fn on_stop(&self, ctx: &PluginContext) -> PluginResult<()> {
98///         // Cleanup resources
99///         Ok(())
100///     }
101/// }
102/// ```
103#[async_trait]
104pub trait Plugin: Send + Sync + 'static {
105    /// Called when the plugin is starting up
106    ///
107    /// Use this to initialize resources, connections, etc.
108    /// The plugin transitions to Active state after this returns successfully.
109    async fn on_start(&self, ctx: &PluginContext) -> PluginResult<()>;
110
111    /// Handle an incoming request
112    ///
113    /// - `type_tag`: Message type identifier (e.g., "user.create")
114    /// - `payload`: JSON-encoded request payload
115    ///
116    /// Returns JSON-encoded response payload
117    async fn handle_request(
118        &self,
119        ctx: &PluginContext,
120        type_tag: &str,
121        payload: &[u8],
122    ) -> PluginResult<Vec<u8>>;
123
124    /// Called when the plugin is shutting down
125    ///
126    /// Use this to cleanup resources, close connections, etc.
127    /// The plugin transitions to Stopped state after this returns.
128    async fn on_stop(&self, ctx: &PluginContext) -> PluginResult<()>;
129
130    /// Get plugin metadata
131    ///
132    /// Override this to provide plugin information
133    fn metadata(&self) -> Option<crate::PluginMetadata> {
134        None
135    }
136
137    /// List supported message types
138    ///
139    /// Override this to provide a list of supported type tags
140    fn supported_types(&self) -> Vec<&'static str> {
141        Vec::new()
142    }
143}
144
145/// Factory trait for creating plugins with optional configuration.
146///
147/// The framework calls `create()` which handles the null-config case
148/// automatically by falling back to `Default::default()`.
149///
150/// Plugin authors only need to override `create_configured()` if they
151/// want to use config.data for initialization.
152///
153/// # Example
154///
155/// ```ignore
156/// use rustbridge_core::prelude::*;
157///
158/// struct MyPlugin {
159///     cache_size: usize,
160/// }
161///
162/// impl Default for MyPlugin {
163///     fn default() -> Self {
164///         Self { cache_size: 100 }
165///     }
166/// }
167///
168/// impl PluginFactory for MyPlugin {
169///     fn create_configured(config: &PluginConfig) -> PluginResult<Self> {
170///         let size = config.get::<usize>("cache_size").unwrap_or(100);
171///         Ok(Self { cache_size: size })
172///     }
173/// }
174/// ```
175pub trait PluginFactory: Default + Plugin + Sized {
176    /// Create a plugin instance. Called by the framework.
177    ///
178    /// Default implementation: uses `Default` when config.data is null,
179    /// otherwise calls `create_configured()`.
180    fn create(config: &PluginConfig) -> PluginResult<Self> {
181        if config.data.is_null() {
182            Ok(Self::default())
183        } else {
184            Self::create_configured(config)
185        }
186    }
187
188    /// Create a plugin with non-null config.data.
189    ///
190    /// Override this to parse and use configuration data.
191    /// Default implementation ignores config and uses `Default`.
192    fn create_configured(_config: &PluginConfig) -> PluginResult<Self> {
193        Ok(Self::default())
194    }
195}
196
197#[cfg(test)]
198#[path = "plugin/plugin_tests.rs"]
199mod plugin_tests;