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 rustbridge.toml 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;