Skip to main content

heliosdb_proxy/plugins/
config.rs

1//! Plugin Configuration
2//!
3//! Configuration types for the WASM plugin system.
4
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::time::Duration;
8
9/// Configuration for the plugin runtime
10#[derive(Debug, Clone)]
11pub struct PluginRuntimeConfig {
12    /// Enable plugin system
13    pub enabled: bool,
14
15    /// Plugin directory
16    pub plugin_dir: PathBuf,
17
18    /// Hot reload on file change
19    pub hot_reload: bool,
20
21    /// Memory limit per plugin (bytes)
22    pub memory_limit: usize,
23
24    /// Execution timeout per call
25    pub timeout: Duration,
26
27    /// Maximum plugins loaded
28    pub max_plugins: usize,
29
30    /// Enable fuel metering (limits CPU cycles)
31    pub fuel_metering: bool,
32
33    /// Fuel limit per call (if fuel_metering enabled)
34    pub fuel_limit: u64,
35
36    /// Enable WASM SIMD
37    pub enable_simd: bool,
38
39    /// Enable multi-threading
40    pub enable_threads: bool,
41
42    /// Cache compiled modules
43    pub cache_modules: bool,
44
45    /// Module cache directory
46    pub cache_dir: Option<PathBuf>,
47
48    /// Per-plugin configurations
49    pub plugins: HashMap<String, PluginConfig>,
50
51    /// Optional Ed25519 trust root: directory of `*.pub` files. When
52    /// set, every loaded `.wasm` requires a sidecar `.sig` that
53    /// verifies against one of the keys. When `None`, signatures
54    /// aren't checked (preserves the dev-loop ergonomic of dropping
55    /// unsigned `.wasm` files in the plugin dir).
56    pub trust_root: Option<PathBuf>,
57}
58
59impl Default for PluginRuntimeConfig {
60    fn default() -> Self {
61        Self {
62            enabled: true,
63            plugin_dir: PathBuf::from("/etc/heliosproxy/plugins"),
64            hot_reload: false,
65            memory_limit: 64 * 1024 * 1024, // 64MB
66            timeout: Duration::from_millis(100),
67            max_plugins: 20,
68            fuel_metering: true,
69            fuel_limit: 1_000_000,
70            enable_simd: true,
71            enable_threads: false,
72            cache_modules: true,
73            cache_dir: None,
74            plugins: HashMap::new(),
75            trust_root: None,
76        }
77    }
78}
79
80/// Convert the TOML-shaped `PluginToml` into a live `PluginRuntimeConfig`.
81///
82/// Values that aren't exposed in TOML (SIMD, threading, module cache)
83/// take their runtime defaults.
84impl From<&crate::config::PluginToml> for PluginRuntimeConfig {
85    fn from(t: &crate::config::PluginToml) -> Self {
86        Self {
87            enabled: t.enabled,
88            plugin_dir: PathBuf::from(&t.plugin_dir),
89            hot_reload: t.hot_reload,
90            memory_limit: t.memory_limit_mb.saturating_mul(1024 * 1024),
91            timeout: Duration::from_millis(t.timeout_ms),
92            max_plugins: t.max_plugins,
93            fuel_metering: t.fuel_metering,
94            fuel_limit: t.fuel_limit,
95            enable_simd: true,
96            enable_threads: false,
97            cache_modules: true,
98            cache_dir: None,
99            plugins: HashMap::new(),
100            trust_root: t.trust_root.as_ref().map(PathBuf::from),
101        }
102    }
103}
104
105/// Builder for PluginRuntimeConfig
106pub struct PluginRuntimeConfigBuilder {
107    config: PluginRuntimeConfig,
108}
109
110impl PluginRuntimeConfigBuilder {
111    /// Create a new builder
112    pub fn new() -> Self {
113        Self {
114            config: PluginRuntimeConfig::default(),
115        }
116    }
117
118    /// Set enabled
119    pub fn enabled(mut self, enabled: bool) -> Self {
120        self.config.enabled = enabled;
121        self
122    }
123
124    /// Set plugin directory
125    pub fn plugin_dir(mut self, dir: PathBuf) -> Self {
126        self.config.plugin_dir = dir;
127        self
128    }
129
130    /// Set hot reload
131    pub fn hot_reload(mut self, enabled: bool) -> Self {
132        self.config.hot_reload = enabled;
133        self
134    }
135
136    /// Set memory limit
137    pub fn memory_limit(mut self, limit: usize) -> Self {
138        self.config.memory_limit = limit;
139        self
140    }
141
142    /// Set timeout
143    pub fn timeout(mut self, timeout: Duration) -> Self {
144        self.config.timeout = timeout;
145        self
146    }
147
148    /// Set max plugins
149    pub fn max_plugins(mut self, max: usize) -> Self {
150        self.config.max_plugins = max;
151        self
152    }
153
154    /// Set fuel metering
155    pub fn fuel_metering(mut self, enabled: bool) -> Self {
156        self.config.fuel_metering = enabled;
157        self
158    }
159
160    /// Set fuel limit
161    pub fn fuel_limit(mut self, limit: u64) -> Self {
162        self.config.fuel_limit = limit;
163        self
164    }
165
166    /// Enable SIMD
167    pub fn enable_simd(mut self, enabled: bool) -> Self {
168        self.config.enable_simd = enabled;
169        self
170    }
171
172    /// Enable threads
173    pub fn enable_threads(mut self, enabled: bool) -> Self {
174        self.config.enable_threads = enabled;
175        self
176    }
177
178    /// Enable module caching
179    pub fn cache_modules(mut self, enabled: bool) -> Self {
180        self.config.cache_modules = enabled;
181        self
182    }
183
184    /// Set cache directory
185    pub fn cache_dir(mut self, dir: PathBuf) -> Self {
186        self.config.cache_dir = Some(dir);
187        self
188    }
189
190    /// Add plugin config
191    pub fn add_plugin(mut self, name: String, config: PluginConfig) -> Self {
192        self.config.plugins.insert(name, config);
193        self
194    }
195
196    /// Build the config
197    pub fn build(self) -> PluginRuntimeConfig {
198        self.config
199    }
200}
201
202impl Default for PluginRuntimeConfigBuilder {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208/// Per-plugin configuration
209#[derive(Debug, Clone)]
210pub struct PluginConfig {
211    /// Enable this plugin
212    pub enabled: bool,
213
214    /// Plugin priority (higher = earlier execution)
215    pub priority: i32,
216
217    /// Custom config passed to plugin
218    pub config: HashMap<String, serde_json::Value>,
219
220    /// Override memory limit
221    pub memory_limit: Option<usize>,
222
223    /// Override timeout
224    pub timeout: Option<Duration>,
225
226    /// Override fuel limit
227    pub fuel_limit: Option<u64>,
228
229    /// Allowed permissions
230    pub permissions: Vec<String>,
231}
232
233impl Default for PluginConfig {
234    fn default() -> Self {
235        Self {
236            enabled: true,
237            priority: 0,
238            config: HashMap::new(),
239            memory_limit: None,
240            timeout: None,
241            fuel_limit: None,
242            permissions: Vec::new(),
243        }
244    }
245}
246
247/// Builder for PluginConfig
248pub struct PluginConfigBuilder {
249    config: PluginConfig,
250}
251
252impl PluginConfigBuilder {
253    /// Create new builder
254    pub fn new() -> Self {
255        Self {
256            config: PluginConfig::default(),
257        }
258    }
259
260    /// Set enabled
261    pub fn enabled(mut self, enabled: bool) -> Self {
262        self.config.enabled = enabled;
263        self
264    }
265
266    /// Set priority
267    pub fn priority(mut self, priority: i32) -> Self {
268        self.config.priority = priority;
269        self
270    }
271
272    /// Add config value
273    pub fn config_value(mut self, key: &str, value: serde_json::Value) -> Self {
274        self.config.config.insert(key.to_string(), value);
275        self
276    }
277
278    /// Set memory limit
279    pub fn memory_limit(mut self, limit: usize) -> Self {
280        self.config.memory_limit = Some(limit);
281        self
282    }
283
284    /// Set timeout
285    pub fn timeout(mut self, timeout: Duration) -> Self {
286        self.config.timeout = Some(timeout);
287        self
288    }
289
290    /// Set fuel limit
291    pub fn fuel_limit(mut self, limit: u64) -> Self {
292        self.config.fuel_limit = Some(limit);
293        self
294    }
295
296    /// Add permission
297    pub fn permission(mut self, permission: &str) -> Self {
298        self.config.permissions.push(permission.to_string());
299        self
300    }
301
302    /// Build
303    pub fn build(self) -> PluginConfig {
304        self.config
305    }
306}
307
308impl Default for PluginConfigBuilder {
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    #[test]
319    fn test_runtime_config_default() {
320        let config = PluginRuntimeConfig::default();
321        assert!(config.enabled);
322        assert!(!config.hot_reload);
323        assert_eq!(config.memory_limit, 64 * 1024 * 1024);
324        assert_eq!(config.max_plugins, 20);
325    }
326
327    #[test]
328    fn test_runtime_config_builder() {
329        let config = PluginRuntimeConfigBuilder::new()
330            .enabled(true)
331            .hot_reload(true)
332            .memory_limit(128 * 1024 * 1024)
333            .timeout(Duration::from_millis(200))
334            .max_plugins(50)
335            .fuel_metering(true)
336            .fuel_limit(2_000_000)
337            .build();
338
339        assert!(config.enabled);
340        assert!(config.hot_reload);
341        assert_eq!(config.memory_limit, 128 * 1024 * 1024);
342        assert_eq!(config.timeout, Duration::from_millis(200));
343        assert_eq!(config.max_plugins, 50);
344    }
345
346    #[test]
347    fn test_plugin_config_default() {
348        let config = PluginConfig::default();
349        assert!(config.enabled);
350        assert_eq!(config.priority, 0);
351        assert!(config.permissions.is_empty());
352    }
353
354    #[test]
355    fn test_plugin_config_builder() {
356        let config = PluginConfigBuilder::new()
357            .enabled(true)
358            .priority(100)
359            .config_value("key", serde_json::json!("value"))
360            .memory_limit(32 * 1024 * 1024)
361            .permission("http_fetch")
362            .permission("cache_read")
363            .build();
364
365        assert!(config.enabled);
366        assert_eq!(config.priority, 100);
367        assert_eq!(config.config.get("key"), Some(&serde_json::json!("value")));
368        assert_eq!(config.memory_limit, Some(32 * 1024 * 1024));
369        assert_eq!(config.permissions.len(), 2);
370    }
371}