use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum RestartStrategy {
#[default]
OneForOne,
OneForAll,
RestForOne,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestartIntensity {
pub max_restarts: u32,
pub time_window: Duration,
}
impl Default for RestartIntensity {
fn default() -> Self {
Self {
max_restarts: 5,
time_window: Duration::from_secs(60),
}
}
}
impl RestartIntensity {
pub fn new(max_restarts: u32, time_window: Duration) -> Self {
Self {
max_restarts,
time_window,
}
}
pub fn lenient() -> Self {
Self {
max_restarts: 10,
time_window: Duration::from_secs(120),
}
}
pub fn strict() -> Self {
Self {
max_restarts: 3,
time_window: Duration::from_secs(30),
}
}
pub fn never() -> Self {
Self {
max_restarts: 0,
time_window: Duration::from_secs(1),
}
}
pub fn is_restart_allowed(
&self,
restart_count: u32,
time_since_first_restart: Duration,
) -> bool {
if restart_count == 0 {
return true;
}
if time_since_first_restart > self.time_window {
return true;
}
restart_count < self.max_restarts
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestartPolicy {
pub strategy: RestartStrategy,
pub intensity: RestartIntensity,
}
impl Default for RestartPolicy {
fn default() -> Self {
Self {
strategy: RestartStrategy::OneForOne,
intensity: RestartIntensity::default(),
}
}
}
impl RestartPolicy {
pub fn new(strategy: RestartStrategy, intensity: RestartIntensity) -> Self {
Self {
strategy,
intensity,
}
}
pub fn lenient_one_for_one() -> Self {
Self {
strategy: RestartStrategy::OneForOne,
intensity: RestartIntensity::lenient(),
}
}
pub fn strict_one_for_all() -> Self {
Self {
strategy: RestartStrategy::OneForAll,
intensity: RestartIntensity::strict(),
}
}
pub fn never_restart() -> Self {
Self {
strategy: RestartStrategy::OneForOne,
intensity: RestartIntensity::never(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_restart_intensity_default() {
let intensity = RestartIntensity::default();
assert_eq!(intensity.max_restarts, 5);
assert_eq!(intensity.time_window, Duration::from_secs(60));
}
#[test]
fn test_restart_intensity_is_allowed() {
let intensity = RestartIntensity::new(3, Duration::from_secs(60));
assert!(intensity.is_restart_allowed(0, Duration::from_secs(0)));
assert!(intensity.is_restart_allowed(2, Duration::from_secs(30)));
assert!(!intensity.is_restart_allowed(3, Duration::from_secs(30)));
assert!(intensity.is_restart_allowed(3, Duration::from_secs(120)));
}
#[test]
fn test_restart_policy_presets() {
let lenient = RestartPolicy::lenient_one_for_one();
assert_eq!(lenient.strategy, RestartStrategy::OneForOne);
assert_eq!(lenient.intensity.max_restarts, 10);
let strict = RestartPolicy::strict_one_for_all();
assert_eq!(strict.strategy, RestartStrategy::OneForAll);
assert_eq!(strict.intensity.max_restarts, 3);
let never = RestartPolicy::never_restart();
assert_eq!(never.intensity.max_restarts, 0);
}
#[test]
fn test_restart_strategy_serialization() {
let strategy = RestartStrategy::RestForOne;
let serialized = serde_json::to_string(&strategy).unwrap();
let deserialized: RestartStrategy = serde_json::from_str(&serialized).unwrap();
assert_eq!(strategy, deserialized);
}
#[test]
fn restart_window_boundary_exact_window() {
let intensity = RestartIntensity::new(3, Duration::from_secs(60));
assert!(!intensity.is_restart_allowed(3, Duration::from_secs(60)));
}
#[test]
fn restart_window_boundary_one_sec_over() {
let intensity = RestartIntensity::new(3, Duration::from_secs(60));
assert!(intensity.is_restart_allowed(3, Duration::from_secs(61)));
}
#[test]
fn restart_never_policy_rejects_first() {
let intensity = RestartIntensity::never();
assert!(intensity.is_restart_allowed(0, Duration::from_secs(0)));
assert!(!intensity.is_restart_allowed(1, Duration::from_secs(0)));
}
}