1#![cfg_attr(
52 feature = "random",
53 doc = r##"
54To apply random jitter to any delay strategy, the [`delay::jitter`] function can be used in
55combination with the [`Iterator`] API:
56
57```
58# use retry::retry;
59# use retry::delay::{Exponential, jitter};
60let mut collection = vec![1, 2, 3].into_iter();
61
62let result = retry(Exponential::from_millis(10).map(jitter).take(3), || {
63 match collection.next() {
64 Some(n) if n == 3 => Ok("n is 3!"),
65 Some(_) => Err("n must be 3!"),
66 None => Err("n was never 3!"),
67 }
68});
69
70assert!(result.is_ok());
71```
72"##
73)]
74#![deny(missing_debug_implementations, missing_docs, warnings)]
127
128use std::{
129 error::Error as StdError,
130 fmt::{Display, Error as FmtError, Formatter},
131 thread::sleep,
132 time::Duration,
133};
134
135pub mod delay;
136mod opresult;
137
138#[doc(inline)]
139pub use opresult::OperationResult;
140
141pub fn retry<I, O, R, E, OR>(iterable: I, mut operation: O) -> Result<R, Error<E>>
144where
145 I: IntoIterator<Item = Duration>,
146 O: FnMut() -> OR,
147 OR: Into<OperationResult<R, E>>,
148{
149 retry_with_index(iterable, |_| operation())
150}
151
152pub fn retry_with_index<I, O, R, E, OR>(iterable: I, mut operation: O) -> Result<R, Error<E>>
156where
157 I: IntoIterator<Item = Duration>,
158 O: FnMut(u64) -> OR,
159 OR: Into<OperationResult<R, E>>,
160{
161 let mut iterator = iterable.into_iter();
162 let mut current_try = 1;
163 let mut total_delay = Duration::default();
164
165 loop {
166 match operation(current_try).into() {
167 OperationResult::Ok(value) => return Ok(value),
168 OperationResult::Retry(error) => {
169 if let Some(delay) = iterator.next() {
170 sleep(delay);
171 current_try += 1;
172 total_delay += delay;
173 } else {
174 return Err(Error {
175 error,
176 total_delay,
177 tries: current_try,
178 });
179 }
180 }
181 OperationResult::Err(error) => {
182 return Err(Error {
183 error,
184 total_delay,
185 tries: current_try,
186 });
187 }
188 }
189 }
190}
191
192#[derive(Debug, PartialEq, Eq)]
194pub struct Error<E> {
195 pub error: E,
197 pub total_delay: Duration,
201 pub tries: u64,
203}
204
205impl<E> Display for Error<E>
206where
207 E: Display,
208{
209 fn fmt(&self, formatter: &mut Formatter) -> Result<(), FmtError> {
210 Display::fmt(&self.error, formatter)
211 }
212}
213
214impl<E> StdError for Error<E>
215where
216 E: StdError,
217{
218 #[allow(deprecated)]
219 fn description(&self) -> &str {
220 self.error.description()
221 }
222
223 fn cause(&self) -> Option<&dyn StdError> {
224 Some(&self.error)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use std::time::Duration;
231
232 use super::delay::{Exponential, Fixed, NoDelay};
233 use super::opresult::OperationResult;
234 use super::{retry, retry_with_index, Error};
235
236 #[test]
237 fn succeeds_with_infinite_retries() {
238 let mut collection = vec![1, 2, 3, 4, 5].into_iter();
239
240 let value = retry(NoDelay, || match collection.next() {
241 Some(n) if n == 5 => Ok(n),
242 Some(_) => Err("not 5"),
243 None => Err("not 5"),
244 })
245 .unwrap();
246
247 assert_eq!(value, 5);
248 }
249
250 #[test]
251 fn succeeds_with_maximum_retries() {
252 let mut collection = vec![1, 2].into_iter();
253
254 let value = retry(NoDelay.take(1), || match collection.next() {
255 Some(n) if n == 2 => Ok(n),
256 Some(_) => Err("not 2"),
257 None => Err("not 2"),
258 })
259 .unwrap();
260
261 assert_eq!(value, 2);
262 }
263
264 #[test]
265 fn fails_after_last_try() {
266 let mut collection = vec![1].into_iter();
267
268 let res = retry(NoDelay.take(1), || match collection.next() {
269 Some(n) if n == 2 => Ok(n),
270 Some(_) => Err("not 2"),
271 None => Err("not 2"),
272 });
273
274 assert_eq!(
275 res,
276 Err(Error {
277 error: "not 2",
278 tries: 2,
279 total_delay: Duration::from_millis(0)
280 })
281 );
282 }
283
284 #[test]
285 fn fatal_errors() {
286 let mut collection = vec![1].into_iter();
287
288 let res = retry(NoDelay.take(2), || match collection.next() {
289 Some(n) if n == 2 => OperationResult::Ok(n),
290 Some(_) => OperationResult::Err("no retry"),
291 None => OperationResult::Err("not 2"),
292 });
293
294 assert_eq!(
295 res,
296 Err(Error {
297 error: "no retry",
298 tries: 1,
299 total_delay: Duration::from_millis(0)
300 })
301 );
302 }
303
304 #[test]
305 fn succeeds_with_fixed_delay() {
306 let mut collection = vec![1, 2].into_iter();
307
308 let value = retry(Fixed::from_millis(1), || match collection.next() {
309 Some(n) if n == 2 => Ok(n),
310 Some(_) => Err("not 2"),
311 None => Err("not 2"),
312 })
313 .unwrap();
314
315 assert_eq!(value, 2);
316 }
317
318 #[test]
319 fn fixed_delay_from_duration() {
320 assert_eq!(
321 Fixed::from_millis(1_000).next(),
322 Fixed::from(Duration::from_secs(1)).next(),
323 );
324 }
325
326 #[test]
327 fn succeeds_with_exponential_delay() {
328 let mut collection = vec![1, 2].into_iter();
329
330 let value = retry(Exponential::from_millis(1), || match collection.next() {
331 Some(n) if n == 2 => Ok(n),
332 Some(_) => Err("not 2"),
333 None => Err("not 2"),
334 })
335 .unwrap();
336
337 assert_eq!(value, 2);
338 }
339
340 #[test]
341 fn succeeds_with_exponential_delay_with_factor() {
342 let mut collection = vec![1, 2].into_iter();
343
344 let value = retry(
345 Exponential::from_millis_with_factor(1000, 2.0),
346 || match collection.next() {
347 Some(n) if n == 2 => Ok(n),
348 Some(_) => Err("not 2"),
349 None => Err("not 2"),
350 },
351 )
352 .unwrap();
353
354 assert_eq!(value, 2);
355 }
356
357 #[test]
358 #[cfg(feature = "random")]
359 fn succeeds_with_ranged_delay() {
360 use super::delay::Range;
361
362 let mut collection = vec![1, 2].into_iter();
363
364 let value = retry(Range::from_millis_exclusive(1, 10), || {
365 match collection.next() {
366 Some(n) if n == 2 => Ok(n),
367 Some(_) => Err("not 2"),
368 None => Err("not 2"),
369 }
370 })
371 .unwrap();
372
373 assert_eq!(value, 2);
374 }
375
376 #[test]
377 fn succeeds_with_index() {
378 let mut collection = vec![1, 2, 3].into_iter();
379
380 let value = retry_with_index(NoDelay, |current_try| match collection.next() {
381 Some(n) if n == current_try => Ok(n),
382 Some(_) => Err("not current_try"),
383 None => Err("not current_try"),
384 })
385 .unwrap();
386
387 assert_eq!(value, 1);
388 }
389}