mneme/
config.rs

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        // Update retry delay config with new base delay but keep max delay
52        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}