Skip to main content

rustbridge_core/
config.rs

1//! Plugin configuration types
2
3use crate::LogLevel;
4use serde::{Deserialize, Serialize};
5
6/// Plugin configuration passed during initialization
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct PluginConfig {
9    /// Plugin-specific configuration data
10    ///
11    /// General runtime configuration such as database URLs, API keys, feature flags, etc.
12    /// Accessible throughout the plugin's lifetime.
13    #[serde(default)]
14    pub data: serde_json::Value,
15
16    /// Initialization parameters
17    ///
18    /// Structured data passed to the plugin during initialization.
19    /// Intended for one-time setup parameters that are only needed during `on_start()`.
20    /// Provides better separation from runtime configuration in `data`.
21    #[serde(default)]
22    pub init_params: Option<serde_json::Value>,
23
24    /// Number of async worker threads (default: number of CPU cores)
25    #[serde(default)]
26    pub worker_threads: Option<usize>,
27
28    /// Initial log level (default: Info)
29    #[serde(default)]
30    pub log_level: LogLevel,
31
32    /// Maximum concurrent async operations
33    #[serde(default = "default_max_concurrent")]
34    pub max_concurrent_ops: usize,
35
36    /// Shutdown timeout in milliseconds
37    #[serde(default = "default_shutdown_timeout")]
38    pub shutdown_timeout_ms: u64,
39}
40
41fn default_max_concurrent() -> usize {
42    1000
43}
44
45fn default_shutdown_timeout() -> u64 {
46    5000
47}
48
49impl Default for PluginConfig {
50    fn default() -> Self {
51        Self {
52            data: serde_json::Value::Null,
53            init_params: None,
54            worker_threads: None,
55            log_level: LogLevel::default(),
56            max_concurrent_ops: default_max_concurrent(),
57            shutdown_timeout_ms: default_shutdown_timeout(),
58        }
59    }
60}
61
62impl PluginConfig {
63    /// Create a new empty configuration
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Create configuration from JSON bytes
69    pub fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
70        if bytes.is_empty() {
71            return Ok(Self::default());
72        }
73        serde_json::from_slice(bytes)
74    }
75
76    /// Get a typed value from the configuration data
77    pub fn get<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
78        self.data
79            .get(key)
80            .and_then(|v| serde_json::from_value(v.clone()).ok())
81    }
82
83    /// Set a value in the configuration data
84    pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), serde_json::Error> {
85        // Ensure data is an object
86        if !self.data.is_object() {
87            self.data = serde_json::json!({});
88        }
89        // Now we can safely get the object
90        #[allow(clippy::unwrap_used)] // Safe: we just set data to an empty object above
91        let obj = self.data.as_object_mut().unwrap();
92        obj.insert(key.to_string(), serde_json::to_value(value)?);
93        Ok(())
94    }
95
96    /// Get a typed value from initialization parameters
97    ///
98    /// Returns `None` if init_params is not set or the key doesn't exist.
99    ///
100    /// # Example
101    ///
102    /// ```ignore
103    /// #[derive(Deserialize)]
104    /// struct DatabaseInit {
105    ///     migrations_path: String,
106    ///     seed_data: bool,
107    /// }
108    ///
109    /// async fn on_start(&self, ctx: &PluginContext) -> PluginResult<()> {
110    ///     if let Some(db_init) = ctx.config().get_init_param::<DatabaseInit>("database") {
111    ///         if db_init.seed_data {
112    ///             self.seed_database(&db_init.migrations_path).await?;
113    ///         }
114    ///     }
115    ///     Ok(())
116    /// }
117    /// ```
118    pub fn get_init_param<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
119        self.init_params
120            .as_ref()?
121            .get(key)
122            .and_then(|v| serde_json::from_value(v.clone()).ok())
123    }
124
125    /// Get the entire initialization parameters as a typed value
126    ///
127    /// Returns `None` if init_params is not set.
128    ///
129    /// # Example
130    ///
131    /// ```ignore
132    /// #[derive(Deserialize)]
133    /// struct InitParams {
134    ///     setup_mode: String,
135    ///     enable_features: Vec<String>,
136    /// }
137    ///
138    /// async fn on_start(&self, ctx: &PluginContext) -> PluginResult<()> {
139    ///     if let Some(params) = ctx.config().init_params_as::<InitParams>() {
140    ///         // Use initialization parameters...
141    ///     }
142    ///     Ok(())
143    /// }
144    /// ```
145    pub fn init_params_as<T: for<'de> Deserialize<'de>>(&self) -> Option<T> {
146        let init_params = self.init_params.as_ref()?;
147        serde_json::from_value(init_params.clone()).ok()
148    }
149
150    /// Set initialization parameters
151    ///
152    /// This is typically called by the host application before initializing the plugin.
153    ///
154    /// # Example
155    ///
156    /// ```ignore
157    /// let mut config = PluginConfig::default();
158    /// config.set_init_params(serde_json::json!({
159    ///     "setup_mode": "development",
160    ///     "seed_data": true
161    /// }));
162    /// ```
163    pub fn set_init_params(&mut self, params: serde_json::Value) {
164        self.init_params = Some(params);
165    }
166}
167
168/// Plugin metadata from bundle manifest
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct PluginMetadata {
171    /// Plugin name
172    pub name: String,
173
174    /// Plugin version (semver)
175    pub version: String,
176
177    /// Plugin description
178    #[serde(default)]
179    pub description: Option<String>,
180
181    /// Plugin author(s)
182    #[serde(default)]
183    pub authors: Vec<String>,
184
185    /// Minimum rustbridge version required
186    #[serde(default)]
187    pub min_rustbridge_version: Option<String>,
188}
189
190impl PluginMetadata {
191    /// Create new plugin metadata
192    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
193        Self {
194            name: name.into(),
195            version: version.into(),
196            description: None,
197            authors: Vec::new(),
198            min_rustbridge_version: None,
199        }
200    }
201}
202
203#[cfg(test)]
204#[path = "config/config_tests.rs"]
205mod config_tests;
206
207#[cfg(test)]
208#[path = "config/config_parameterized_tests.rs"]
209mod config_parameterized_tests;