use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct ExecutionLimits {
pub max_steps: Option<usize>,
pub max_recursion_depth: Option<usize>,
pub max_duration_ms: Option<u64>,
pub max_memory_bytes: Option<usize>,
}
impl Default for ExecutionLimits {
fn default() -> Self {
Self {
max_steps: Some(1_000_000), max_recursion_depth: Some(1000), max_duration_ms: Some(30_000), max_memory_bytes: None,
}
}
}
impl ExecutionLimits {
pub fn unrestricted() -> Self {
Self {
max_steps: None,
max_recursion_depth: None,
max_duration_ms: None,
max_memory_bytes: None,
}
}
pub fn strict() -> Self {
Self {
max_steps: Some(100_000), max_recursion_depth: Some(100), max_duration_ms: Some(5_000), max_memory_bytes: None,
}
}
pub fn lenient() -> Self {
Self {
max_steps: Some(10_000_000), max_recursion_depth: Some(5000), max_duration_ms: Some(300_000), max_memory_bytes: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutionLimitError {
StepLimitExceeded { steps: usize, limit: usize },
RecursionDepthExceeded { depth: usize, limit: usize },
DurationExceeded { duration_ms: u64, limit: u64 },
MemoryLimitExceeded { bytes: usize, limit: usize },
}
impl fmt::Display for ExecutionLimitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExecutionLimitError::StepLimitExceeded { steps, limit } => write!(
f,
"Execution step limit exceeded: {} steps (limit: {})",
steps, limit
),
ExecutionLimitError::RecursionDepthExceeded { depth, limit } => write!(
f,
"Recursion depth limit exceeded: {} levels (limit: {})",
depth, limit
),
ExecutionLimitError::DurationExceeded { duration_ms, limit } => write!(
f,
"Execution duration limit exceeded: {} ms (limit: {} ms)",
duration_ms, limit
),
ExecutionLimitError::MemoryLimitExceeded { bytes, limit } => write!(
f,
"Memory limit exceeded: {} bytes (limit: {} bytes)",
bytes, limit
),
}
}
}
impl std::error::Error for ExecutionLimitError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_limits() {
let limits = ExecutionLimits::default();
assert_eq!(limits.max_steps, Some(1_000_000));
assert_eq!(limits.max_recursion_depth, Some(1000));
assert_eq!(limits.max_duration_ms, Some(30_000));
assert_eq!(limits.max_memory_bytes, None);
}
#[test]
fn test_unrestricted_limits() {
let limits = ExecutionLimits::unrestricted();
assert_eq!(limits.max_steps, None);
assert_eq!(limits.max_recursion_depth, None);
assert_eq!(limits.max_duration_ms, None);
assert_eq!(limits.max_memory_bytes, None);
}
#[test]
fn test_strict_limits() {
let limits = ExecutionLimits::strict();
assert_eq!(limits.max_steps, Some(100_000));
assert_eq!(limits.max_recursion_depth, Some(100));
assert_eq!(limits.max_duration_ms, Some(5_000));
assert_eq!(limits.max_memory_bytes, None);
}
#[test]
fn test_lenient_limits() {
let limits = ExecutionLimits::lenient();
assert_eq!(limits.max_steps, Some(10_000_000));
assert_eq!(limits.max_recursion_depth, Some(5000));
assert_eq!(limits.max_duration_ms, Some(300_000));
assert_eq!(limits.max_memory_bytes, None);
}
#[test]
fn test_error_display() {
let err = ExecutionLimitError::StepLimitExceeded {
steps: 1000,
limit: 100,
};
assert!(err.to_string().contains("1000"));
assert!(err.to_string().contains("100"));
let err = ExecutionLimitError::RecursionDepthExceeded {
depth: 500,
limit: 100,
};
assert!(err.to_string().contains("500"));
assert!(err.to_string().contains("100"));
let err = ExecutionLimitError::DurationExceeded {
duration_ms: 5000,
limit: 1000,
};
assert!(err.to_string().contains("5000"));
assert!(err.to_string().contains("1000"));
}
}