tower_retry_plus/
config.rs1use crate::backoff::{ExponentialBackoff, FixedInterval, IntervalFunction};
2use crate::events::RetryEvent;
3use crate::policy::{RetryPolicy, RetryPredicate};
4use std::sync::Arc;
5use std::time::Duration;
6use tower_resilience_core::events::{EventListeners, FnListener};
7
8pub struct RetryConfig<E> {
10 pub(crate) policy: RetryPolicy<E>,
11 pub(crate) event_listeners: EventListeners<RetryEvent>,
12 pub(crate) name: String,
13}
14
15impl<E> RetryConfig<E> {
16 pub fn builder() -> RetryConfigBuilder<E> {
18 RetryConfigBuilder::new()
19 }
20
21 pub fn layer(self) -> crate::RetryLayer<E> {
23 crate::RetryLayer::new(self)
24 }
25}
26
27pub struct RetryConfigBuilder<E> {
29 max_attempts: usize,
30 interval_fn: Option<Arc<dyn IntervalFunction>>,
31 retry_predicate: Option<RetryPredicate<E>>,
32 event_listeners: EventListeners<RetryEvent>,
33 name: String,
34}
35
36impl<E> Default for RetryConfigBuilder<E> {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl<E> RetryConfigBuilder<E> {
43 pub fn new() -> Self {
50 Self {
51 max_attempts: 3,
52 interval_fn: None,
53 retry_predicate: None,
54 event_listeners: EventListeners::new(),
55 name: "<unnamed>".to_string(),
56 }
57 }
58
59 pub fn max_attempts(mut self, max_attempts: usize) -> Self {
64 self.max_attempts = max_attempts;
65 self
66 }
67
68 pub fn fixed_backoff(mut self, duration: Duration) -> Self {
70 self.interval_fn = Some(Arc::new(FixedInterval::new(duration)));
71 self
72 }
73
74 pub fn exponential_backoff(mut self, initial_interval: Duration) -> Self {
76 self.interval_fn = Some(Arc::new(ExponentialBackoff::new(initial_interval)));
77 self
78 }
79
80 pub fn backoff<I>(mut self, interval_fn: I) -> Self
82 where
83 I: IntervalFunction + 'static,
84 {
85 self.interval_fn = Some(Arc::new(interval_fn));
86 self
87 }
88
89 pub fn retry_on<F>(mut self, predicate: F) -> Self
91 where
92 F: Fn(&E) -> bool + Send + Sync + 'static,
93 {
94 self.retry_predicate = Some(Arc::new(predicate));
95 self
96 }
97
98 pub fn name<S: Into<String>>(mut self, name: S) -> Self {
100 self.name = name.into();
101 self
102 }
103
104 pub fn on_retry<F>(mut self, f: F) -> Self
106 where
107 F: Fn(usize, Duration) + Send + Sync + 'static,
108 {
109 self.event_listeners.add(FnListener::new(move |event| {
110 if let RetryEvent::Retry { attempt, delay, .. } = event {
111 f(*attempt, *delay);
112 }
113 }));
114 self
115 }
116
117 pub fn on_success<F>(mut self, f: F) -> Self
119 where
120 F: Fn(usize) + Send + Sync + 'static,
121 {
122 self.event_listeners.add(FnListener::new(move |event| {
123 if let RetryEvent::Success { attempts, .. } = event {
124 f(*attempts);
125 }
126 }));
127 self
128 }
129
130 pub fn on_error<F>(mut self, f: F) -> Self
132 where
133 F: Fn(usize) + Send + Sync + 'static,
134 {
135 self.event_listeners.add(FnListener::new(move |event| {
136 if let RetryEvent::Error { attempts, .. } = event {
137 f(*attempts);
138 }
139 }));
140 self
141 }
142
143 pub fn on_ignored_error<F>(mut self, f: F) -> Self
145 where
146 F: Fn() + Send + Sync + 'static,
147 {
148 self.event_listeners.add(FnListener::new(move |event| {
149 if matches!(event, RetryEvent::IgnoredError { .. }) {
150 f();
151 }
152 }));
153 self
154 }
155
156 pub fn build(self) -> RetryConfig<E> {
158 let interval_fn = self
159 .interval_fn
160 .unwrap_or_else(|| Arc::new(ExponentialBackoff::new(Duration::from_millis(100))));
161
162 let mut policy = RetryPolicy::new(self.max_attempts, interval_fn);
163 if let Some(predicate) = self.retry_predicate {
164 policy.retry_predicate = Some(predicate);
165 }
166
167 RetryConfig {
168 policy,
169 event_listeners: self.event_listeners,
170 name: self.name,
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use std::sync::atomic::{AtomicUsize, Ordering};
179
180 #[test]
181 fn test_builder_defaults() {
182 let config: RetryConfig<std::io::Error> = RetryConfig::builder().build();
183 assert_eq!(config.policy.max_attempts, 3);
184 assert_eq!(config.name, "<unnamed>");
185 }
186
187 #[test]
188 fn test_builder_custom_values() {
189 let config: RetryConfig<std::io::Error> = RetryConfig::builder()
190 .max_attempts(5)
191 .fixed_backoff(Duration::from_secs(2))
192 .name("test-retry")
193 .build();
194
195 assert_eq!(config.policy.max_attempts, 5);
196 assert_eq!(config.policy.next_backoff(0), Duration::from_secs(2));
197 assert_eq!(config.name, "test-retry");
198 }
199
200 #[test]
201 fn test_event_listeners() {
202 let retry_count = Arc::new(AtomicUsize::new(0));
203 let success_count = Arc::new(AtomicUsize::new(0));
204
205 let rc = Arc::clone(&retry_count);
206 let sc = Arc::clone(&success_count);
207
208 let config: RetryConfig<std::io::Error> = RetryConfig::builder()
209 .on_retry(move |_, _| {
210 rc.fetch_add(1, Ordering::SeqCst);
211 })
212 .on_success(move |_| {
213 sc.fetch_add(1, Ordering::SeqCst);
214 })
215 .build();
216
217 let event = RetryEvent::Retry {
219 pattern_name: "test".to_string(),
220 timestamp: std::time::Instant::now(),
221 attempt: 1,
222 delay: Duration::from_secs(1),
223 };
224 config.event_listeners.emit(&event);
225
226 let event = RetryEvent::Success {
227 pattern_name: "test".to_string(),
228 timestamp: std::time::Instant::now(),
229 attempts: 2,
230 };
231 config.event_listeners.emit(&event);
232
233 assert_eq!(retry_count.load(Ordering::SeqCst), 1);
234 assert_eq!(success_count.load(Ordering::SeqCst), 1);
235 }
236}