use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FailureMode {
#[default]
FailClosed,
FailOpen,
UseCached {
default_allow: bool,
},
}
impl FailureMode {
pub fn allows_on_failure(&self) -> bool {
match self {
FailureMode::FailClosed => false,
FailureMode::FailOpen => true,
FailureMode::UseCached { default_allow } => *default_allow,
}
}
}
#[derive(Debug, Clone)]
pub struct DegradationConfig {
pub failure_mode: FailureMode,
pub circuit_breaker_enabled: bool,
pub circuit_breaker_threshold: u32,
pub circuit_breaker_reset_timeout: Duration,
pub request_timeout: Duration,
pub log_degraded_decisions: bool,
}
impl Default for DegradationConfig {
fn default() -> Self {
Self {
failure_mode: FailureMode::default(),
circuit_breaker_enabled: true,
circuit_breaker_threshold: 5,
circuit_breaker_reset_timeout: Duration::from_secs(30),
request_timeout: Duration::from_secs(5),
log_degraded_decisions: true,
}
}
}
impl DegradationConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_failure_mode(mut self, mode: FailureMode) -> Self {
self.failure_mode = mode;
self
}
#[must_use]
pub fn with_circuit_breaker_enabled(mut self, enabled: bool) -> Self {
self.circuit_breaker_enabled = enabled;
self
}
#[must_use]
pub fn with_circuit_breaker_threshold(mut self, threshold: u32) -> Self {
self.circuit_breaker_threshold = threshold;
self
}
#[must_use]
pub fn with_circuit_breaker_reset_timeout(mut self, timeout: Duration) -> Self {
self.circuit_breaker_reset_timeout = timeout;
self
}
#[must_use]
pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = timeout;
self
}
#[must_use]
pub fn with_log_degraded_decisions(mut self, log: bool) -> Self {
self.log_degraded_decisions = log;
self
}
pub fn fail_open() -> Self {
Self {
failure_mode: FailureMode::FailOpen,
..Default::default()
}
}
pub fn fail_closed() -> Self {
Self {
failure_mode: FailureMode::FailClosed,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_fail_closed() {
let config = DegradationConfig::new();
assert_eq!(config.failure_mode, FailureMode::FailClosed);
assert!(!config.failure_mode.allows_on_failure());
}
#[test]
fn test_fail_open() {
let config = DegradationConfig::fail_open();
assert_eq!(config.failure_mode, FailureMode::FailOpen);
assert!(config.failure_mode.allows_on_failure());
}
#[test]
fn test_use_cached() {
let mode = FailureMode::UseCached {
default_allow: true,
};
assert!(mode.allows_on_failure());
let mode = FailureMode::UseCached {
default_allow: false,
};
assert!(!mode.allows_on_failure());
}
#[test]
fn test_circuit_breaker_config() {
let config = DegradationConfig::new()
.with_circuit_breaker_enabled(true)
.with_circuit_breaker_threshold(10)
.with_circuit_breaker_reset_timeout(Duration::from_secs(60));
assert!(config.circuit_breaker_enabled);
assert_eq!(config.circuit_breaker_threshold, 10);
assert_eq!(
config.circuit_breaker_reset_timeout,
Duration::from_secs(60)
);
}
#[test]
fn test_request_timeout() {
let config = DegradationConfig::new().with_request_timeout(Duration::from_secs(10));
assert_eq!(config.request_timeout, Duration::from_secs(10));
}
#[test]
fn test_with_failure_mode() {
let config = DegradationConfig::new().with_failure_mode(FailureMode::FailOpen);
assert_eq!(config.failure_mode, FailureMode::FailOpen);
}
#[test]
fn test_with_log_degraded_decisions() {
let config = DegradationConfig::new().with_log_degraded_decisions(false);
assert!(!config.log_degraded_decisions);
let config = DegradationConfig::new().with_log_degraded_decisions(true);
assert!(config.log_degraded_decisions);
}
#[test]
fn test_fail_closed() {
let config = DegradationConfig::fail_closed();
assert_eq!(config.failure_mode, FailureMode::FailClosed);
}
}