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}