1use crate::delay::RetryDelay;
2use crate::error::Error;
3
4const MAX_RETRIES_LIMIT: u32 = 10;
5const MIN_DELAY_MS: u64 = 50;
6const MAX_DELAY_MS: u64 = 5000;
7
8#[derive(Debug, Clone)]
9pub struct ExecuteConfig {
10 max_retries: u32,
11 retry_delay: RetryDelay,
12}
13
14impl ExecuteConfig {
15 pub fn with_max_retries(mut self, max_retries: u32) -> Result<Self, Error> {
16 if max_retries == 0 {
17 return Err(Error::InvalidConfig {
18 message: "max_retries cannot be 0".to_string(),
19 parameter: Some("max_retries".to_string()),
20 });
21 }
22 if max_retries > MAX_RETRIES_LIMIT {
23 return Err(Error::InvalidConfig {
24 message: format!("max_retries cannot exceed {MAX_RETRIES_LIMIT}"),
25 parameter: Some("max_retries".to_string()),
26 });
27 }
28 self.max_retries = max_retries;
29 Ok(self)
30 }
31
32 pub fn with_base_delay(mut self, delay_ms: u64) -> Result<Self, Error> {
33 if delay_ms == 0 {
34 return Err(Error::InvalidConfig {
35 message: "base_retry_delay_ms cannot be 0".to_string(),
36 parameter: Some("base_retry_delay_ms".to_string()),
37 });
38 }
39 if delay_ms < MIN_DELAY_MS {
40 return Err(Error::InvalidConfig {
41 message: format!("base_retry_delay_ms must be at least {MIN_DELAY_MS}ms"),
42 parameter: Some("base_retry_delay_ms".to_string()),
43 });
44 }
45 if delay_ms > MAX_DELAY_MS {
46 return Err(Error::InvalidConfig {
47 message: format!("base_retry_delay_ms cannot exceed {MAX_DELAY_MS}ms"),
48 parameter: Some("base_retry_delay_ms".to_string()),
49 });
50 }
51 self.retry_delay = RetryDelay::new(delay_ms, self.retry_delay.max_delay_ms());
53 Ok(self)
54 }
55
56 pub fn with_max_delay(mut self, max_delay_ms: u64) -> Result<Self, Error> {
57 if max_delay_ms < self.retry_delay.base_delay_ms() {
58 return Err(Error::InvalidConfig {
59 message: format!(
60 "max_delay_ms ({max_delay_ms}) cannot be less than base_delay_ms ({})",
61 self.retry_delay.base_delay_ms()
62 ),
63 parameter: Some("max_delay_ms".to_string()),
64 });
65 }
66 self.retry_delay = RetryDelay::new(self.retry_delay.base_delay_ms(), max_delay_ms);
67 Ok(self)
68 }
69
70 pub fn max_retries(&self) -> u32 {
71 self.max_retries
72 }
73
74 pub fn retry_delay(&self) -> &RetryDelay {
75 &self.retry_delay
76 }
77}
78
79impl Default for ExecuteConfig {
80 fn default() -> Self {
81 Self {
82 max_retries: 3,
83 retry_delay: RetryDelay::default(),
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn validates_max_retries() {
94 match ExecuteConfig::default().with_max_retries(0) {
95 Err(Error::InvalidConfig {
96 message, parameter, ..
97 }) => {
98 assert_eq!(message, "max_retries cannot be 0");
99 assert_eq!(parameter, Some("max_retries".to_string()));
100 }
101 other => panic!("Expected InvalidConfig error, got {:?}", other),
102 }
103
104 match ExecuteConfig::default().with_max_retries(MAX_RETRIES_LIMIT + 1) {
105 Err(Error::InvalidConfig {
106 message, parameter, ..
107 }) => {
108 assert_eq!(
109 message,
110 format!("max_retries cannot exceed {MAX_RETRIES_LIMIT}")
111 );
112 assert_eq!(parameter, Some("max_retries".to_string()));
113 }
114 other => panic!("Expected InvalidConfig error, got {:?}", other),
115 }
116
117 let config = ExecuteConfig::default()
118 .with_max_retries(5)
119 .expect("Failed to set valid max_retries");
120 assert_eq!(config.max_retries(), 5);
121 }
122
123 #[test]
124 fn validates_base_delay() {
125 match ExecuteConfig::default().with_base_delay(0) {
126 Err(Error::InvalidConfig {
127 message, parameter, ..
128 }) => {
129 assert_eq!(message, "base_retry_delay_ms cannot be 0");
130 assert_eq!(parameter, Some("base_retry_delay_ms".to_string()));
131 }
132 other => panic!("Expected InvalidConfig error, got {:?}", other),
133 }
134
135 match ExecuteConfig::default().with_base_delay(MIN_DELAY_MS - 1) {
136 Err(Error::InvalidConfig {
137 message, parameter, ..
138 }) => {
139 assert_eq!(
140 message,
141 format!("base_retry_delay_ms must be at least {MIN_DELAY_MS}ms")
142 );
143 assert_eq!(parameter, Some("base_retry_delay_ms".to_string()));
144 }
145 other => panic!("Expected InvalidConfig error, got {:?}", other),
146 }
147
148 match ExecuteConfig::default().with_base_delay(MAX_DELAY_MS + 1) {
149 Err(Error::InvalidConfig {
150 message, parameter, ..
151 }) => {
152 assert_eq!(
153 message,
154 format!("base_retry_delay_ms cannot exceed {MAX_DELAY_MS}ms")
155 );
156 assert_eq!(parameter, Some("base_retry_delay_ms".to_string()));
157 }
158 other => panic!("Expected InvalidConfig error, got {:?}", other),
159 }
160
161 let config = ExecuteConfig::default()
162 .with_base_delay(200)
163 .expect("Failed to set valid base_delay");
164 assert_eq!(config.retry_delay().base_delay_ms(), 200);
165 }
166
167 #[test]
168 fn validates_max_delay() {
169 let config = ExecuteConfig::default().with_base_delay(100).unwrap();
170
171 match config.clone().with_max_delay(50) {
172 Err(Error::InvalidConfig {
173 message, parameter, ..
174 }) => {
175 assert_eq!(
176 message,
177 "max_delay_ms (50) cannot be less than base_delay_ms (100)"
178 );
179 assert_eq!(parameter, Some("max_delay_ms".to_string()));
180 }
181 other => panic!("Expected InvalidConfig error, got {:?}", other),
182 }
183
184 let config = config
185 .with_max_delay(1000)
186 .expect("Failed to set valid max_delay");
187 assert_eq!(config.retry_delay().max_delay_ms(), 1000);
188 }
189
190 #[test]
191 fn default_values_are_valid() {
192 let config = ExecuteConfig::default();
193 assert!(
194 config
195 .clone()
196 .with_max_retries(config.max_retries())
197 .is_ok()
198 );
199 assert!(
200 config
201 .clone()
202 .with_base_delay(config.retry_delay().base_delay_ms())
203 .is_ok()
204 );
205 }
206}