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;