Skip to main content

camel_api/
circuit_breaker.rs

1use std::time::Duration;
2
3use crate::BoxProcessor;
4
5/// Configuration for the circuit breaker pattern.
6///
7/// The circuit breaker monitors failures and temporarily stops sending
8/// requests to a failing service, giving it time to recover.
9///
10/// # States
11///
12/// - **Closed** — Normal operation. Failures are counted; when `failure_threshold`
13///   consecutive failures occur, the circuit opens.
14/// - **Open** — All calls are rejected with [`CamelError::CircuitOpen`](crate::CamelError::CircuitOpen).
15///   After `open_duration` elapses, the circuit transitions to half-open.
16/// - **Half-Open** — A single probe call is allowed through. If it succeeds
17///   (`success_threshold` times), the circuit closes. If it fails, the circuit reopens.
18#[derive(Clone)]
19pub struct CircuitBreakerConfig {
20    /// Number of consecutive failures before opening the circuit.
21    pub failure_threshold: u32,
22    /// Number of successful probes in half-open state before closing.
23    ///
24    /// **Current limitation:** Only `1` is effectively supported. The state
25    /// machine resets to `Closed` on the first successful half-open probe
26    /// regardless of this value. Multi-probe half-open tracking is deferred.
27    pub success_threshold: u32,
28    /// How long the circuit stays open before allowing a probe.
29    pub open_duration: Duration,
30    /// Optional fallback processor invoked when circuit is open.
31    pub fallback: Option<BoxProcessor>,
32}
33
34impl std::fmt::Debug for CircuitBreakerConfig {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.debug_struct("CircuitBreakerConfig")
37            .field("failure_threshold", &self.failure_threshold)
38            .field("success_threshold", &self.success_threshold)
39            .field("open_duration", &self.open_duration)
40            .field("fallback", &self.fallback.as_ref().map(|_| "<processor>"))
41            .finish()
42    }
43}
44
45impl Default for CircuitBreakerConfig {
46    fn default() -> Self {
47        Self {
48            failure_threshold: 5,
49            success_threshold: 1,
50            open_duration: Duration::from_secs(30),
51            fallback: None,
52        }
53    }
54}
55
56impl CircuitBreakerConfig {
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    pub fn failure_threshold(mut self, n: u32) -> Self {
62        self.failure_threshold = n;
63        self
64    }
65
66    pub fn open_duration(mut self, duration: Duration) -> Self {
67        self.open_duration = duration;
68        self
69    }
70
71    pub fn fallback(mut self, fallback: BoxProcessor) -> Self {
72        self.fallback = Some(fallback);
73        self
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_default_config() {
83        let config = CircuitBreakerConfig::default();
84        assert_eq!(config.failure_threshold, 5);
85        assert_eq!(config.success_threshold, 1);
86        assert_eq!(config.open_duration, Duration::from_secs(30));
87        assert!(config.fallback.is_none());
88    }
89
90    #[test]
91    fn test_builder_pattern() {
92        let config = CircuitBreakerConfig::new()
93            .failure_threshold(3)
94            .open_duration(Duration::from_millis(500));
95        assert_eq!(config.failure_threshold, 3);
96        assert_eq!(config.success_threshold, 1); // default, no setter exposed
97        assert_eq!(config.open_duration, Duration::from_millis(500));
98    }
99}