Skip to main content

camel_component_wasm/
config.rs

1//! WASM plugin configuration: timeouts, memory limits, epoch settings.
2
3use std::time::Duration;
4
5/// Default execution timeout in seconds.
6const DEFAULT_TIMEOUT_SECS: u64 = 30;
7
8/// Default maximum linear memory in bytes (50 MB).
9const DEFAULT_MAX_MEMORY_BYTES: u64 = 50 * 1024 * 1024;
10
11/// Epoch tick interval in milliseconds (same as Surrealism).
12const EPOCH_INTERVAL_MILLIS: u64 = 10;
13
14/// Configuration for a WASM plugin instance.
15///
16/// Parsed from URI query parameters or Camel.toml.
17/// Example URI: `wasm:plugin.wasm?timeout=10&max-memory=52428800`
18#[derive(Debug, Clone)]
19pub struct WasmConfig {
20    /// Maximum execution time per guest call, in seconds.
21    pub timeout_secs: u64,
22
23    /// Maximum linear memory the guest can allocate, in bytes.
24    pub max_memory_bytes: u64,
25}
26
27impl Default for WasmConfig {
28    fn default() -> Self {
29        Self {
30            timeout_secs: DEFAULT_TIMEOUT_SECS,
31            max_memory_bytes: DEFAULT_MAX_MEMORY_BYTES,
32        }
33    }
34}
35
36impl WasmConfig {
37    /// Parse `WasmConfig` from the query portion of a WASM URI.
38    ///
39    /// `uri_without_scheme` is everything after `wasm:`, e.g.
40    /// `plugins/my_processor.wasm?timeout=10&max-memory=52428800`.
41    ///
42    /// Returns `(path, config)` where path has no query string.
43    pub fn from_uri(uri_without_scheme: &str) -> (String, WasmConfig) {
44        let (path, query) = match uri_without_scheme.find('?') {
45            Some(i) => (&uri_without_scheme[..i], Some(&uri_without_scheme[i + 1..])),
46            None => (uri_without_scheme, None),
47        };
48
49        let mut config = WasmConfig::default();
50
51        if let Some(q) = query {
52            for pair in q.split('&') {
53                if let Some((key, value)) = pair.split_once('=') {
54                    match key {
55                        "timeout" => {
56                            if let Ok(secs) = value.parse::<u64>()
57                                && secs > 0
58                            {
59                                config.timeout_secs = secs;
60                            }
61                        }
62                        "max-memory" => {
63                            if let Ok(bytes) = value.parse::<u64>()
64                                && bytes > 0
65                            {
66                                config.max_memory_bytes = bytes;
67                            }
68                        }
69                        _ => {} // ignore unknown params
70                    }
71                }
72            }
73        }
74
75        (path.to_string(), config)
76    }
77
78    /// Convert the wall-clock timeout to an epoch deadline (number of ticks).
79    ///
80    /// At 10ms per tick: deadline = timeout_secs * 100
81    pub fn epoch_deadline(&self) -> u64 {
82        self.timeout_secs * (1000 / EPOCH_INTERVAL_MILLIS)
83    }
84
85    /// The interval at which the epoch ticker thread increments the epoch.
86    pub fn epoch_interval(&self) -> Duration {
87        Duration::from_millis(EPOCH_INTERVAL_MILLIS)
88    }
89
90    /// Returns the configured epoch interval in milliseconds.
91    pub fn epoch_interval_millis(&self) -> u64 {
92        EPOCH_INTERVAL_MILLIS
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_default_config() {
102        let config = WasmConfig::default();
103        assert_eq!(config.timeout_secs, 30);
104        assert_eq!(config.max_memory_bytes, 50 * 1024 * 1024);
105    }
106
107    #[test]
108    fn test_from_uri_no_params() {
109        let (path, config) = WasmConfig::from_uri("plugins/test.wasm");
110        assert_eq!(path, "plugins/test.wasm");
111        assert_eq!(config.timeout_secs, 30);
112        assert_eq!(config.max_memory_bytes, 50 * 1024 * 1024);
113    }
114
115    #[test]
116    fn test_from_uri_with_timeout() {
117        let (path, config) = WasmConfig::from_uri("plugins/test.wasm?timeout=10");
118        assert_eq!(path, "plugins/test.wasm");
119        assert_eq!(config.timeout_secs, 10);
120        assert_eq!(config.max_memory_bytes, 50 * 1024 * 1024);
121    }
122
123    #[test]
124    fn test_from_uri_with_max_memory() {
125        let (path, config) = WasmConfig::from_uri("plugins/test.wasm?max-memory=10485760");
126        assert_eq!(path, "plugins/test.wasm");
127        assert_eq!(config.timeout_secs, 30);
128        assert_eq!(config.max_memory_bytes, 10_485_760);
129    }
130
131    #[test]
132    fn test_from_uri_with_both_params() {
133        let (path, config) = WasmConfig::from_uri("plugins/test.wasm?timeout=5&max-memory=1048576");
134        assert_eq!(path, "plugins/test.wasm");
135        assert_eq!(config.timeout_secs, 5);
136        assert_eq!(config.max_memory_bytes, 1_048_576);
137    }
138
139    #[test]
140    fn test_from_uri_ignores_unknown_params() {
141        let (path, config) = WasmConfig::from_uri("plugins/test.wasm?foo=bar&timeout=60");
142        assert_eq!(path, "plugins/test.wasm");
143        assert_eq!(config.timeout_secs, 60);
144    }
145
146    #[test]
147    fn test_from_uri_ignores_invalid_values() {
148        let (_path, config) = WasmConfig::from_uri("plugins/test.wasm?timeout=abc");
149        assert_eq!(config.timeout_secs, 30); // stays default
150    }
151
152    #[test]
153    fn test_from_uri_ignores_zero_values() {
154        let (_path, config) = WasmConfig::from_uri("plugins/test.wasm?timeout=0&max-memory=0");
155        assert_eq!(config.timeout_secs, 30); // stays default
156        assert_eq!(config.max_memory_bytes, 50 * 1024 * 1024); // stays default
157    }
158
159    #[test]
160    fn test_epoch_deadline() {
161        let config = WasmConfig {
162            timeout_secs: 30,
163            max_memory_bytes: 0,
164        };
165        assert_eq!(config.epoch_deadline(), 3000); // 30s * 100 ticks/s
166    }
167
168    #[test]
169    fn test_epoch_deadline_custom_timeout() {
170        let config = WasmConfig {
171            timeout_secs: 5,
172            max_memory_bytes: 0,
173        };
174        assert_eq!(config.epoch_deadline(), 500);
175    }
176
177    #[test]
178    fn test_epoch_interval() {
179        let config = WasmConfig::default();
180        assert_eq!(config.epoch_interval(), Duration::from_millis(10));
181    }
182}