tower_circuitbreaker/
config.rs

1use crate::SharedFailureClassifier;
2use crate::events::CircuitBreakerEvent;
3use std::sync::Arc;
4use std::time::Duration;
5use tower_resilience_core::EventListeners;
6
7/// Type of sliding window used for tracking calls.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SlidingWindowType {
10    /// Count-based window tracks the last N calls.
11    CountBased,
12    /// Time-based window tracks calls within a time duration.
13    TimeBased,
14}
15
16/// Configuration for the circuit breaker pattern.
17pub struct CircuitBreakerConfig<Res, Err> {
18    pub(crate) failure_rate_threshold: f64,
19    pub(crate) sliding_window_type: SlidingWindowType,
20    pub(crate) sliding_window_size: usize,
21    pub(crate) sliding_window_duration: Option<Duration>,
22    pub(crate) wait_duration_in_open: Duration,
23    pub(crate) permitted_calls_in_half_open: usize,
24    pub(crate) minimum_number_of_calls: usize,
25    pub(crate) failure_classifier: SharedFailureClassifier<Res, Err>,
26    pub(crate) slow_call_duration_threshold: Option<Duration>,
27    pub(crate) slow_call_rate_threshold: f64,
28    pub(crate) event_listeners: EventListeners<CircuitBreakerEvent>,
29    pub(crate) name: String,
30}
31
32impl<Res, Err> CircuitBreakerConfig<Res, Err> {
33    /// Creates a new configuration builder.
34    pub fn builder() -> CircuitBreakerConfigBuilder<Res, Err> {
35        CircuitBreakerConfigBuilder::new()
36    }
37}
38
39/// Builder for configuring and constructing a circuit breaker.
40pub struct CircuitBreakerConfigBuilder<Res, Err> {
41    failure_rate_threshold: f64,
42    sliding_window_type: SlidingWindowType,
43    sliding_window_size: usize,
44    sliding_window_duration: Option<Duration>,
45    wait_duration_in_open: Duration,
46    permitted_calls_in_half_open: usize,
47    failure_classifier: SharedFailureClassifier<Res, Err>,
48    minimum_number_of_calls: Option<usize>,
49    slow_call_duration_threshold: Option<Duration>,
50    slow_call_rate_threshold: f64,
51    event_listeners: EventListeners<CircuitBreakerEvent>,
52    name: String,
53}
54
55impl<Res, Err> CircuitBreakerConfigBuilder<Res, Err> {
56    /// Creates a new builder with default values.
57    pub fn new() -> Self {
58        Self {
59            failure_rate_threshold: 0.5,
60            sliding_window_type: SlidingWindowType::CountBased,
61            sliding_window_size: 100,
62            sliding_window_duration: None,
63            wait_duration_in_open: Duration::from_secs(30),
64            permitted_calls_in_half_open: 1,
65            failure_classifier: Arc::new(|res| res.is_err()),
66            minimum_number_of_calls: None,
67            slow_call_duration_threshold: None,
68            slow_call_rate_threshold: 1.0,
69            event_listeners: EventListeners::new(),
70            name: String::from("<unnamed>"),
71        }
72    }
73
74    /// Sets the failure rate threshold at which the circuit will open.
75    ///
76    /// Default: 0.5 (50%)
77    pub fn failure_rate_threshold(mut self, rate: f64) -> Self {
78        self.failure_rate_threshold = rate;
79        self
80    }
81
82    /// Sets the type of sliding window to use.
83    ///
84    /// Default: CountBased
85    pub fn sliding_window_type(mut self, window_type: SlidingWindowType) -> Self {
86        self.sliding_window_type = window_type;
87        self
88    }
89
90    /// Sets the size of the sliding window for failure rate calculation (count-based).
91    ///
92    /// For count-based windows, this is the number of calls to track.
93    /// For time-based windows, this is used as the minimum calls threshold if not set explicitly.
94    ///
95    /// Default: 100
96    pub fn sliding_window_size(mut self, size: usize) -> Self {
97        self.sliding_window_size = size;
98        self
99    }
100
101    /// Sets the duration of the sliding window (time-based only).
102    ///
103    /// Only used when `sliding_window_type` is `TimeBased`.
104    /// Calls older than this duration are excluded from failure rate calculation.
105    ///
106    /// Default: None (must be set for time-based windows)
107    pub fn sliding_window_duration(mut self, duration: Duration) -> Self {
108        self.sliding_window_duration = Some(duration);
109        self
110    }
111
112    /// Sets the duration the circuit remains open before transitioning to half-open.
113    ///
114    /// Default: 30 seconds
115    pub fn wait_duration_in_open(mut self, duration: Duration) -> Self {
116        self.wait_duration_in_open = duration;
117        self
118    }
119
120    /// Sets the number of permitted calls in the half-open state.
121    ///
122    /// Default: 1
123    pub fn permitted_calls_in_half_open(mut self, n: usize) -> Self {
124        self.permitted_calls_in_half_open = n;
125        self
126    }
127
128    /// Sets a custom failure classifier function.
129    ///
130    /// Default: classifies errors as failures
131    pub fn failure_classifier<F>(mut self, classifier: F) -> Self
132    where
133        F: Fn(&Result<Res, Err>) -> bool + Send + Sync + 'static,
134    {
135        self.failure_classifier = Arc::new(classifier);
136        self
137    }
138
139    /// Sets the minimum number of calls before failure rate is evaluated.
140    ///
141    /// Default: same as sliding_window_size
142    pub fn minimum_number_of_calls(mut self, n: usize) -> Self {
143        self.minimum_number_of_calls = Some(n);
144        self
145    }
146
147    /// Sets the duration threshold for considering a call "slow".
148    ///
149    /// When set, calls exceeding this duration will be tracked and can trigger
150    /// circuit opening based on `slow_call_rate_threshold`.
151    ///
152    /// Default: None (slow call detection disabled)
153    pub fn slow_call_duration_threshold(mut self, duration: Duration) -> Self {
154        self.slow_call_duration_threshold = Some(duration);
155        self
156    }
157
158    /// Sets the slow call rate threshold at which the circuit will open.
159    ///
160    /// Only applies when `slow_call_duration_threshold` is set.
161    ///
162    /// Default: 1.0 (100%, effectively disabled)
163    pub fn slow_call_rate_threshold(mut self, rate: f64) -> Self {
164        self.slow_call_rate_threshold = rate;
165        self
166    }
167
168    /// Give this breaker a human-readable name for observability.
169    ///
170    /// Default: `<unnamed>`
171    pub fn name<N: Into<String>>(mut self, n: N) -> Self {
172        self.name = n.into();
173        self
174    }
175
176    /// Register a callback for state transition events.
177    pub fn on_state_transition<F>(mut self, f: F) -> Self
178    where
179        F: Fn(crate::CircuitState, crate::CircuitState) + Send + Sync + 'static,
180    {
181        use tower_resilience_core::FnListener;
182        self.event_listeners
183            .add(FnListener::new(move |event: &CircuitBreakerEvent| {
184                if let CircuitBreakerEvent::StateTransition {
185                    from_state,
186                    to_state,
187                    ..
188                } = event
189                {
190                    f(*from_state, *to_state);
191                }
192            }));
193        self
194    }
195
196    /// Register a callback for call permitted events.
197    pub fn on_call_permitted<F>(mut self, f: F) -> Self
198    where
199        F: Fn(crate::CircuitState) + Send + Sync + 'static,
200    {
201        self.event_listeners
202            .add(tower_resilience_core::FnListener::new(
203                move |event: &CircuitBreakerEvent| {
204                    if let CircuitBreakerEvent::CallPermitted { state, .. } = event {
205                        f(*state);
206                    }
207                },
208            ));
209        self
210    }
211
212    /// Register a callback for call rejected events.
213    pub fn on_call_rejected<F>(mut self, f: F) -> Self
214    where
215        F: Fn() + Send + Sync + 'static,
216    {
217        self.event_listeners
218            .add(tower_resilience_core::FnListener::new(
219                move |event: &CircuitBreakerEvent| {
220                    if matches!(event, CircuitBreakerEvent::CallRejected { .. }) {
221                        f();
222                    }
223                },
224            ));
225        self
226    }
227
228    /// Register a callback for success recorded events.
229    pub fn on_success<F>(mut self, f: F) -> Self
230    where
231        F: Fn(crate::CircuitState) + Send + Sync + 'static,
232    {
233        self.event_listeners
234            .add(tower_resilience_core::FnListener::new(
235                move |event: &CircuitBreakerEvent| {
236                    if let CircuitBreakerEvent::SuccessRecorded { state, .. } = event {
237                        f(*state);
238                    }
239                },
240            ));
241        self
242    }
243
244    /// Register a callback for failure recorded events.
245    pub fn on_failure<F>(mut self, f: F) -> Self
246    where
247        F: Fn(crate::CircuitState) + Send + Sync + 'static,
248    {
249        self.event_listeners
250            .add(tower_resilience_core::FnListener::new(
251                move |event: &CircuitBreakerEvent| {
252                    if let CircuitBreakerEvent::FailureRecorded { state, .. } = event {
253                        f(*state);
254                    }
255                },
256            ));
257        self
258    }
259
260    /// Register a callback for slow call detected events.
261    pub fn on_slow_call<F>(mut self, f: F) -> Self
262    where
263        F: Fn(Duration) + Send + Sync + 'static,
264    {
265        use tower_resilience_core::FnListener;
266        self.event_listeners
267            .add(FnListener::new(move |event: &CircuitBreakerEvent| {
268                if let CircuitBreakerEvent::SlowCallDetected { duration, .. } = event {
269                    f(*duration);
270                }
271            }));
272        self
273    }
274
275    /// Builds the configuration and returns a CircuitBreakerLayer.
276    pub fn build(self) -> crate::layer::CircuitBreakerLayer<Res, Err> {
277        // Validate time-based window configuration
278        if self.sliding_window_type == SlidingWindowType::TimeBased
279            && self.sliding_window_duration.is_none()
280        {
281            panic!("sliding_window_duration must be set when using TimeBased sliding window");
282        }
283
284        let config = CircuitBreakerConfig {
285            failure_rate_threshold: self.failure_rate_threshold,
286            sliding_window_type: self.sliding_window_type,
287            sliding_window_size: self.sliding_window_size,
288            sliding_window_duration: self.sliding_window_duration,
289            wait_duration_in_open: self.wait_duration_in_open,
290            permitted_calls_in_half_open: self.permitted_calls_in_half_open,
291            failure_classifier: self.failure_classifier,
292            minimum_number_of_calls: self
293                .minimum_number_of_calls
294                .unwrap_or(self.sliding_window_size),
295            slow_call_duration_threshold: self.slow_call_duration_threshold,
296            slow_call_rate_threshold: self.slow_call_rate_threshold,
297            event_listeners: self.event_listeners,
298            name: self.name,
299        };
300
301        crate::layer::CircuitBreakerLayer::new(config)
302    }
303}
304
305impl<Res, Err> Default for CircuitBreakerConfigBuilder<Res, Err> {
306    fn default() -> Self {
307        Self::new()
308    }
309}