voirs_cli/workflow/
retry.rs1use super::definition::{BackoffType, RetryStrategy};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RetryConfig {
11 pub max_attempts: usize,
13 pub initial_delay_ms: u64,
15 pub max_delay_ms: u64,
17 pub multiplier: f64,
19 pub strategy: BackoffStrategy,
21}
22
23impl Default for RetryConfig {
24 fn default() -> Self {
25 Self {
26 max_attempts: 3,
27 initial_delay_ms: 1000,
28 max_delay_ms: 60_000,
29 multiplier: 2.0,
30 strategy: BackoffStrategy::Exponential,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37pub enum BackoffStrategy {
38 Fixed,
40 Linear,
42 Exponential,
44 ExponentialJitter,
46}
47
48pub struct RetryManager;
50
51impl RetryManager {
52 pub fn new() -> Self {
54 Self
55 }
56
57 pub fn calculate_delay(&self, strategy: &RetryStrategy, attempt: usize) -> u64 {
59 let backoff_strategy = match strategy.backoff {
60 BackoffType::Fixed => BackoffStrategy::Fixed,
61 BackoffType::Linear => BackoffStrategy::Linear,
62 BackoffType::Exponential => BackoffStrategy::Exponential,
63 BackoffType::ExponentialJitter => BackoffStrategy::ExponentialJitter,
64 };
65
66 let delay = match backoff_strategy {
67 BackoffStrategy::Fixed => strategy.initial_delay_ms,
68 BackoffStrategy::Linear => strategy.initial_delay_ms * attempt as u64,
69 BackoffStrategy::Exponential => {
70 let delay = strategy.initial_delay_ms
71 * (strategy.backoff_multiplier.powi(attempt as i32 - 1) as u64);
72 delay.min(strategy.max_delay_ms)
73 }
74 BackoffStrategy::ExponentialJitter => {
75 let base_delay = strategy.initial_delay_ms
76 * (strategy.backoff_multiplier.powi(attempt as i32 - 1) as u64);
77 let jitter = (base_delay as f64 * 0.1) as u64;
79 (base_delay + jitter).min(strategy.max_delay_ms)
80 }
81 };
82
83 delay
84 .max(strategy.initial_delay_ms)
85 .min(strategy.max_delay_ms)
86 }
87
88 pub fn should_retry(&self, attempt: usize, max_attempts: usize) -> bool {
90 attempt < max_attempts
91 }
92}
93
94impl Default for RetryManager {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_retry_config_default() {
106 let config = RetryConfig::default();
107 assert_eq!(config.max_attempts, 3);
108 assert_eq!(config.initial_delay_ms, 1000);
109 assert_eq!(config.max_delay_ms, 60_000);
110 }
111
112 #[test]
113 fn test_retry_manager_creation() {
114 let manager = RetryManager::new();
115 assert!(manager.should_retry(1, 3));
117 }
118
119 #[test]
120 fn test_should_retry() {
121 let manager = RetryManager::new();
122 assert!(manager.should_retry(1, 3));
123 assert!(manager.should_retry(2, 3));
124 assert!(!manager.should_retry(3, 3));
125 assert!(!manager.should_retry(4, 3));
126 }
127
128 #[test]
129 fn test_fixed_backoff() {
130 let manager = RetryManager::new();
131 let strategy = RetryStrategy {
132 max_attempts: 3,
133 backoff: BackoffType::Fixed,
134 initial_delay_ms: 1000,
135 max_delay_ms: 60_000,
136 backoff_multiplier: 2.0,
137 };
138
139 let delay1 = manager.calculate_delay(&strategy, 1);
140 let delay2 = manager.calculate_delay(&strategy, 2);
141 let delay3 = manager.calculate_delay(&strategy, 3);
142
143 assert_eq!(delay1, 1000);
144 assert_eq!(delay2, 1000);
145 assert_eq!(delay3, 1000);
146 }
147
148 #[test]
149 fn test_linear_backoff() {
150 let manager = RetryManager::new();
151 let strategy = RetryStrategy {
152 max_attempts: 3,
153 backoff: BackoffType::Linear,
154 initial_delay_ms: 1000,
155 max_delay_ms: 60_000,
156 backoff_multiplier: 2.0,
157 };
158
159 let delay1 = manager.calculate_delay(&strategy, 1);
160 let delay2 = manager.calculate_delay(&strategy, 2);
161 let delay3 = manager.calculate_delay(&strategy, 3);
162
163 assert_eq!(delay1, 1000);
164 assert_eq!(delay2, 2000);
165 assert_eq!(delay3, 3000);
166 }
167
168 #[test]
169 fn test_exponential_backoff() {
170 let manager = RetryManager::new();
171 let strategy = RetryStrategy {
172 max_attempts: 4,
173 backoff: BackoffType::Exponential,
174 initial_delay_ms: 1000,
175 max_delay_ms: 60_000,
176 backoff_multiplier: 2.0,
177 };
178
179 let delay1 = manager.calculate_delay(&strategy, 1);
180 let delay2 = manager.calculate_delay(&strategy, 2);
181 let delay3 = manager.calculate_delay(&strategy, 3);
182
183 assert_eq!(delay1, 1000);
184 assert_eq!(delay2, 2000);
185 assert_eq!(delay3, 4000);
186 }
187
188 #[test]
189 fn test_max_delay_cap() {
190 let manager = RetryManager::new();
191 let strategy = RetryStrategy {
192 max_attempts: 10,
193 backoff: BackoffType::Exponential,
194 initial_delay_ms: 1000,
195 max_delay_ms: 5000,
196 backoff_multiplier: 2.0,
197 };
198
199 let delay5 = manager.calculate_delay(&strategy, 5);
200 assert!(delay5 <= 5000);
201 }
202}