Skip to main content

rustbridge_core/
config.rs

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