execution_engine/
config.rs1use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8
9#[cfg(feature = "server-feedback")]
10use std::sync::Arc;
11
12#[cfg(feature = "server-feedback")]
13use cloudops_network::NetworkClient;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum OversizedOutputStrategy {
18 TruncateWithWarning,
20 FailExecution,
22 StreamToFile,
24}
25
26#[derive(Clone)]
28pub struct ExecutionConfig {
29 pub default_timeout_ms: u64,
31
32 pub max_timeout_ms: u64,
34
35 pub stream_output: bool,
37
38 pub log_dir: Option<PathBuf>,
40
41 pub max_concurrent_executions: usize,
43
44 pub max_in_memory_executions: usize,
46
47 pub execution_retention_secs: u64,
49
50 pub enable_auto_cleanup: bool,
52
53 pub max_output_size_bytes: usize,
55
56 pub oversized_output_strategy: OversizedOutputStrategy,
58
59 pub enable_server_feedback: bool,
61
62 #[cfg(feature = "server-feedback")]
65 pub network_client: Option<Arc<NetworkClient>>,
66}
67
68impl std::fmt::Debug for ExecutionConfig {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("ExecutionConfig")
71 .field("default_timeout_ms", &self.default_timeout_ms)
72 .field("max_timeout_ms", &self.max_timeout_ms)
73 .field("stream_output", &self.stream_output)
74 .field("log_dir", &self.log_dir)
75 .field("max_concurrent_executions", &self.max_concurrent_executions)
76 .field("max_in_memory_executions", &self.max_in_memory_executions)
77 .field("execution_retention_secs", &self.execution_retention_secs)
78 .field("enable_auto_cleanup", &self.enable_auto_cleanup)
79 .field("max_output_size_bytes", &self.max_output_size_bytes)
80 .field("oversized_output_strategy", &self.oversized_output_strategy)
81 .field("enable_server_feedback", &self.enable_server_feedback)
82 .finish()
83 }
84}
85
86impl Default for ExecutionConfig {
87 fn default() -> Self {
88 Self {
89 default_timeout_ms: 300_000, max_timeout_ms: 3_600_000, stream_output: true,
92 log_dir: None,
93 max_concurrent_executions: 100,
94 max_in_memory_executions: 1_000,
95 execution_retention_secs: 3_600, enable_auto_cleanup: true,
97 max_output_size_bytes: 10_485_760, oversized_output_strategy: OversizedOutputStrategy::TruncateWithWarning,
99 enable_server_feedback: false,
100 #[cfg(feature = "server-feedback")]
101 network_client: None,
102 }
103 }
104}
105
106impl ExecutionConfig {
107 pub fn validate(&self) -> Result<(), String> {
122 if self.default_timeout_ms == 0 {
123 return Err("default_timeout_ms must be greater than 0".to_string());
124 }
125
126 if self.max_timeout_ms == 0 {
127 return Err("max_timeout_ms must be greater than 0".to_string());
128 }
129
130 if self.default_timeout_ms > self.max_timeout_ms {
131 return Err("default_timeout_ms cannot exceed max_timeout_ms".to_string());
132 }
133
134 if self.max_concurrent_executions == 0 {
135 return Err("max_concurrent_executions must be greater than 0".to_string());
136 }
137
138 if self.max_in_memory_executions == 0 {
139 return Err("max_in_memory_executions must be greater than 0".to_string());
140 }
141
142 if self.execution_retention_secs == 0 {
143 return Err("execution_retention_secs must be greater than 0".to_string());
144 }
145
146 if self.max_output_size_bytes == 0 {
147 return Err("max_output_size_bytes must be greater than 0".to_string());
148 }
149
150 Ok(())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_default_config() {
160 let config = ExecutionConfig::default();
161 assert_eq!(config.default_timeout_ms, 300_000);
162 assert_eq!(config.max_timeout_ms, 3_600_000);
163 assert!(config.stream_output);
164 assert!(config.log_dir.is_none());
165 assert_eq!(config.max_concurrent_executions, 100);
166 assert_eq!(config.max_in_memory_executions, 1_000);
167 assert_eq!(config.execution_retention_secs, 3_600);
168 assert!(config.enable_auto_cleanup);
169 assert_eq!(config.max_output_size_bytes, 10_485_760);
170 assert_eq!(
171 config.oversized_output_strategy,
172 OversizedOutputStrategy::TruncateWithWarning
173 );
174 }
175
176 #[test]
177 fn test_validate_success() {
178 let config = ExecutionConfig::default();
179 assert!(config.validate().is_ok());
180 }
181
182 #[test]
183 fn test_validate_default_timeout_zero() {
184 let mut config = ExecutionConfig::default();
185 config.default_timeout_ms = 0;
186 assert!(config.validate().is_err());
187 assert_eq!(
188 config.validate().unwrap_err(),
189 "default_timeout_ms must be greater than 0"
190 );
191 }
192
193 #[test]
194 fn test_validate_max_timeout_zero() {
195 let mut config = ExecutionConfig::default();
196 config.max_timeout_ms = 0;
197 assert!(config.validate().is_err());
198 assert_eq!(
199 config.validate().unwrap_err(),
200 "max_timeout_ms must be greater than 0"
201 );
202 }
203
204 #[test]
205 fn test_validate_default_exceeds_max() {
206 let mut config = ExecutionConfig::default();
207 config.default_timeout_ms = 1_000_000;
208 config.max_timeout_ms = 500_000;
209 assert!(config.validate().is_err());
210 assert_eq!(
211 config.validate().unwrap_err(),
212 "default_timeout_ms cannot exceed max_timeout_ms"
213 );
214 }
215
216 #[test]
217 fn test_validate_max_concurrent_zero() {
218 let mut config = ExecutionConfig::default();
219 config.max_concurrent_executions = 0;
220 assert!(config.validate().is_err());
221 assert_eq!(
222 config.validate().unwrap_err(),
223 "max_concurrent_executions must be greater than 0"
224 );
225 }
226
227 #[test]
228 fn test_validate_max_in_memory_zero() {
229 let mut config = ExecutionConfig::default();
230 config.max_in_memory_executions = 0;
231 assert!(config.validate().is_err());
232 assert_eq!(
233 config.validate().unwrap_err(),
234 "max_in_memory_executions must be greater than 0"
235 );
236 }
237
238 #[test]
239 fn test_validate_retention_zero() {
240 let mut config = ExecutionConfig::default();
241 config.execution_retention_secs = 0;
242 assert!(config.validate().is_err());
243 assert_eq!(
244 config.validate().unwrap_err(),
245 "execution_retention_secs must be greater than 0"
246 );
247 }
248
249 #[test]
250 fn test_validate_max_output_size_zero() {
251 let mut config = ExecutionConfig::default();
252 config.max_output_size_bytes = 0;
253 assert!(config.validate().is_err());
254 assert_eq!(
255 config.validate().unwrap_err(),
256 "max_output_size_bytes must be greater than 0"
257 );
258 }
259
260 #[test]
261 fn test_oversized_output_strategy_equality() {
262 assert_eq!(
263 OversizedOutputStrategy::TruncateWithWarning,
264 OversizedOutputStrategy::TruncateWithWarning
265 );
266 assert_ne!(
267 OversizedOutputStrategy::TruncateWithWarning,
268 OversizedOutputStrategy::FailExecution
269 );
270 assert_ne!(
271 OversizedOutputStrategy::FailExecution,
272 OversizedOutputStrategy::StreamToFile
273 );
274 }
275
276 #[test]
277 fn test_config_clone() {
278 let config = ExecutionConfig::default();
279 let cloned = config.clone();
280 assert_eq!(config.default_timeout_ms, cloned.default_timeout_ms);
281 assert_eq!(config.max_timeout_ms, cloned.max_timeout_ms);
282 }
283}