resilient_rs/config/mod.rs
1use crate::strategies::RetryStrategy;
2use std::error::Error;
3use std::time::Duration;
4
5#[derive(Debug)]
6pub struct RetryConfig<E> {
7 /// The maximum number of retry attempts.
8 ///
9 /// This specifies how many times the operation will be retried before
10 /// giving up. For example, if `max_attempts` is set to 3, the operation
11 /// will be attempted up to 3 times (1 initial attempt + 2 retries).
12 pub max_attempts: usize,
13
14 /// The delay between retry attempts.
15 ///
16 /// This specifies the base amount of time to wait between each retry attempt.
17 /// The actual delay may vary depending on the `strategy`. For example, if
18 /// `delay` is set to `Duration::from_secs(2)` and the strategy is `Linear`,
19 /// the program will wait 2 seconds between retries.
20 pub delay: Duration,
21
22 /// The strategy used to calculate delays between retry attempts.
23 ///
24 /// This field determines how the `delay` is applied:
25 /// - `Linear`: Uses a fixed delay between retries.
26 /// - `ExponentialBackoff`: Increases the delay exponentially with each retry.
27 /// - `FibonacciBackoff`: Increases the delay following the Fibonacci sequence with each retry.
28 pub strategy: RetryStrategy,
29
30 /// An optional function to determine if a retry should be attempted.
31 ///
32 /// This field allows you to specify a custom condition for retrying based on the error type `E`.
33 /// It takes a reference to the error (`&E`) and returns a `bool`:
34 /// - `true` if the operation should be retried.
35 /// - `false` if the operation should not be retried, causing it to fail immediately.
36 ///
37 /// If set to `None` (the default), all errors will trigger a retry up to `max_attempts`.
38 /// If set to `Some(fn)`, only errors for which the function returns `true` will be retried.
39 /// In this example, only errors containing the word "transient" will trigger retries.
40 pub retry_condition: Option<fn(&E) -> bool>,
41}
42
43impl<E> Default for RetryConfig<E> {
44 /// Provides a default configuration for retrying operations.
45 ///
46 /// The default configuration includes:
47 /// - `max_attempts`: 3 retries
48 /// - `delay`: 2 seconds between retries
49 /// - `strategy`: `Linear`
50 /// - `retry_condition`: `None`, meaning all errors trigger retries
51 ///
52 /// This implementation allows you to create a `RetryConfig` with sensible
53 /// defaults using `RetryConfig::default()`.
54 fn default() -> Self {
55 RetryConfig {
56 max_attempts: 3,
57 delay: Duration::from_secs(2),
58 strategy: RetryStrategy::Linear,
59 retry_condition: None,
60 }
61 }
62}
63
64impl<E> RetryConfig<E> {
65 /// Creates a new `RetryConfig` with the specified maximum attempts, delay, and strategy.
66 ///
67 /// This constructor initializes a `RetryConfig` with the given `max_attempts`, `delay`,
68 /// and `strategy`, setting `retry_condition` to `None`. When `retry_condition` is `None`,
69 /// all errors will trigger retries up to the specified `max_attempts`.
70 ///
71 /// # Arguments
72 /// * `max_attempts` - The maximum number of attempts (including the initial attempt).
73 /// * `delay` - The base duration to wait between retry attempts.
74 /// * `strategy` - The retry strategy to use (`Linear` or `ExponentialBackoff`).
75 ///
76 /// # Returns
77 /// A new `RetryConfig` instance with the provided settings and no retry condition.
78 ///
79 /// # Examples
80 /// ```
81 /// use std::time::Duration;
82 /// use resilient_rs::config::RetryConfig;
83 /// use resilient_rs::strategies::RetryStrategy;
84 /// let config : RetryConfig<()> = RetryConfig::new(3, Duration::from_secs(1), RetryStrategy::Linear);
85 /// ```
86 pub fn new(max_attempts: usize, delay: Duration, strategy: RetryStrategy) -> Self {
87 RetryConfig {
88 max_attempts,
89 delay,
90 strategy,
91 retry_condition: None,
92 }
93 }
94
95 /// Sets a custom retry condition and returns the modified `RetryConfig`.
96 ///
97 /// This method allows you to specify a function that determines whether an operation should
98 /// be retried based on the error. It takes ownership of the `RetryConfig`, sets the
99 /// `retry_condition` field to the provided function, and returns the updated instance.
100 /// This enables method chaining in a builder-like pattern.
101 ///
102 /// # Arguments
103 /// * `retry_condition` - A function that takes a reference to an error (`&E`) and returns
104 /// `true` if the operation should be retried, or `false` if it should fail immediately.
105 ///
106 /// # Returns
107 /// The updated `RetryConfig` with the specified retry condition.
108 ///
109 /// # Examples
110 /// ```
111 /// use std::time::Duration;
112 /// use resilient_rs::config::RetryConfig;
113 /// use resilient_rs::strategies::RetryStrategy;
114 /// let config = RetryConfig::new(3, Duration::from_secs(1), RetryStrategy::Linear)
115 /// .with_retry_condition(|e: &String| e.contains("transient"));
116 /// ```
117 pub fn with_retry_condition(mut self, retry_condition: fn(&E) -> bool) -> Self {
118 self.retry_condition = Some(retry_condition);
119 self
120 }
121
122 /// Sets a custom retry strategy and returns the modified `RetryConfig`.
123 ///
124 /// This method allows you to specify the retry strategy (`Linear` or `ExponentialBackoff`).
125 /// It takes ownership of the `RetryConfig`, sets the `strategy` field to the provided value,
126 /// and returns the updated instance. This enables method chaining in a builder-like pattern.
127 ///
128 /// # Arguments
129 /// * `strategy` - The retry strategy to use (`Linear` or `ExponentialBackoff`).
130 ///
131 /// # Returns
132 /// The updated `RetryConfig` with the specified retry strategy.
133 ///
134 /// # Examples
135 /// ```
136 /// use std::time::Duration;
137 /// use resilient_rs::config::RetryConfig;
138 /// use resilient_rs::strategies::RetryStrategy;
139 /// let config : RetryConfig<()> = RetryConfig::default()
140 /// .with_strategy(RetryStrategy::ExponentialBackoff);
141 /// ```
142 pub fn with_strategy(mut self, strategy: RetryStrategy) -> Self {
143 self.strategy = strategy;
144 self
145 }
146}
147
148/// Configuration for executable tasks supporting both synchronous and asynchronous operations.
149///
150/// This struct defines execution parameters for tasks that may run either synchronously
151/// or asynchronously, including a timeout duration and an optional fallback function.
152/// It's designed to be passed to functions that handle task execution with support for
153/// both execution models.
154///
155/// # Type Parameters
156/// * `T` - The type of the successful result, must implement `Clone`
157/// * `E` - The type of the error that may occur during execution
158///
159#[derive(Debug)]
160pub struct ExecConfig<T> {
161 /// The maximum duration allowed for task execution before timeout.
162 ///
163 /// This applies to both synchronous and asynchronous operations. For async operations,
164 /// this typically integrates with runtime timeout mechanisms.
165 pub timeout_duration: Duration,
166
167 /// Optional fallback function to execute if the primary task fails or times out.
168 ///
169 /// The fallback must be a synchronous function that returns a `Result`. For async
170 /// contexts, the execution function is responsible for handling the sync-to-async
171 /// transition if needed.
172 pub fallback: Option<fn() -> Result<T, Box<dyn Error>>>,
173}
174
175impl<T> ExecConfig<T>
176where
177 T: Clone,
178{
179 /// Creates a new execution configuration with the specified timeout duration.
180 ///
181 /// Initializes the configuration without a fallback function. This is suitable
182 /// for both synchronous and asynchronous task execution scenarios.
183 ///
184 /// # Arguments
185 /// * `timeout_duration` - Maximum execution time for sync or async operations
186 ///
187 /// # Returns
188 /// A new `ExecConfig` instance configured with the given timeout
189 pub fn new(timeout_duration: Duration) -> Self {
190 ExecConfig {
191 timeout_duration,
192 fallback: None,
193 }
194 }
195
196 /// Sets a fallback function to handle task failure or timeout scenarios.
197 ///
198 /// The fallback is a synchronous function, but can be used in both sync and async
199 /// execution contexts. When used with async operations, the executing function
200 /// should handle any necessary async adaptation.
201 ///
202 /// # Arguments
203 /// * `fallback` - Synchronous function returning a `Result` with matching types
204 pub fn with_fallback(&mut self, fallback: fn() -> Result<T, Box<dyn Error>>) {
205 self.fallback = Some(fallback);
206 }
207}
208
209/// Configuration for a Circuit Breaker.
210///
211/// The `CircuitBreakerConfig` struct holds the static configuration parameters for a circuit breaker.
212/// It defines how the circuit breaker behaves during different states (Closed, Open, and HalfOpen).
213/// These settings determine the thresholds for failures, successes, and the cooldown period for recovery attempts.
214///
215/// Use this struct to configure and initialize a `CircuitBreaker` instance with specific settings.
216///
217/// # Fields
218/// - `failure_threshold`: The maximum number of consecutive failures before the circuit breaker transitions
219/// from `Close` to `Open`. This threshold determines how sensitive the circuit breaker is to failures.
220/// - `success_threshold`: The number of successful operations required in the `HalfOpen` state before
221/// transitioning back to `Close`. This determines how many recovery attempts the system will test before
222/// considering the service restored.
223/// - `cooldown_period`: The duration to wait in the `Open` state before transitioning to `HalfOpen` to test
224/// if the system has recovered. This period allows the failing system time to stabilize and prevents
225/// immediate retries.
226///
227/// # Example
228/// ```
229/// use std::time::Duration;
230/// use resilient_rs::config::CircuitBreakerConfig;
231///
232/// let config = CircuitBreakerConfig::new(3, 5, Duration::from_secs(10));
233/// println!("{:?}", config);
234/// ```
235
236#[derive(Debug, Clone, Copy)]
237pub struct CircuitBreakerConfig {
238 pub failure_threshold: usize,
239 pub success_threshold: usize,
240 pub cooldown_period: Duration,
241}
242
243impl Default for CircuitBreakerConfig {
244 /// # Default Configuration
245 /// The default configuration sets:
246 /// - `failure_threshold` to 5 (max failures before opening the circuit)
247 /// - `success_threshold` to 2 (successes required to close the circuit from HalfOpen)
248 /// - `cooldown_period` to 2 seconds (time to wait before testing recovery)
249 fn default() -> Self {
250 Self {
251 success_threshold: 2,
252 failure_threshold: 5,
253 cooldown_period: Duration::from_secs(2),
254 }
255 }
256}
257
258impl CircuitBreakerConfig {
259 /// Creates a new `CircuitBreakerConfig` instance with the specified settings.
260 ///
261 /// This method constructs a configuration object for a circuit breaker based on the provided thresholds
262 /// and cooldown period. The configuration defines how the circuit breaker will behave during operation.
263 ///
264 /// # Parameters
265 /// - `success_threshold`: The number of successful operations required in the `HalfOpen` state
266 /// to transition back to `Close`. This must be greater than 0 for meaningful recovery.
267 /// - `failure_threshold`: The number of consecutive failures in the `Close` state that will trigger
268 /// a transition to `Open`. This must be greater than 0.
269 /// - `cooldown_period`: The duration to wait in the `Open` state before moving to `HalfOpen` to test
270 /// recovery. Should be long enough to allow the system to stabilize and prevent immediate retries.
271 ///
272 /// # Returns
273 /// A new `CircuitBreakerConfig` instance with the provided parameters.
274 ///
275 /// # Panics
276 /// This function will panic if any parameter is invalid (e.g., zero or negative values for thresholds).
277 ///
278 /// # Example
279 /// ```
280 /// use std::time::Duration;
281 /// use resilient_rs::config::CircuitBreakerConfig;
282 /// let config = CircuitBreakerConfig::new(3, 5, Duration::from_secs(10));
283 /// assert_eq!(config.failure_threshold, 5);
284 /// ```
285 pub fn new(
286 success_threshold: usize,
287 failure_threshold: usize,
288 cooldown_period: Duration,
289 ) -> Self {
290 assert!(
291 success_threshold > 0,
292 "success_threshold must be greater than 0"
293 );
294 assert!(
295 failure_threshold > 0,
296 "failure_threshold must be greater than 0"
297 );
298 assert!(
299 cooldown_period > Duration::ZERO,
300 "cooldown_period must be non-zero"
301 );
302
303 Self {
304 failure_threshold,
305 success_threshold,
306 cooldown_period,
307 }
308 }
309
310 /// Builder-style setter for `failure_threshold`.
311 ///
312 /// This method allows you to modify the `failure_threshold` value after the initial configuration.
313 /// It enables more flexible configuration using a builder pattern.
314 ///
315 /// # Parameters
316 /// - `threshold`: The number of failures required to trigger a transition to `Open`. Must be greater than 0.
317 ///
318 /// # Returns
319 /// A new `CircuitBreakerConfig` instance with the updated `failure_threshold`.
320 ///
321 /// # Example
322 /// ```
323 /// use resilient_rs::config::CircuitBreakerConfig;
324 /// let config = CircuitBreakerConfig::default().with_failure_threshold(3);
325 /// assert_eq!(config.failure_threshold, 3);
326 /// ```
327 pub fn with_failure_threshold(mut self, threshold: usize) -> Self {
328 assert!(threshold > 0, "failure_threshold must be greater than 0");
329 self.failure_threshold = threshold;
330 self
331 }
332
333 /// Builder-style setter for `success_threshold`.
334 ///
335 /// This method allows you to modify the `success_threshold` value after the initial configuration.
336 /// It enables more flexible configuration using a builder pattern.
337 ///
338 /// # Parameters
339 /// - `threshold`: The number of successes required to close the circuit from the `HalfOpen` state. Must be greater than 0.
340 ///
341 /// # Returns
342 /// A new `CircuitBreakerConfig` instance with the updated `success_threshold`.
343 ///
344 /// # Example
345 /// ```
346 /// use resilient_rs::config::CircuitBreakerConfig;
347 /// let config = CircuitBreakerConfig::default().with_success_threshold(4);
348 /// assert_eq!(config.success_threshold, 4);
349 /// ```
350 pub fn with_success_threshold(mut self, threshold: usize) -> Self {
351 assert!(threshold > 0, "success_threshold must be greater than 0");
352 self.success_threshold = threshold;
353 self
354 }
355
356 /// Builder-style setter for `cooldown_period`.
357 ///
358 /// This method allows you to modify the `cooldown_period` value after the initial configuration.
359 /// It enables more flexible configuration using a builder pattern.
360 ///
361 /// # Parameters
362 /// - `period`: The duration to wait before testing the recovery of the system. Must be greater than 0.
363 ///
364 /// # Returns
365 /// A new `CircuitBreakerConfig` instance with the updated `cooldown_period`.
366 ///
367 /// # Example
368 /// ```
369 /// use resilient_rs::config::CircuitBreakerConfig;
370 /// let config = CircuitBreakerConfig::default().with_cooldown_period(std::time::Duration::from_secs(5));
371 /// assert_eq!(config.cooldown_period, std::time::Duration::from_secs(5));
372 /// ```
373 pub fn with_cooldown_period(mut self, period: Duration) -> Self {
374 assert!(period > Duration::ZERO, "cooldown_period must be non-zero");
375 self.cooldown_period = period;
376 self
377 }
378}