chrono_machines 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/Cargo.lock +679 -0
  4. data/Cargo.toml +7 -0
  5. data/lib/chrono_machines/version.rb +1 -1
  6. data/lib/chrono_machines.rb +1 -0
  7. metadata +4 -20
  8. data/ext/chrono_machines_native/target/debug/build/clang-sys-414c893ed9493fe4/out/common.rs +0 -355
  9. data/ext/chrono_machines_native/target/debug/build/clang-sys-414c893ed9493fe4/out/dynamic.rs +0 -276
  10. data/ext/chrono_machines_native/target/debug/build/clang-sys-414c893ed9493fe4/out/macros.rs +0 -49
  11. data/ext/chrono_machines_native/target/debug/build/clang-sys-b1d0ba329a011cdd/out/common.rs +0 -355
  12. data/ext/chrono_machines_native/target/debug/build/clang-sys-b1d0ba329a011cdd/out/dynamic.rs +0 -276
  13. data/ext/chrono_machines_native/target/debug/build/clang-sys-b1d0ba329a011cdd/out/macros.rs +0 -49
  14. data/ext/chrono_machines_native/target/debug/build/rb-sys-7d416123f8551038/out/bindings-0.9.124-mri-arm64-darwin23-4.0.3.rs +0 -9057
  15. data/ext/chrono_machines_native/target/debug/build/rb-sys-848783778984ec5d/out/bindings-0.9.124-mri-arm64-darwin23-3.4.8.rs +0 -8933
  16. data/ext/chrono_machines_native/target/debug/build/rb-sys-ce17442a66050c01/out/bindings-0.9.128-mri-arm64-darwin23-3.4.8.rs +0 -8933
  17. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/Cargo.toml +0 -76
  18. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/backoff.rs +0 -497
  19. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/dsl.rs +0 -108
  20. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/lib.rs +0 -352
  21. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/policy.rs +0 -189
  22. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/retry.rs +0 -1489
  23. data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/sleep.rs +0 -97
  24. data/ext/chrono_machines_native/target/package/chrono_machines_native-0.2.3/Cargo.toml +0 -56
  25. data/ext/chrono_machines_native/target/package/chrono_machines_native-0.2.3/src/lib.rs +0 -192
@@ -1,76 +0,0 @@
1
- # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
2
- #
3
- # When uploading crates to the registry Cargo will automatically
4
- # "normalize" Cargo.toml files for maximal compatibility
5
- # with all versions of Cargo and also rewrite `path` dependencies
6
- # to registry (e.g., crates.io) dependencies.
7
- #
8
- # If you are reading this file be aware that the original Cargo.toml
9
- # will likely look very different (and much more reasonable).
10
- # See Cargo.toml.orig for the original contents.
11
-
12
- [package]
13
- edition = "2024"
14
- name = "chrono-machines"
15
- version = "0.3.2"
16
- authors = ["Abdelkader Boudih <terminale@gmail.com>"]
17
- build = false
18
- autolib = false
19
- autobins = false
20
- autoexamples = false
21
- autotests = false
22
- autobenches = false
23
- description = "Exponential, constant, and Fibonacci backoff retry library with full jitter support - no_std compatible"
24
- readme = "README.md"
25
- keywords = [
26
- "retry",
27
- "backoff",
28
- "exponential",
29
- "jitter",
30
- "resilience",
31
- ]
32
- categories = [
33
- "no-std",
34
- "algorithms",
35
- ]
36
- license = "MIT"
37
- repository = "https://github.com/seuros/chrono_machines"
38
- resolver = "2"
39
-
40
- [features]
41
- alloc = []
42
- async = [
43
- "std",
44
- "dep:async-trait",
45
- ]
46
- default = ["std"]
47
- std = [
48
- "alloc",
49
- "rand/std",
50
- "rand/std_rng",
51
- ]
52
-
53
- [lib]
54
- name = "chrono_machines"
55
- crate-type = ["lib"]
56
- path = "src/lib.rs"
57
-
58
- [dependencies.async-trait]
59
- version = "0.1"
60
- optional = true
61
-
62
- [dependencies.rand]
63
- version = "0.10"
64
- features = [
65
- "std_rng",
66
- "sys_rng",
67
- ]
68
- default-features = false
69
-
70
- [dev-dependencies.tokio]
71
- version = "1"
72
- features = [
73
- "rt",
74
- "macros",
75
- "time",
76
- ]
@@ -1,497 +0,0 @@
1
- //! Backoff strategy implementations for retry mechanisms
2
- //!
3
- //! This module provides various backoff strategies to control delay timing
4
- //! between retry attempts.
5
-
6
- use rand::Rng;
7
- use rand::RngExt;
8
-
9
- /// Calculate the nth Fibonacci number (1-indexed): 1, 1, 2, 3, 5, 8, 13, ...
10
- pub fn fibonacci(n: u8) -> u64 {
11
- match n {
12
- 0 => 0,
13
- 1 | 2 => 1,
14
- _ => {
15
- let mut a = 1u64;
16
- let mut b = 1u64;
17
- for _ in 2..n {
18
- let next = a.saturating_add(b);
19
- a = b;
20
- b = next;
21
- }
22
- b
23
- }
24
- }
25
- }
26
-
27
- /// Blend a base delay with jitter using the full-jitter algorithm.
28
- ///
29
- /// `jitter_factor` is clamped to `[0.0, 1.0]`: `0.0` returns `base` unchanged,
30
- /// `1.0` yields a uniform value in `[0, base]`.
31
- fn apply_jitter<R: Rng>(base: f64, jitter_factor: f64, rng: &mut R) -> u64 {
32
- let jitter_factor = jitter_factor.clamp(0.0, 1.0);
33
- let random_scalar: f64 = rng.random_range(0.0..=1.0);
34
- let jitter_blend = 1.0 - jitter_factor + random_scalar * jitter_factor;
35
- (base * jitter_blend) as u64
36
- }
37
-
38
- /// Trait for backoff strategies that calculate delays between retry attempts
39
- pub trait BackoffStrategy {
40
- /// Calculate the delay in milliseconds for the given attempt number
41
- ///
42
- /// # Arguments
43
- ///
44
- /// * `attempt` - Current attempt number (1-indexed)
45
- /// * `rng` - Random number generator for jitter
46
- ///
47
- /// # Returns
48
- ///
49
- /// Delay in milliseconds, or `None` if retries should stop
50
- fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64>;
51
-
52
- /// Check if another retry should be attempted
53
- ///
54
- /// # Arguments
55
- ///
56
- /// * `attempt` - Current attempt number (1-indexed)
57
- ///
58
- /// # Returns
59
- ///
60
- /// `true` if another retry is allowed, `false` otherwise
61
- fn should_retry(&self, attempt: u8) -> bool;
62
-
63
- /// Maximum number of retry attempts permitted by this strategy.
64
- fn max_attempts(&self) -> u8;
65
- }
66
-
67
- /// Exponential backoff strategy with configurable jitter
68
- ///
69
- /// Delays grow exponentially: base_delay * multiplier^(attempt-1)
70
- ///
71
- /// # Example
72
- ///
73
- /// ```rust
74
- /// use chrono_machines::ExponentialBackoff;
75
- ///
76
- /// let backoff = ExponentialBackoff::new()
77
- /// .base_delay_ms(100)
78
- /// .multiplier(2.0)
79
- /// .max_delay_ms(10_000)
80
- /// .max_attempts(5)
81
- /// .jitter_factor(1.0); // Full jitter
82
- /// ```
83
- #[derive(Debug, Clone, Copy)]
84
- pub struct ExponentialBackoff {
85
- /// Maximum number of retry attempts
86
- pub max_attempts: u8,
87
- /// Base delay in milliseconds
88
- pub base_delay_ms: u64,
89
- /// Exponential backoff multiplier
90
- pub multiplier: f64,
91
- /// Maximum delay cap in milliseconds
92
- pub max_delay_ms: u64,
93
- /// Jitter factor (0.0 = no jitter, 1.0 = full jitter)
94
- pub jitter_factor: f64,
95
- }
96
-
97
- impl ExponentialBackoff {
98
- /// Create a new exponential backoff builder with default values
99
- pub fn new() -> Self {
100
- Self::default()
101
- }
102
-
103
- /// Set the base delay in milliseconds
104
- pub fn base_delay_ms(mut self, ms: u64) -> Self {
105
- self.base_delay_ms = ms;
106
- self
107
- }
108
-
109
- /// Set the exponential multiplier
110
- pub fn multiplier(mut self, multiplier: f64) -> Self {
111
- self.multiplier = multiplier;
112
- self
113
- }
114
-
115
- /// Set the maximum delay cap in milliseconds
116
- pub fn max_delay_ms(mut self, ms: u64) -> Self {
117
- self.max_delay_ms = ms;
118
- self
119
- }
120
-
121
- /// Set the maximum number of attempts
122
- pub fn max_attempts(mut self, attempts: u8) -> Self {
123
- self.max_attempts = attempts;
124
- self
125
- }
126
-
127
- /// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
128
- pub fn jitter_factor(mut self, factor: f64) -> Self {
129
- self.jitter_factor = factor.clamp(0.0, 1.0);
130
- self
131
- }
132
- }
133
-
134
- impl Default for ExponentialBackoff {
135
- fn default() -> Self {
136
- Self {
137
- max_attempts: 3,
138
- base_delay_ms: 100,
139
- multiplier: 2.0,
140
- max_delay_ms: 10_000,
141
- jitter_factor: 1.0, // Full jitter by default
142
- }
143
- }
144
- }
145
-
146
- impl BackoffStrategy for ExponentialBackoff {
147
- fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
148
- if attempt >= self.max_attempts {
149
- return None;
150
- }
151
-
152
- let exponent = attempt.saturating_sub(1) as i32;
153
- let base_exponential = (self.base_delay_ms as f64) * self.multiplier.powi(exponent);
154
- let capped = base_exponential.min(self.max_delay_ms as f64);
155
-
156
- Some(apply_jitter(capped, self.jitter_factor, rng))
157
- }
158
-
159
- fn should_retry(&self, attempt: u8) -> bool {
160
- attempt < self.max_attempts
161
- }
162
-
163
- fn max_attempts(&self) -> u8 {
164
- self.max_attempts
165
- }
166
- }
167
-
168
- /// Constant backoff strategy with fixed delay
169
- ///
170
- /// All retry delays are the same constant value.
171
- ///
172
- /// # Example
173
- ///
174
- /// ```rust
175
- /// use chrono_machines::ConstantBackoff;
176
- ///
177
- /// let backoff = ConstantBackoff::new()
178
- /// .delay_ms(500)
179
- /// .max_attempts(5)
180
- /// .jitter_factor(0.1); // 10% jitter
181
- /// ```
182
- #[derive(Debug, Clone, Copy)]
183
- pub struct ConstantBackoff {
184
- /// Fixed delay in milliseconds
185
- pub delay_ms: u64,
186
- /// Maximum number of retry attempts
187
- pub max_attempts: u8,
188
- /// Jitter factor (0.0 = no jitter, 1.0 = full jitter)
189
- pub jitter_factor: f64,
190
- }
191
-
192
- impl ConstantBackoff {
193
- /// Create a new constant backoff builder with default values
194
- pub fn new() -> Self {
195
- Self::default()
196
- }
197
-
198
- /// Set the constant delay in milliseconds
199
- pub fn delay_ms(mut self, ms: u64) -> Self {
200
- self.delay_ms = ms;
201
- self
202
- }
203
-
204
- /// Set the maximum number of attempts
205
- pub fn max_attempts(mut self, attempts: u8) -> Self {
206
- self.max_attempts = attempts;
207
- self
208
- }
209
-
210
- /// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
211
- pub fn jitter_factor(mut self, factor: f64) -> Self {
212
- self.jitter_factor = factor.clamp(0.0, 1.0);
213
- self
214
- }
215
- }
216
-
217
- impl Default for ConstantBackoff {
218
- fn default() -> Self {
219
- Self {
220
- delay_ms: 100,
221
- max_attempts: 3,
222
- jitter_factor: 0.0, // No jitter for constant by default
223
- }
224
- }
225
- }
226
-
227
- impl BackoffStrategy for ConstantBackoff {
228
- fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
229
- if attempt >= self.max_attempts {
230
- return None;
231
- }
232
-
233
- Some(apply_jitter(self.delay_ms as f64, self.jitter_factor, rng))
234
- }
235
-
236
- fn should_retry(&self, attempt: u8) -> bool {
237
- attempt < self.max_attempts
238
- }
239
-
240
- fn max_attempts(&self) -> u8 {
241
- self.max_attempts
242
- }
243
- }
244
-
245
- /// Fibonacci backoff strategy
246
- ///
247
- /// Delays follow the Fibonacci sequence: 1, 1, 2, 3, 5, 8, 13, ...
248
- /// Each delay is base_delay_ms * fibonacci(attempt).
249
- ///
250
- /// # Example
251
- ///
252
- /// ```rust
253
- /// use chrono_machines::FibonacciBackoff;
254
- ///
255
- /// let backoff = FibonacciBackoff::new()
256
- /// .base_delay_ms(100) // 100ms, 100ms, 200ms, 300ms, 500ms...
257
- /// .max_delay_ms(5_000)
258
- /// .max_attempts(8)
259
- /// .jitter_factor(0.5); // 50% jitter
260
- /// ```
261
- #[derive(Debug, Clone, Copy)]
262
- pub struct FibonacciBackoff {
263
- /// Base delay in milliseconds (multiplied by Fibonacci number)
264
- pub base_delay_ms: u64,
265
- /// Maximum delay cap in milliseconds
266
- pub max_delay_ms: u64,
267
- /// Maximum number of retry attempts
268
- pub max_attempts: u8,
269
- /// Jitter factor (0.0 = no jitter, 1.0 = full jitter)
270
- pub jitter_factor: f64,
271
- }
272
-
273
- impl FibonacciBackoff {
274
- /// Create a new Fibonacci backoff builder with default values
275
- pub fn new() -> Self {
276
- Self::default()
277
- }
278
-
279
- /// Set the base delay in milliseconds
280
- pub fn base_delay_ms(mut self, ms: u64) -> Self {
281
- self.base_delay_ms = ms;
282
- self
283
- }
284
-
285
- /// Set the maximum delay cap in milliseconds
286
- pub fn max_delay_ms(mut self, ms: u64) -> Self {
287
- self.max_delay_ms = ms;
288
- self
289
- }
290
-
291
- /// Set the maximum number of attempts
292
- pub fn max_attempts(mut self, attempts: u8) -> Self {
293
- self.max_attempts = attempts;
294
- self
295
- }
296
-
297
- /// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
298
- pub fn jitter_factor(mut self, factor: f64) -> Self {
299
- self.jitter_factor = factor.clamp(0.0, 1.0);
300
- self
301
- }
302
- }
303
-
304
- impl Default for FibonacciBackoff {
305
- fn default() -> Self {
306
- Self {
307
- base_delay_ms: 100,
308
- max_delay_ms: 10_000,
309
- max_attempts: 8,
310
- jitter_factor: 1.0, // Full jitter by default
311
- }
312
- }
313
- }
314
-
315
- impl BackoffStrategy for FibonacciBackoff {
316
- fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
317
- if attempt >= self.max_attempts {
318
- return None;
319
- }
320
-
321
- let fib = fibonacci(attempt);
322
- let base = ((self.base_delay_ms as f64) * (fib as f64)).min(self.max_delay_ms as f64);
323
-
324
- Some(apply_jitter(base, self.jitter_factor, rng))
325
- }
326
-
327
- fn should_retry(&self, attempt: u8) -> bool {
328
- attempt < self.max_attempts
329
- }
330
-
331
- fn max_attempts(&self) -> u8 {
332
- self.max_attempts
333
- }
334
- }
335
-
336
- /// Backoff policy that can represent any supported strategy.
337
- ///
338
- /// The enum form makes it possible to store heterogeneous strategies in a
339
- /// registry or configuration without heap allocation or dynamic dispatch.
340
- #[derive(Debug, Clone, Copy)]
341
- pub enum BackoffPolicy {
342
- /// Exponential backoff policy
343
- Exponential(ExponentialBackoff),
344
- /// Constant backoff policy
345
- Constant(ConstantBackoff),
346
- /// Fibonacci backoff policy
347
- Fibonacci(FibonacciBackoff),
348
- }
349
-
350
- impl BackoffPolicy {
351
- /// Return the maximum retry attempts for the wrapped strategy.
352
- pub fn max_attempts(&self) -> u8 {
353
- match self {
354
- BackoffPolicy::Exponential(policy) => policy.max_attempts,
355
- BackoffPolicy::Constant(policy) => policy.max_attempts,
356
- BackoffPolicy::Fibonacci(policy) => policy.max_attempts,
357
- }
358
- }
359
- }
360
-
361
- impl BackoffStrategy for BackoffPolicy {
362
- fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
363
- match self {
364
- BackoffPolicy::Exponential(policy) => policy.delay(attempt, rng),
365
- BackoffPolicy::Constant(policy) => policy.delay(attempt, rng),
366
- BackoffPolicy::Fibonacci(policy) => policy.delay(attempt, rng),
367
- }
368
- }
369
-
370
- fn should_retry(&self, attempt: u8) -> bool {
371
- match self {
372
- BackoffPolicy::Exponential(policy) => policy.should_retry(attempt),
373
- BackoffPolicy::Constant(policy) => policy.should_retry(attempt),
374
- BackoffPolicy::Fibonacci(policy) => policy.should_retry(attempt),
375
- }
376
- }
377
-
378
- fn max_attempts(&self) -> u8 {
379
- match self {
380
- BackoffPolicy::Exponential(policy) => policy.max_attempts(),
381
- BackoffPolicy::Constant(policy) => policy.max_attempts(),
382
- BackoffPolicy::Fibonacci(policy) => policy.max_attempts(),
383
- }
384
- }
385
- }
386
-
387
- impl From<ExponentialBackoff> for BackoffPolicy {
388
- fn from(value: ExponentialBackoff) -> Self {
389
- BackoffPolicy::Exponential(value)
390
- }
391
- }
392
-
393
- impl From<ConstantBackoff> for BackoffPolicy {
394
- fn from(value: ConstantBackoff) -> Self {
395
- BackoffPolicy::Constant(value)
396
- }
397
- }
398
-
399
- impl From<FibonacciBackoff> for BackoffPolicy {
400
- fn from(value: FibonacciBackoff) -> Self {
401
- BackoffPolicy::Fibonacci(value)
402
- }
403
- }
404
-
405
- #[cfg(test)]
406
- mod tests {
407
- use super::*;
408
- use rand::rngs::StdRng;
409
- use rand::SeedableRng;
410
-
411
- #[test]
412
- fn test_exponential_backoff_builder() {
413
- let backoff = ExponentialBackoff::new()
414
- .base_delay_ms(200)
415
- .multiplier(3.0)
416
- .max_delay_ms(5000)
417
- .max_attempts(5)
418
- .jitter_factor(0.5);
419
-
420
- assert_eq!(backoff.base_delay_ms, 200);
421
- assert_eq!(backoff.multiplier, 3.0);
422
- assert_eq!(backoff.max_delay_ms, 5000);
423
- assert_eq!(backoff.max_attempts, 5);
424
- assert_eq!(backoff.jitter_factor, 0.5);
425
- }
426
-
427
- #[test]
428
- fn test_exponential_delays() {
429
- let backoff = ExponentialBackoff::new()
430
- .base_delay_ms(100)
431
- .multiplier(2.0)
432
- .jitter_factor(0.0); // No jitter for predictable testing
433
-
434
- let mut rng = StdRng::seed_from_u64(42);
435
-
436
- assert_eq!(backoff.delay(1, &mut rng), Some(100));
437
- assert_eq!(backoff.delay(2, &mut rng), Some(200));
438
- assert_eq!(backoff.delay(3, &mut rng), None); // Exceeds max_attempts (default 3)
439
- }
440
-
441
- #[test]
442
- fn test_constant_backoff() {
443
- let backoff = ConstantBackoff::new()
444
- .delay_ms(500)
445
- .max_attempts(4)
446
- .jitter_factor(0.0);
447
-
448
- let mut rng = StdRng::seed_from_u64(42);
449
-
450
- assert_eq!(backoff.delay(1, &mut rng), Some(500));
451
- assert_eq!(backoff.delay(2, &mut rng), Some(500));
452
- assert_eq!(backoff.delay(3, &mut rng), Some(500));
453
- assert_eq!(backoff.delay(4, &mut rng), None);
454
- }
455
-
456
- #[test]
457
- fn test_fibonacci_sequence() {
458
- assert_eq!(fibonacci(1), 1);
459
- assert_eq!(fibonacci(2), 1);
460
- assert_eq!(fibonacci(3), 2);
461
- assert_eq!(fibonacci(4), 3);
462
- assert_eq!(fibonacci(5), 5);
463
- assert_eq!(fibonacci(6), 8);
464
- assert_eq!(fibonacci(7), 13);
465
- }
466
-
467
- #[test]
468
- fn test_fibonacci_backoff() {
469
- let backoff = FibonacciBackoff::new()
470
- .base_delay_ms(100)
471
- .max_attempts(5)
472
- .jitter_factor(0.0);
473
-
474
- let mut rng = StdRng::seed_from_u64(42);
475
-
476
- assert_eq!(backoff.delay(1, &mut rng), Some(100)); // 100 * 1
477
- assert_eq!(backoff.delay(2, &mut rng), Some(100)); // 100 * 1
478
- assert_eq!(backoff.delay(3, &mut rng), Some(200)); // 100 * 2
479
- assert_eq!(backoff.delay(4, &mut rng), Some(300)); // 100 * 3
480
- assert_eq!(backoff.delay(5, &mut rng), None); // Exceeds max_attempts
481
- }
482
-
483
- #[test]
484
- fn test_jitter_application() {
485
- let backoff = ConstantBackoff::new().delay_ms(1000).jitter_factor(1.0); // Full jitter
486
-
487
- let mut rng = StdRng::seed_from_u64(42);
488
- let delays: Vec<u64> = (1..10).filter_map(|i| backoff.delay(i, &mut rng)).collect();
489
-
490
- // With full jitter, delays should vary
491
- let all_different = delays.windows(2).any(|w| w[0] != w[1]);
492
- assert!(all_different, "Full jitter should produce varying delays");
493
-
494
- // All delays should be <= base delay
495
- assert!(delays.iter().all(|&d| d <= 1000));
496
- }
497
- }
@@ -1,108 +0,0 @@
1
- //! Ergonomic helpers that mirror the high-level DSL available in the Ruby gem.
2
- //!
3
- //! These helpers build on top of the global policy registry (std-only) to allow
4
- //! concise execution using named policies.
5
-
6
- use crate::backoff::BackoffPolicy;
7
- use crate::policy::get_global_policy;
8
- use crate::retry::{RetryBuilder, RetryError, RetryOutcome, Retryable};
9
- use std::fmt;
10
-
11
- /// Type alias for retry builder with policy from global registry
12
- type PolicyRetryBuilder<F, T, E> = RetryBuilder<F, BackoffPolicy, T, E, fn(&E) -> bool>;
13
-
14
- /// Errors produced by the DSL helpers.
15
- #[derive(Debug)]
16
- pub enum DslError<E> {
17
- /// Referenced policy name is missing from the global registry.
18
- PolicyMissing(String),
19
- /// Underlying retry execution failed.
20
- Execution(RetryError<E>),
21
- }
22
-
23
- impl<E> From<RetryError<E>> for DslError<E> {
24
- fn from(value: RetryError<E>) -> Self {
25
- DslError::Execution(value)
26
- }
27
- }
28
-
29
- impl<E> fmt::Display for DslError<E>
30
- where
31
- E: fmt::Display,
32
- {
33
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34
- match self {
35
- DslError::PolicyMissing(name) => write!(f, "retry policy '{}' is not registered", name),
36
- DslError::Execution(err) => write!(f, "{err}"),
37
- }
38
- }
39
- }
40
-
41
- impl<E> std::error::Error for DslError<E> where E: fmt::Display + std::error::Error {}
42
-
43
- /// Construct a [`RetryBuilder`] using a named policy from the global registry.
44
- pub fn builder_for_policy<F, T, E>(
45
- policy_name: &str,
46
- operation: F,
47
- ) -> Result<PolicyRetryBuilder<F, T, E>, DslError<E>>
48
- where
49
- F: FnMut() -> Result<T, E>,
50
- {
51
- let policy = get_global_policy(policy_name)
52
- .ok_or_else(|| DslError::PolicyMissing(policy_name.to_string()))?;
53
-
54
- Ok(operation.retry(policy))
55
- }
56
-
57
- /// Execute an operation using a named policy from the global registry.
58
- pub fn retry_with_policy<F, T, E>(
59
- policy_name: &str,
60
- operation: F,
61
- ) -> Result<RetryOutcome<T>, DslError<E>>
62
- where
63
- F: FnMut() -> Result<T, E>,
64
- {
65
- builder_for_policy(policy_name, operation)?
66
- .call()
67
- .map_err(DslError::Execution)
68
- }
69
-
70
- #[cfg(test)]
71
- mod tests {
72
- use super::*;
73
- use crate::backoff::{BackoffPolicy, ExponentialBackoff};
74
- use crate::policy::{clear_global_policies, register_global_policy};
75
-
76
- #[test]
77
- fn test_retry_with_policy_success() {
78
- clear_global_policies();
79
- register_global_policy(
80
- "default",
81
- BackoffPolicy::from(ExponentialBackoff::new().max_attempts(2)),
82
- );
83
-
84
- let mut attempts = 0;
85
- let outcome = retry_with_policy("default", || {
86
- attempts += 1;
87
- if attempts == 1 {
88
- Err::<_, &'static str>("fail")
89
- } else {
90
- Ok("ok")
91
- }
92
- })
93
- .expect("dsl retry should succeed");
94
-
95
- assert_eq!(attempts, 2);
96
- assert_eq!(outcome.into_inner(), "ok");
97
- }
98
-
99
- #[test]
100
- fn test_retry_with_policy_missing() {
101
- clear_global_policies();
102
- let result = retry_with_policy::<_, (), &str>("missing", || Ok(()));
103
- match result {
104
- Err(DslError::PolicyMissing(name)) => assert_eq!(name, "missing"),
105
- _ => panic!("expected policy missing error"),
106
- }
107
- }
108
- }