avail_rust_client/
utils.rs

1use crate::platform::sleep;
2use std::{fmt::Debug, time::Duration};
3
4#[cfg(feature = "tracing")]
5pub(crate) fn trace_warn(message: &str) {
6	tracing::warn!(target: "lib", message);
7}
8
9/// Repeatedly executes an asynchronous operation until it succeeds or retries are exhausted.
10///
11/// # Arguments
12///
13/// * `f` - Factory producing a future that performs the operation.
14/// * `retry_on_error` - When `true`, the function sleeps and retries on failure.
15///
16/// # Returns
17///
18/// Returns the successful output of `f` or propagates the last encountered error.
19///
20/// # Errors
21///
22/// Returns the final error emitted by `f` once no retries remain.
23///
24/// # Examples
25///
26/// ```no_run
27/// use avail_rust_client::utils::with_retry_on_error;
28///
29/// async fn fetch_value() -> Result<u32, &'static str> {
30///     Err("transient failure")
31/// }
32///
33/// async fn run() -> Result<u32, &'static str> {
34///     with_retry_on_error(fetch_value, true).await
35/// }
36/// ```
37pub async fn with_retry_on_error<F, Fut, O, E>(f: F, retry_on_error: bool) -> Result<O, E>
38where
39	F: Fn() -> Fut,
40	Fut: Future<Output = Result<O, E>>,
41	E: Debug,
42{
43	let mut sleep_duration: Vec<u64> = vec![8, 5, 3, 2, 1];
44	if !retry_on_error {
45		sleep_duration.clear();
46	}
47
48	loop {
49		match f().await {
50			Ok(x) => return Ok(x),
51			Err(err) => {
52				let Some(duration) = sleep_duration.pop() else {
53					return Err(err);
54				};
55
56				#[cfg(feature = "tracing")]
57				trace_warn(&std::format!("Retrying after error: {:?}; next attempt in {}s", err, duration));
58				sleep(Duration::from_secs(duration)).await;
59			},
60		};
61	}
62}
63
64/// Executes an asynchronous operation, retrying on errors and optionally on `None` results.
65///
66/// # Arguments
67///
68/// * `f` - Factory producing a future that returns `Option<O>`.
69/// * `retry_on_error` - Controls whether errors trigger retries.
70/// * `retry_on_none` - When `true`, `None` results trigger retries until exhausted.
71///
72/// # Returns
73///
74/// Returns `Ok(Some(O))` on success, `Ok(None)` if no value was produced, or the last error emitted.
75///
76/// # Errors
77///
78/// Propagates the final error returned by `f` after exhausting retries.
79///
80/// # Examples
81///
82/// ```no_run
83/// use avail_rust_client::utils::with_retry_on_error_and_none;
84///
85/// async fn maybe_fetch() -> Result<Option<u32>, &'static str> {
86///     Ok(None)
87/// }
88///
89/// async fn run() -> Result<Option<u32>, &'static str> {
90///     with_retry_on_error_and_none(maybe_fetch, true, true).await
91/// }
92/// ```
93pub async fn with_retry_on_error_and_none<F, Fut, O, E>(
94	f: F,
95	retry_on_error: bool,
96	retry_on_none: bool,
97) -> Result<Option<O>, E>
98where
99	F: Fn() -> Fut,
100	Fut: Future<Output = Result<Option<O>, E>>,
101	E: Debug,
102{
103	let mut sleep_duration: Vec<u64> = vec![8, 5, 3, 2, 1];
104	loop {
105		match f().await {
106			Ok(Some(x)) => return Ok(Some(x)),
107			Ok(None) if !retry_on_none => {
108				return Ok(None);
109			},
110			Ok(None) => {
111				let Some(duration) = sleep_duration.pop() else {
112					return Ok(None);
113				};
114
115				#[cfg(feature = "tracing")]
116				trace_warn(&std::format!(
117					"Received None result; retrying in {}s because retry_on_none is enabled",
118					duration
119				));
120				sleep(Duration::from_secs(duration)).await;
121			},
122			Err(err) if !retry_on_error => {
123				return Err(err);
124			},
125			Err(err) => {
126				let Some(duration) = sleep_duration.pop() else {
127					return Err(err);
128				};
129
130				#[cfg(feature = "tracing")]
131				trace_warn(&std::format!(
132					"Retrying after error while awaiting Option result: {:?}; next attempt in {}s",
133					err,
134					duration
135				));
136				sleep(Duration::from_secs(duration)).await;
137			},
138		};
139	}
140}