1#![forbid(unsafe_code)]
83#![deny(clippy::unwrap_used)]
84
85pub mod delay;
86pub mod delay_executor;
87pub mod delay_strategy;
88mod duration;
89mod fallible;
90mod tracked_iterator;
91
92use std::fmt::Debug;
93use std::marker::PhantomData;
94
95#[cfg(feature = "async")]
96use crate::delay_executor::AsyncDelayExecutor;
97use crate::delay_executor::DelayExecutor;
98use crate::delay_executor::ThreadSleep;
99#[cfg(feature = "async-tokio")]
100use crate::delay_executor::TokioSleep;
101use crate::delay_strategy::DelayStrategy;
102
103pub use duration::IntoStdDuration;
104pub use duration::StdDuration;
105pub use fallible::NeedsRetry;
106
107#[tracing::instrument(level = "debug", name = "retry", skip(operation))]
108#[must_use = "Call `delayed_by` on the returned value to complete the retry strategy configuration."]
109pub fn retry<Out, Op>(operation: Op) -> NeedsDelayStrategy<Out, Op>
110where
111 Out: NeedsRetry + Debug,
112 Op: Fn() -> Out,
113{
114 NeedsDelayStrategy { operation }
115}
116
117pub struct NeedsDelayStrategy<Out, Op>
118where
119 Out: NeedsRetry + Debug,
120 Op: Fn() -> Out,
121{
122 operation: Op,
123}
124
125impl<Out, Op> NeedsDelayStrategy<Out, Op>
126where
127 Out: NeedsRetry + Debug,
128 Op: Fn() -> Out,
129{
130 pub fn delayed_by<DelayStrat>(self, delay: DelayStrat) -> Out
131 where
132 DelayStrat: DelayStrategy<StdDuration>,
133 {
134 retry_with_options(
135 self.operation,
136 RetryOptions {
137 delay_strategy: delay,
138 delay_executor: ThreadSleep,
139 _marker: PhantomData,
140 },
141 )
142 }
143}
144
145#[derive(Debug)]
146pub struct RetryOptions<
147 Delay: Debug + Clone,
148 DelayStrat: DelayStrategy<Delay>,
149 DelayExec: DelayExecutor<Delay>,
150> {
151 pub delay_strategy: DelayStrat,
152 pub delay_executor: DelayExec,
153 pub _marker: PhantomData<Delay>,
154}
155
156#[tracing::instrument(level = "debug", name = "retry_with_options", skip(operation))]
157pub fn retry_with_options<Delay, DelayStrat, DelayExec, Out, Op>(
158 operation: Op,
159 mut options: RetryOptions<Delay, DelayStrat, DelayExec>,
160) -> Out
161where
162 Delay: Debug + Clone,
163 DelayStrat: DelayStrategy<Delay> + Debug,
164 DelayExec: DelayExecutor<Delay> + Debug,
165 Out: NeedsRetry + Debug,
166 Op: Fn() -> Out,
167{
168 let mut tries: usize = 1;
169 loop {
170 let out = operation();
171 match out.needs_retry() {
172 false => return out,
173 true => match options.delay_strategy.next_delay() {
174 Some(delay) => {
175 tracing::debug!(tries, delay = ?delay, "Operation was not successful. Waiting...");
176 options.delay_executor.delay_by(delay.clone());
177 tries += 1;
178 }
179 None => {
180 tracing::error!(tries, last_output = ?out, "Operation was not successful after maximum retries. Aborting with last output seen.");
181 return out;
182 }
183 },
184 };
185 }
186}
187
188#[cfg(feature = "async")]
189#[tracing::instrument(level = "debug", name = "retry_async", skip(operation))]
190pub fn retry_async<Out, Op>(operation: Op) -> AsyncNeedsDelayStrategy<Out, Op>
191where
192 Out: NeedsRetry + Debug,
193 Op: AsyncFn() -> Out,
194{
195 AsyncNeedsDelayStrategy { operation }
196}
197
198#[cfg(feature = "async")]
199pub struct AsyncNeedsDelayStrategy<Out, Op>
200where
201 Out: NeedsRetry + Debug,
202 Op: AsyncFn() -> Out,
203{
204 operation: Op,
205}
206
207#[cfg(feature = "async")]
208impl<Out, Op> AsyncNeedsDelayStrategy<Out, Op>
209where
210 Out: NeedsRetry + Debug,
211 Op: AsyncFn() -> Out,
212{
213 pub async fn delayed_by<DelayStrat>(self, delay: DelayStrat) -> Out
214 where
215 DelayStrat: DelayStrategy<StdDuration>,
216 {
217 retry_async_with_options(
218 self.operation,
219 RetryAsyncOptions {
220 delay_strategy: delay,
221 delay_executor: TokioSleep,
222 _marker: PhantomData,
223 },
224 )
225 .await
226 }
227}
228
229#[cfg(feature = "async")]
230#[derive(Debug)]
231pub struct RetryAsyncOptions<
232 Delay: Debug + Clone,
233 DelayStrat: DelayStrategy<Delay>,
234 DelayExec: AsyncDelayExecutor<Delay>,
235> {
236 pub delay_strategy: DelayStrat,
237 pub delay_executor: DelayExec,
238 pub _marker: PhantomData<Delay>,
239}
240
241#[cfg(feature = "async")]
242#[tracing::instrument(
243 level = "debug",
244 name = "retry_async_with_delay_strategy",
245 skip(operation)
246)]
247pub async fn retry_async_with_options<Delay, DelayStrat, DelayExec, Out>(
248 operation: impl AsyncFn() -> Out,
249 mut options: RetryAsyncOptions<Delay, DelayStrat, DelayExec>,
250) -> Out
251where
252 Delay: Debug + Clone,
253 DelayStrat: DelayStrategy<Delay>,
254 DelayExec: AsyncDelayExecutor<Delay>,
255 Out: NeedsRetry + Debug,
256{
257 let mut tries: usize = 1;
258 loop {
259 let out = operation().await;
260 match out.needs_retry() {
261 false => return out,
262 true => match options.delay_strategy.next_delay() {
263 Some(delay) => {
264 tracing::debug!(tries, delay = ?delay, "Operation was not successful. Waiting...");
265 options.delay_executor.delay_by(delay.clone()).await;
266 tries += 1;
267 }
268 None => {
269 tracing::error!(tries, last_output = ?out, "Operation was not successful after maximum retries. Aborting with last output seen.");
270 return out;
271 }
272 },
273 };
274 }
275}