use serde::{Deserialize, Serialize};
use std::time::Instant;
#[derive(Debug, Clone)]
pub enum BreakerState {
Closed {
failure_count: u32,
},
Open {
opened_at: Instant,
until: Instant,
},
HalfOpen {
success_count: u32,
probe_count: u32,
},
}
impl BreakerState {
pub fn closed() -> Self {
Self::Closed { failure_count: 0 }
}
pub fn is_closed(&self) -> bool {
matches!(self, Self::Closed { .. })
}
pub fn is_open(&self) -> bool {
matches!(self, Self::Open { .. })
}
pub fn is_half_open(&self) -> bool {
matches!(self, Self::HalfOpen { .. })
}
pub fn failure_count(&self) -> Option<u32> {
match self {
Self::Closed { failure_count } => Some(*failure_count),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Closed { .. } => "closed",
Self::Open { .. } => "open",
Self::HalfOpen { .. } => "half_open",
}
}
}
impl Default for BreakerState {
fn default() -> Self {
Self::closed()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BreakerMetrics {
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub rejected_requests: u64,
pub times_opened: u64,
pub times_closed: u64,
}
impl BreakerMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_success(&mut self) {
self.total_requests += 1;
self.successful_requests += 1;
}
pub fn record_failure(&mut self) {
self.total_requests += 1;
self.failed_requests += 1;
}
pub fn record_rejected(&mut self) {
self.total_requests += 1;
self.rejected_requests += 1;
}
pub fn record_opened(&mut self) {
self.times_opened += 1;
}
pub fn record_closed(&mut self) {
self.times_closed += 1;
}
pub fn success_rate(&self) -> f64 {
if self.total_requests == 0 {
return 1.0;
}
self.successful_requests as f64 / self.total_requests as f64
}
pub fn failure_rate(&self) -> f64 {
if self.total_requests == 0 {
return 0.0;
}
self.failed_requests as f64 / self.total_requests as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_breaker_state_default() {
let state = BreakerState::default();
assert!(state.is_closed());
assert_eq!(state.failure_count(), Some(0));
}
#[test]
fn test_breaker_state_names() {
assert_eq!(BreakerState::closed().name(), "closed");
assert_eq!(
BreakerState::Open {
opened_at: Instant::now(),
until: Instant::now(),
}
.name(),
"open"
);
assert_eq!(
BreakerState::HalfOpen {
success_count: 0,
probe_count: 0,
}
.name(),
"half_open"
);
}
#[test]
fn test_metrics() {
let mut metrics = BreakerMetrics::new();
assert_eq!(metrics.success_rate(), 1.0);
assert_eq!(metrics.failure_rate(), 0.0);
metrics.record_success();
metrics.record_success();
metrics.record_failure();
assert_eq!(metrics.total_requests, 3);
assert_eq!(metrics.successful_requests, 2);
assert_eq!(metrics.failed_requests, 1);
assert!((metrics.success_rate() - 0.666).abs() < 0.01);
}
}