use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[cfg(feature = "server-feedback")]
use std::sync::Arc;
#[cfg(feature = "server-feedback")]
use cloudops_network::NetworkClient;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OversizedOutputStrategy {
TruncateWithWarning,
FailExecution,
StreamToFile,
}
#[derive(Clone)]
pub struct ExecutionConfig {
pub default_timeout_ms: u64,
pub max_timeout_ms: u64,
pub stream_output: bool,
pub log_dir: Option<PathBuf>,
pub max_concurrent_executions: usize,
pub max_in_memory_executions: usize,
pub execution_retention_secs: u64,
pub enable_auto_cleanup: bool,
pub max_output_size_bytes: usize,
pub oversized_output_strategy: OversizedOutputStrategy,
pub enable_server_feedback: bool,
#[cfg(feature = "server-feedback")]
pub network_client: Option<Arc<NetworkClient>>,
}
impl std::fmt::Debug for ExecutionConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExecutionConfig")
.field("default_timeout_ms", &self.default_timeout_ms)
.field("max_timeout_ms", &self.max_timeout_ms)
.field("stream_output", &self.stream_output)
.field("log_dir", &self.log_dir)
.field("max_concurrent_executions", &self.max_concurrent_executions)
.field("max_in_memory_executions", &self.max_in_memory_executions)
.field("execution_retention_secs", &self.execution_retention_secs)
.field("enable_auto_cleanup", &self.enable_auto_cleanup)
.field("max_output_size_bytes", &self.max_output_size_bytes)
.field("oversized_output_strategy", &self.oversized_output_strategy)
.field("enable_server_feedback", &self.enable_server_feedback)
.finish()
}
}
impl Default for ExecutionConfig {
fn default() -> Self {
Self {
default_timeout_ms: 300_000, max_timeout_ms: 3_600_000, stream_output: true,
log_dir: None,
max_concurrent_executions: 100,
max_in_memory_executions: 1_000,
execution_retention_secs: 3_600, enable_auto_cleanup: true,
max_output_size_bytes: 10_485_760, oversized_output_strategy: OversizedOutputStrategy::TruncateWithWarning,
enable_server_feedback: false,
#[cfg(feature = "server-feedback")]
network_client: None,
}
}
}
impl ExecutionConfig {
pub fn validate(&self) -> Result<(), String> {
if self.default_timeout_ms == 0 {
return Err("default_timeout_ms must be greater than 0".to_string());
}
if self.max_timeout_ms == 0 {
return Err("max_timeout_ms must be greater than 0".to_string());
}
if self.default_timeout_ms > self.max_timeout_ms {
return Err("default_timeout_ms cannot exceed max_timeout_ms".to_string());
}
if self.max_concurrent_executions == 0 {
return Err("max_concurrent_executions must be greater than 0".to_string());
}
if self.max_in_memory_executions == 0 {
return Err("max_in_memory_executions must be greater than 0".to_string());
}
if self.execution_retention_secs == 0 {
return Err("execution_retention_secs must be greater than 0".to_string());
}
if self.max_output_size_bytes == 0 {
return Err("max_output_size_bytes must be greater than 0".to_string());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ExecutionConfig::default();
assert_eq!(config.default_timeout_ms, 300_000);
assert_eq!(config.max_timeout_ms, 3_600_000);
assert!(config.stream_output);
assert!(config.log_dir.is_none());
assert_eq!(config.max_concurrent_executions, 100);
assert_eq!(config.max_in_memory_executions, 1_000);
assert_eq!(config.execution_retention_secs, 3_600);
assert!(config.enable_auto_cleanup);
assert_eq!(config.max_output_size_bytes, 10_485_760);
assert_eq!(
config.oversized_output_strategy,
OversizedOutputStrategy::TruncateWithWarning
);
}
#[test]
fn test_validate_success() {
let config = ExecutionConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_validate_default_timeout_zero() {
let mut config = ExecutionConfig::default();
config.default_timeout_ms = 0;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"default_timeout_ms must be greater than 0"
);
}
#[test]
fn test_validate_max_timeout_zero() {
let mut config = ExecutionConfig::default();
config.max_timeout_ms = 0;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"max_timeout_ms must be greater than 0"
);
}
#[test]
fn test_validate_default_exceeds_max() {
let mut config = ExecutionConfig::default();
config.default_timeout_ms = 1_000_000;
config.max_timeout_ms = 500_000;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"default_timeout_ms cannot exceed max_timeout_ms"
);
}
#[test]
fn test_validate_max_concurrent_zero() {
let mut config = ExecutionConfig::default();
config.max_concurrent_executions = 0;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"max_concurrent_executions must be greater than 0"
);
}
#[test]
fn test_validate_max_in_memory_zero() {
let mut config = ExecutionConfig::default();
config.max_in_memory_executions = 0;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"max_in_memory_executions must be greater than 0"
);
}
#[test]
fn test_validate_retention_zero() {
let mut config = ExecutionConfig::default();
config.execution_retention_secs = 0;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"execution_retention_secs must be greater than 0"
);
}
#[test]
fn test_validate_max_output_size_zero() {
let mut config = ExecutionConfig::default();
config.max_output_size_bytes = 0;
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"max_output_size_bytes must be greater than 0"
);
}
#[test]
fn test_oversized_output_strategy_equality() {
assert_eq!(
OversizedOutputStrategy::TruncateWithWarning,
OversizedOutputStrategy::TruncateWithWarning
);
assert_ne!(
OversizedOutputStrategy::TruncateWithWarning,
OversizedOutputStrategy::FailExecution
);
assert_ne!(
OversizedOutputStrategy::FailExecution,
OversizedOutputStrategy::StreamToFile
);
}
#[test]
fn test_config_clone() {
let config = ExecutionConfig::default();
let cloned = config.clone();
assert_eq!(config.default_timeout_ms, cloned.default_timeout_ms);
assert_eq!(config.max_timeout_ms, cloned.max_timeout_ms);
}
}