pub fn option_to_result<T, E, F>(option: Option<T>, err_fn: F) -> Result<T, E>
where
F: FnOnce() -> E,
{
option.ok_or_else(err_fn)
}
pub fn with_context<T, E, C, F, G>(
result: Result<T, E>,
context_fn: F,
error_mapper: G,
) -> Result<T, E>
where
E: std::fmt::Display,
F: FnOnce() -> C,
C: std::fmt::Display,
G: FnOnce(String) -> E,
{
result.map_err(|err| {
let context = context_fn();
error_mapper(format!("{}: {}", context, err))
})
}
pub fn result_to_option<T, E: std::fmt::Display>(result: Result<T, E>) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(err) => {
tracing::warn!(error = %err, "Converting error result to None");
None
},
}
}
pub async fn retry<T, E, F, Fut>(
operation: F,
max_attempts: usize,
delay: std::time::Duration,
) -> Result<T, E>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
if max_attempts == 0 {
return operation().await;
}
let mut attempts = 0;
let mut last_error = None;
while attempts < max_attempts {
match operation().await {
Ok(value) => return Ok(value),
Err(err) => {
attempts += 1;
if attempts < max_attempts {
tracing::warn!(attempt = attempts, error = %err, "Attempt failed; retrying");
tokio::time::sleep(delay).await;
}
last_error = Some(err);
},
}
}
match last_error {
Some(err) => Err(err),
None => operation().await,
}
}
#[cfg(test)]
mod tests {
use super::retry;
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};
#[tokio::test]
async fn test_retry_zero_attempts_runs_once_ok() {
let calls = Arc::new(AtomicUsize::new(0));
let calls_for_closure = calls.clone();
let result: Result<u32, &'static str> = retry(
move || {
let calls = calls_for_closure.clone();
async move {
calls.fetch_add(1, Ordering::SeqCst);
Ok(42)
}
},
0,
Duration::from_millis(1),
)
.await;
assert_eq!(result.unwrap(), 42);
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_retry_zero_attempts_runs_once_err() {
let result: Result<(), &'static str> =
retry(|| async { Err("fail") }, 0, Duration::from_millis(1)).await;
assert_eq!(result.unwrap_err(), "fail");
}
}