1use crate::*;
2use duration_str::*;
3use serde::Deserialize;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
7#[serde(tag = "strategy")]
8pub enum BackoffConfig {
10 Constant {
12 #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
16 delay: Duration,
17
18 #[serde(default = "defaults::max_retries")]
22 max_retries: usize,
23
24 #[serde(default = "defaults::jitter_enabled")]
28 jitter_enabled: bool,
29
30 #[serde(default = "defaults::jitter_seed")]
34 jitter_seed: Option<u64>,
35 },
36
37 Exponential {
39 #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
43 initial_delay: Duration,
44
45 #[serde(default = "defaults::factor")]
49 factor: f32,
50
51 #[serde(
55 default = "defaults::max_delay",
56 deserialize_with = "deserialize_duration"
57 )]
58 max_delay: Duration,
59
60 #[serde(default = "defaults::max_retries")]
64 max_retries: usize,
65
66 #[serde(
70 default = "defaults::max_total_delay",
71 deserialize_with = "deserialize_duration"
72 )]
73 max_total_delay: Duration,
74
75 #[serde(default = "defaults::jitter_enabled")]
79 jitter_enabled: bool,
80
81 #[serde(default = "defaults::jitter_seed")]
85 jitter_seed: Option<u64>,
86 },
87
88 Fibonacci {
90 #[serde(default = "defaults::delay", deserialize_with = "deserialize_duration")]
94 initial_delay: Duration,
95
96 #[serde(
100 default = "defaults::max_delay",
101 deserialize_with = "deserialize_duration"
102 )]
103 max_delay: Duration,
104
105 #[serde(default = "defaults::max_retries")]
109 max_retries: usize,
110
111 #[serde(default = "defaults::jitter_enabled")]
115 jitter_enabled: bool,
116
117 #[serde(default = "defaults::jitter_seed")]
121 jitter_seed: Option<u64>,
122 },
123
124 NoBackoff,
126}
127
128impl backon::BackoffBuilder for BackoffConfig {
129 type Backoff = Backoff;
130
131 fn build(self) -> Backoff {
132 match self {
133 BackoffConfig::Constant {
134 delay,
135 max_retries,
136 jitter_enabled,
137 jitter_seed,
138 } => {
139 let mut builder = backon::ConstantBuilder::new()
140 .with_delay(delay)
141 .with_max_times(max_retries);
142
143 if jitter_enabled {
144 builder = builder.with_jitter();
145 }
146
147 if let Some(jitter_seed) = jitter_seed {
148 builder = builder.with_jitter_seed(jitter_seed);
149 }
150
151 Backoff::Constant(builder.build())
152 }
153
154 BackoffConfig::Exponential {
155 initial_delay,
156 factor,
157 max_delay,
158 max_retries,
159 max_total_delay,
160 jitter_enabled,
161 jitter_seed,
162 } => {
163 let mut builder = backon::ExponentialBuilder::new()
164 .with_min_delay(initial_delay)
165 .with_factor(factor)
166 .with_max_delay(max_delay)
167 .with_max_times(max_retries)
168 .with_total_delay(Some(max_total_delay));
169
170 if jitter_enabled {
171 builder = builder.with_jitter();
172 }
173
174 if let Some(jitter_seed) = jitter_seed {
175 builder = builder.with_jitter_seed(jitter_seed);
176 }
177
178 Backoff::Exponential(builder.build())
179 }
180
181 BackoffConfig::Fibonacci {
182 initial_delay,
183 max_delay,
184 max_retries,
185 jitter_enabled,
186 jitter_seed,
187 } => {
188 let mut builder = backon::FibonacciBuilder::new()
189 .with_min_delay(initial_delay)
190 .with_max_delay(max_delay)
191 .with_max_times(max_retries);
192
193 if jitter_enabled {
194 builder = builder.with_jitter();
195 }
196
197 if let Some(jitter_seed) = jitter_seed {
198 builder = builder.with_jitter_seed(jitter_seed);
199 }
200
201 Backoff::Fibonacci(builder.build())
202 }
203
204 BackoffConfig::NoBackoff => Backoff::NoBackoff,
205 }
206 }
207}
208
209pub mod defaults {
211 use std::time::Duration;
212
213 pub const fn delay() -> Duration {
215 Duration::from_millis(500)
216 }
217
218 pub const fn max_retries() -> usize {
220 4
221 }
222
223 pub const fn jitter_enabled() -> bool {
225 true
226 }
227
228 pub const fn jitter_seed() -> Option<u64> {
230 None
231 }
232
233 pub const fn factor() -> f32 {
235 2.0
236 }
237
238 pub const fn max_delay() -> Duration {
240 Duration::from_secs(30)
241 }
242
243 pub const fn max_total_delay() -> Duration {
245 Duration::from_secs(60)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use backon::BackoffBuilder;
253 use std::time::Duration;
254
255 #[test]
256 fn constant_backoff_config_to_backoff() {
257 let config = BackoffConfig::Constant {
258 delay: Duration::from_secs(1),
259 max_retries: 3,
260 jitter_enabled: false,
261 jitter_seed: None,
262 };
263
264 let backoff = config.build();
265 assert!(matches!(backoff, Backoff::Constant(_)));
266
267 assert_eq!(
268 backoff
269 .take(100)
270 .map(|duration| duration.as_millis())
271 .collect::<Vec<_>>(),
272 vec![1000; 3]
273 );
274 }
275
276 #[test]
277 fn exponential_backoff_config_to_backoff() {
278 let config = BackoffConfig::Exponential {
279 initial_delay: Duration::from_millis(100),
280 factor: 2_f32,
281 max_delay: Duration::from_millis(800),
282 max_retries: 5,
283 max_total_delay: Duration::from_secs(1000),
284 jitter_enabled: false,
285 jitter_seed: None,
286 };
287
288 let backoff = config.build();
289 assert!(matches!(backoff, Backoff::Exponential(_)));
290
291 assert_eq!(
292 backoff
293 .take(100)
294 .map(|duration| duration.as_millis())
295 .collect::<Vec<_>>(),
296 vec![100, 200, 400, 800, 800]
297 );
298 }
299
300 #[test]
301 fn exponential_backoff_config_to_backoff_with_max_total_delay() {
302 let config = BackoffConfig::Exponential {
303 initial_delay: Duration::from_millis(100),
304 factor: 2_f32,
305 max_delay: Duration::from_millis(800),
306 max_retries: 5,
307 max_total_delay: Duration::from_millis(1500 + 1),
308 jitter_enabled: false,
309 jitter_seed: None,
310 };
311
312 let backoff = config.build();
313 assert!(matches!(backoff, Backoff::Exponential(_)));
314
315 assert_eq!(
316 backoff
317 .take(100)
318 .map(|duration| duration.as_millis())
319 .collect::<Vec<_>>(),
320 vec![100, 200, 400, 800]
321 );
322 }
323
324 #[test]
325 fn fibonacci_backoff_config_to_backoff() {
326 let config = BackoffConfig::Fibonacci {
327 initial_delay: Duration::from_millis(100),
328 max_delay: Duration::from_millis(800),
329 max_retries: 5,
330 jitter_enabled: false,
331 jitter_seed: None,
332 };
333
334 let backoff = config.build();
335 assert!(matches!(backoff, Backoff::Fibonacci(_)));
336
337 assert_eq!(
338 backoff
339 .take(usize::MAX)
340 .map(|duration| duration.as_millis())
341 .collect::<Vec<_>>(),
342 vec![100, 100, 200, 300, 500]
343 );
344 }
345
346 #[test]
347 fn no_backoff_backoff_config_to_backoff() {
348 let config = BackoffConfig::NoBackoff;
349
350 let mut backoff = config.build();
351 assert!(matches!(backoff, Backoff::NoBackoff));
352
353 assert!(backoff.next().is_none());
354 }
355}