use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CircuitBreakerState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CircuitBreakerStatus {
pub state: CircuitBreakerState,
pub failure_count: u32,
pub success_count: u32,
#[serde(skip)]
pub last_failure_time: Option<Instant>,
#[serde(skip)]
pub next_attempt_time: Option<Instant>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CircuitBreakerPolicy {
pub failure_threshold: u32,
pub success_threshold: u32,
pub timeout: Duration,
pub enabled: bool,
}
impl CircuitBreakerPolicy {
pub fn new() -> Self {
Self {
failure_threshold: 5,
success_threshold: 3,
timeout: Duration::from_secs(60),
enabled: true,
}
}
pub fn with_config(
failure_threshold: u32,
success_threshold: u32,
timeout: Duration,
enabled: bool,
) -> Self {
Self {
failure_threshold,
success_threshold,
timeout,
enabled,
}
}
pub fn should_allow_request(&self, status: &CircuitBreakerStatus) -> bool {
if !self.enabled {
return true;
}
match status.state {
CircuitBreakerState::Closed => true,
CircuitBreakerState::Open => {
if let Some(next_attempt) = status.next_attempt_time {
Instant::now() >= next_attempt
} else {
false
}
}
CircuitBreakerState::HalfOpen => {
status.success_count < self.success_threshold
}
}
}
pub fn update_state(&self, status: &mut CircuitBreakerStatus, success: bool) {
if !self.enabled {
return;
}
match status.state {
CircuitBreakerState::Closed => {
if success {
status.failure_count = 0;
} else {
status.failure_count += 1;
status.last_failure_time = Some(Instant::now());
if status.failure_count >= self.failure_threshold {
status.state = CircuitBreakerState::Open;
status.next_attempt_time = Some(Instant::now() + self.timeout);
}
}
}
CircuitBreakerState::Open => {
if let Some(next_attempt) = status.next_attempt_time {
if Instant::now() >= next_attempt {
status.state = CircuitBreakerState::HalfOpen;
status.failure_count = 0;
status.success_count = 0;
status.next_attempt_time = None;
}
}
}
CircuitBreakerState::HalfOpen => {
if success {
status.success_count += 1;
if status.success_count >= self.success_threshold {
status.state = CircuitBreakerState::Closed;
status.failure_count = 0;
status.success_count = 0;
}
} else {
status.state = CircuitBreakerState::Open;
status.failure_count = self.failure_threshold;
status.success_count = 0;
status.last_failure_time = Some(Instant::now());
status.next_attempt_time = Some(Instant::now() + self.timeout);
}
}
}
}
pub fn get_status(&self) -> CircuitBreakerStatus {
CircuitBreakerStatus {
state: CircuitBreakerState::Closed,
failure_count: 0,
success_count: 0,
last_failure_time: None,
next_attempt_time: None,
}
}
pub fn reset(&self, status: &mut CircuitBreakerStatus) {
status.state = CircuitBreakerState::Closed;
status.failure_count = 0;
status.success_count = 0;
status.last_failure_time = None;
status.next_attempt_time = None;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn failure_threshold(&self) -> u32 {
self.failure_threshold
}
pub fn success_threshold(&self) -> u32 {
self.success_threshold
}
pub fn timeout(&self) -> Duration {
self.timeout
}
}
impl Default for CircuitBreakerPolicy {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circuit_breaker_policy_creation() {
let policy = CircuitBreakerPolicy::new();
assert_eq!(policy.failure_threshold(), 5);
assert_eq!(policy.success_threshold(), 3);
assert_eq!(policy.timeout(), Duration::from_secs(60));
assert!(policy.is_enabled());
}
#[test]
fn test_circuit_breaker_policy_with_config() {
let policy = CircuitBreakerPolicy::with_config(10, 5, Duration::from_secs(30), false);
assert_eq!(policy.failure_threshold(), 10);
assert_eq!(policy.success_threshold(), 5);
assert_eq!(policy.timeout(), Duration::from_secs(30));
assert!(!policy.is_enabled());
}
#[test]
fn test_should_allow_request_closed() {
let policy = CircuitBreakerPolicy::new();
let status = CircuitBreakerStatus {
state: CircuitBreakerState::Closed,
failure_count: 0,
success_count: 0,
last_failure_time: None,
next_attempt_time: None,
};
assert!(policy.should_allow_request(&status));
}
#[test]
fn test_should_allow_request_open() {
let policy = CircuitBreakerPolicy::new();
let status = CircuitBreakerStatus {
state: CircuitBreakerState::Open,
failure_count: 5,
success_count: 0,
last_failure_time: Some(Instant::now()),
next_attempt_time: Some(Instant::now() + Duration::from_secs(60)),
};
assert!(!policy.should_allow_request(&status));
}
#[test]
fn test_should_allow_request_half_open() {
let policy = CircuitBreakerPolicy::new();
let status = CircuitBreakerStatus {
state: CircuitBreakerState::HalfOpen,
failure_count: 0,
success_count: 1,
last_failure_time: None,
next_attempt_time: None,
};
assert!(policy.should_allow_request(&status));
}
#[test]
fn test_update_state_closed_success() {
let policy = CircuitBreakerPolicy::new();
let mut status = policy.get_status();
status.failure_count = 2;
policy.update_state(&mut status, true);
assert_eq!(status.failure_count, 0);
assert_eq!(status.state, CircuitBreakerState::Closed);
}
#[test]
fn test_update_state_closed_failure() {
let policy = CircuitBreakerPolicy::with_config(3, 2, Duration::from_secs(1), true);
let mut status = policy.get_status();
status.failure_count = 2;
policy.update_state(&mut status, false);
assert_eq!(status.failure_count, 3);
assert_eq!(status.state, CircuitBreakerState::Open);
assert!(status.next_attempt_time.is_some());
}
#[test]
fn test_update_state_half_open_success() {
let policy = CircuitBreakerPolicy::with_config(3, 2, Duration::from_secs(1), true);
let mut status = CircuitBreakerStatus {
state: CircuitBreakerState::HalfOpen,
failure_count: 0,
success_count: 1,
last_failure_time: None,
next_attempt_time: None,
};
policy.update_state(&mut status, true);
assert_eq!(status.success_count, 2);
assert_eq!(status.state, CircuitBreakerState::Closed);
}
#[test]
fn test_update_state_half_open_failure() {
let policy = CircuitBreakerPolicy::new();
let mut status = CircuitBreakerStatus {
state: CircuitBreakerState::HalfOpen,
failure_count: 0,
success_count: 1,
last_failure_time: None,
next_attempt_time: None,
};
policy.update_state(&mut status, false);
assert_eq!(status.state, CircuitBreakerState::Open);
assert_eq!(status.failure_count, 5);
assert!(status.next_attempt_time.is_some());
}
#[test]
fn test_reset() {
let policy = CircuitBreakerPolicy::new();
let mut status = CircuitBreakerStatus {
state: CircuitBreakerState::Open,
failure_count: 5,
success_count: 0,
last_failure_time: Some(Instant::now()),
next_attempt_time: Some(Instant::now() + Duration::from_secs(60)),
};
policy.reset(&mut status);
assert_eq!(status.state, CircuitBreakerState::Closed);
assert_eq!(status.failure_count, 0);
assert_eq!(status.success_count, 0);
assert!(status.last_failure_time.is_none());
assert!(status.next_attempt_time.is_none());
}
#[test]
fn test_disabled_circuit_breaker() {
let policy = CircuitBreakerPolicy::with_config(1, 1, Duration::from_secs(1), false);
let status = CircuitBreakerStatus {
state: CircuitBreakerState::Open,
failure_count: 1,
success_count: 0,
last_failure_time: Some(Instant::now()),
next_attempt_time: Some(Instant::now() + Duration::from_secs(60)),
};
assert!(policy.should_allow_request(&status));
let mut status = status;
policy.update_state(&mut status, false);
assert_eq!(status.state, CircuitBreakerState::Open); }
}