use std::collections::HashSet;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct CircuitBreakerConfig {
pub failure_threshold: u32,
pub failure_window: Duration,
pub cooldown: Duration,
pub half_open_success_threshold: u32,
pub half_open_max_probes: u32,
pub failure_conditions: FailureConditions,
pub adaptive_enabled: bool,
pub sync_mode_thresholds: Option<SyncModeThresholds>,
}
impl Default for CircuitBreakerConfig {
fn default() -> Self {
Self {
failure_threshold: 5,
failure_window: Duration::from_secs(30),
cooldown: Duration::from_secs(10),
half_open_success_threshold: 3,
half_open_max_probes: 2,
failure_conditions: FailureConditions::default(),
adaptive_enabled: false,
sync_mode_thresholds: None,
}
}
}
impl CircuitBreakerConfig {
pub fn builder() -> CircuitBreakerConfigBuilder {
CircuitBreakerConfigBuilder::default()
}
pub fn effective_threshold(&self, sync_mode: Option<&str>) -> u32 {
if let (Some(thresholds), Some(mode)) = (&self.sync_mode_thresholds, sync_mode) {
match mode.to_lowercase().as_str() {
"sync" | "synchronous" => thresholds.sync_threshold,
"semisync" | "semi-sync" => thresholds.semisync_threshold,
"async" | "asynchronous" => thresholds.async_threshold,
_ => self.failure_threshold,
}
} else {
self.failure_threshold
}
}
pub fn effective_cooldown(&self, sync_mode: Option<&str>) -> Duration {
if let (Some(thresholds), Some(mode)) = (&self.sync_mode_thresholds, sync_mode) {
match mode.to_lowercase().as_str() {
"sync" | "synchronous" => thresholds.sync_cooldown,
"semisync" | "semi-sync" => thresholds.semisync_cooldown,
"async" | "asynchronous" => thresholds.async_cooldown,
_ => self.cooldown,
}
} else {
self.cooldown
}
}
}
#[derive(Debug, Default)]
pub struct CircuitBreakerConfigBuilder {
failure_threshold: Option<u32>,
failure_window_secs: Option<u64>,
cooldown_secs: Option<u64>,
half_open_success_threshold: Option<u32>,
half_open_max_probes: Option<u32>,
failure_conditions: Option<FailureConditions>,
adaptive_enabled: Option<bool>,
sync_mode_thresholds: Option<SyncModeThresholds>,
}
impl CircuitBreakerConfigBuilder {
pub fn failure_threshold(mut self, threshold: u32) -> Self {
self.failure_threshold = Some(threshold);
self
}
pub fn failure_window_secs(mut self, secs: u64) -> Self {
self.failure_window_secs = Some(secs);
self
}
pub fn cooldown_secs(mut self, secs: u64) -> Self {
self.cooldown_secs = Some(secs);
self
}
pub fn half_open_success_threshold(mut self, threshold: u32) -> Self {
self.half_open_success_threshold = Some(threshold);
self
}
pub fn half_open_max_probes(mut self, max: u32) -> Self {
self.half_open_max_probes = Some(max);
self
}
pub fn failure_conditions(mut self, conditions: FailureConditions) -> Self {
self.failure_conditions = Some(conditions);
self
}
pub fn adaptive(mut self, enabled: bool) -> Self {
self.adaptive_enabled = Some(enabled);
self
}
pub fn sync_mode_thresholds(mut self, thresholds: SyncModeThresholds) -> Self {
self.sync_mode_thresholds = Some(thresholds);
self
}
pub fn build(self) -> CircuitBreakerConfig {
let default = CircuitBreakerConfig::default();
CircuitBreakerConfig {
failure_threshold: self.failure_threshold.unwrap_or(default.failure_threshold),
failure_window: self
.failure_window_secs
.map(Duration::from_secs)
.unwrap_or(default.failure_window),
cooldown: self
.cooldown_secs
.map(Duration::from_secs)
.unwrap_or(default.cooldown),
half_open_success_threshold: self
.half_open_success_threshold
.unwrap_or(default.half_open_success_threshold),
half_open_max_probes: self
.half_open_max_probes
.unwrap_or(default.half_open_max_probes),
failure_conditions: self.failure_conditions.unwrap_or(default.failure_conditions),
adaptive_enabled: self.adaptive_enabled.unwrap_or(default.adaptive_enabled),
sync_mode_thresholds: self.sync_mode_thresholds,
}
}
}
#[derive(Debug, Clone)]
pub struct FailureConditions {
pub timeout: Duration,
pub error_codes: HashSet<String>,
pub slow_threshold: Option<Duration>,
pub ignore_transient: bool,
pub count_connection_errors: bool,
pub count_timeouts: bool,
}
impl Default for FailureConditions {
fn default() -> Self {
let mut error_codes = HashSet::new();
error_codes.insert("08001".to_string()); error_codes.insert("08004".to_string()); error_codes.insert("08006".to_string()); error_codes.insert("57P01".to_string()); error_codes.insert("57P02".to_string()); error_codes.insert("57P03".to_string()); error_codes.insert("XX000".to_string()); error_codes.insert("XX001".to_string()); error_codes.insert("XX002".to_string());
Self {
timeout: Duration::from_secs(5),
error_codes,
slow_threshold: Some(Duration::from_secs(2)),
ignore_transient: true,
count_connection_errors: true,
count_timeouts: true,
}
}
}
impl FailureConditions {
pub fn new() -> Self {
Self::default()
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn with_error_code(mut self, code: &str) -> Self {
self.error_codes.insert(code.to_string());
self
}
pub fn with_slow_threshold(mut self, threshold: Duration) -> Self {
self.slow_threshold = Some(threshold);
self
}
pub fn ignore_transient(mut self, ignore: bool) -> Self {
self.ignore_transient = ignore;
self
}
pub fn is_failure_code(&self, code: &str) -> bool {
self.error_codes.contains(code)
}
pub fn is_slow(&self, response_time: Duration) -> bool {
self.slow_threshold
.map(|threshold| response_time > threshold)
.unwrap_or(false)
}
pub fn is_timeout(&self, response_time: Duration) -> bool {
response_time > self.timeout
}
}
#[derive(Debug, Clone)]
pub struct SyncModeThresholds {
pub sync_threshold: u32,
pub sync_cooldown: Duration,
pub semisync_threshold: u32,
pub semisync_cooldown: Duration,
pub async_threshold: u32,
pub async_cooldown: Duration,
}
impl Default for SyncModeThresholds {
fn default() -> Self {
Self {
sync_threshold: 3,
sync_cooldown: Duration::from_secs(5),
semisync_threshold: 5,
semisync_cooldown: Duration::from_secs(10),
async_threshold: 10,
async_cooldown: Duration::from_secs(30),
}
}
}
impl SyncModeThresholds {
pub fn new() -> Self {
Self::default()
}
pub fn with_sync(mut self, threshold: u32, cooldown_secs: u64) -> Self {
self.sync_threshold = threshold;
self.sync_cooldown = Duration::from_secs(cooldown_secs);
self
}
pub fn with_semisync(mut self, threshold: u32, cooldown_secs: u64) -> Self {
self.semisync_threshold = threshold;
self.semisync_cooldown = Duration::from_secs(cooldown_secs);
self
}
pub fn with_async(mut self, threshold: u32, cooldown_secs: u64) -> Self {
self.async_threshold = threshold;
self.async_cooldown = Duration::from_secs(cooldown_secs);
self
}
}
#[derive(Debug, Clone)]
pub struct NodeOverride {
pub node_id: String,
pub failure_threshold: Option<u32>,
pub cooldown: Option<Duration>,
pub half_open_success_threshold: Option<u32>,
}
impl NodeOverride {
pub fn new(node_id: impl Into<String>) -> Self {
Self {
node_id: node_id.into(),
failure_threshold: None,
cooldown: None,
half_open_success_threshold: None,
}
}
pub fn with_failure_threshold(mut self, threshold: u32) -> Self {
self.failure_threshold = Some(threshold);
self
}
pub fn with_cooldown_secs(mut self, secs: u64) -> Self {
self.cooldown = Some(Duration::from_secs(secs));
self
}
pub fn with_half_open_success_threshold(mut self, threshold: u32) -> Self {
self.half_open_success_threshold = Some(threshold);
self
}
pub fn apply_to(&self, base: &CircuitBreakerConfig) -> CircuitBreakerConfig {
CircuitBreakerConfig {
failure_threshold: self.failure_threshold.unwrap_or(base.failure_threshold),
cooldown: self.cooldown.unwrap_or(base.cooldown),
half_open_success_threshold: self
.half_open_success_threshold
.unwrap_or(base.half_open_success_threshold),
..base.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = CircuitBreakerConfig::default();
assert_eq!(config.failure_threshold, 5);
assert_eq!(config.cooldown, Duration::from_secs(10));
assert_eq!(config.half_open_success_threshold, 3);
}
#[test]
fn test_config_builder() {
let config = CircuitBreakerConfig::builder()
.failure_threshold(10)
.failure_window_secs(60)
.cooldown_secs(20)
.half_open_success_threshold(5)
.adaptive(true)
.build();
assert_eq!(config.failure_threshold, 10);
assert_eq!(config.failure_window, Duration::from_secs(60));
assert_eq!(config.cooldown, Duration::from_secs(20));
assert_eq!(config.half_open_success_threshold, 5);
assert!(config.adaptive_enabled);
}
#[test]
fn test_failure_conditions() {
let conditions = FailureConditions::default();
assert!(conditions.is_failure_code("08001"));
assert!(conditions.is_failure_code("57P01"));
assert!(!conditions.is_failure_code("42P01"));
assert!(conditions.is_slow(Duration::from_secs(3)));
assert!(!conditions.is_slow(Duration::from_secs(1)));
}
#[test]
fn test_sync_mode_thresholds() {
let config = CircuitBreakerConfig::builder()
.failure_threshold(5)
.sync_mode_thresholds(SyncModeThresholds::default())
.build();
assert_eq!(config.effective_threshold(Some("sync")), 3);
assert_eq!(config.effective_threshold(Some("semisync")), 5);
assert_eq!(config.effective_threshold(Some("async")), 10);
assert_eq!(config.effective_threshold(None), 5);
}
#[test]
fn test_node_override() {
let base = CircuitBreakerConfig::default();
let override_ = NodeOverride::new("special-node")
.with_failure_threshold(20)
.with_cooldown_secs(60);
let merged = override_.apply_to(&base);
assert_eq!(merged.failure_threshold, 20);
assert_eq!(merged.cooldown, Duration::from_secs(60));
assert_eq!(merged.half_open_success_threshold, base.half_open_success_threshold);
}
}