use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct CircuitConfig {
pub failure_threshold: u32,
pub max_depth: u32,
}
impl Default for CircuitConfig {
fn default() -> Self {
Self {
failure_threshold: 3,
max_depth: 5,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircuitBreaker {
pub state: CircuitState,
pub consecutive_failures: u32,
pub config: CircuitConfig,
}
impl CircuitBreaker {
pub fn new(config: CircuitConfig) -> Self {
Self {
state: CircuitState::Closed,
consecutive_failures: 0,
config,
}
}
pub fn allows_delegation(&self) -> bool {
!matches!(self.state, CircuitState::Open)
}
pub fn exceeds_depth(&self, depth: u32) -> bool {
depth > self.config.max_depth
}
pub fn record_success(&mut self) {
self.consecutive_failures = 0;
if self.state == CircuitState::HalfOpen {
self.state = CircuitState::Closed;
}
}
pub fn record_failure(&mut self) {
self.consecutive_failures = self.consecutive_failures.saturating_add(1);
match self.state {
CircuitState::Closed => {
if self.consecutive_failures >= self.config.failure_threshold {
self.state = CircuitState::Open;
}
}
CircuitState::HalfOpen => {
self.state = CircuitState::Open;
}
CircuitState::Open => {}
}
}
pub fn attempt_reset(&mut self) {
if self.state == CircuitState::Open {
self.state = CircuitState::HalfOpen;
}
}
}
impl Default for CircuitBreaker {
fn default() -> Self {
Self::new(CircuitConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_is_sane() {
let cfg = CircuitConfig::default();
assert!(cfg.failure_threshold > 0);
assert!(cfg.max_depth > 0);
}
#[test]
fn breaker_trips_and_recovers() {
let mut cb = CircuitBreaker::new(CircuitConfig {
failure_threshold: 3,
max_depth: 5,
});
assert_eq!(cb.state, CircuitState::Closed);
assert!(cb.allows_delegation());
cb.record_failure();
cb.record_failure();
assert_eq!(cb.state, CircuitState::Closed);
cb.record_failure(); assert_eq!(cb.state, CircuitState::Open);
assert!(!cb.allows_delegation());
cb.attempt_reset();
assert_eq!(cb.state, CircuitState::HalfOpen);
assert!(cb.allows_delegation());
cb.record_failure();
assert_eq!(cb.state, CircuitState::Open);
cb.attempt_reset();
cb.record_success(); assert_eq!(cb.state, CircuitState::Closed);
assert_eq!(cb.consecutive_failures, 0);
}
#[test]
fn depth_limit_is_enforced() {
let cb = CircuitBreaker::new(CircuitConfig {
failure_threshold: 3,
max_depth: 4,
});
assert!(!cb.exceeds_depth(4));
assert!(cb.exceeds_depth(5));
}
}