1use std::time::Duration;
4use std::future::Future;
5use serde::{Serialize, Deserialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum QueryError {
10 NetworkError(String),
12 SerializationError(String),
14 DeserializationError(String),
16 TimeoutError(String),
18 StorageError(String),
20 GenericError(String),
22}
23
24impl std::fmt::Display for QueryError {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 QueryError::NetworkError(msg) => write!(f, "Network error: {}", msg),
28 QueryError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
29 QueryError::DeserializationError(msg) => write!(f, "Deserialization error: {}", msg),
30 QueryError::TimeoutError(msg) => write!(f, "Timeout error: {}", msg),
31 QueryError::StorageError(msg) => write!(f, "Storage error: {}", msg),
32 QueryError::GenericError(msg) => write!(f, "Error: {}", msg),
33 }
34 }
35}
36
37impl std::error::Error for QueryError {}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct RetryConfig {
42 pub max_retries: usize,
44 pub base_delay: Duration,
46 pub max_delay: Duration,
48 pub exponential_backoff: bool,
50 pub retry_on_network_errors: bool,
52 pub retry_on_timeout_errors: bool,
53}
54
55impl Default for RetryConfig {
56 fn default() -> Self {
57 Self {
58 max_retries: 3,
59 base_delay: Duration::from_millis(1000),
60 max_delay: Duration::from_secs(30),
61 exponential_backoff: true,
62 retry_on_network_errors: true,
63 retry_on_timeout_errors: true,
64 }
65 }
66}
67
68impl RetryConfig {
69 pub fn new(max_retries: usize, base_delay: Duration) -> Self {
71 Self {
72 max_retries,
73 base_delay,
74 max_delay: Duration::from_secs(30),
75 exponential_backoff: true,
76 retry_on_network_errors: true,
77 retry_on_timeout_errors: true,
78 }
79 }
80
81 pub fn with_fixed_delay(mut self) -> Self {
83 self.exponential_backoff = false;
84 self
85 }
86
87 pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
89 self.max_delay = max_delay;
90 self
91 }
92
93 pub fn no_network_retry(mut self) -> Self {
95 self.retry_on_network_errors = false;
96 self
97 }
98
99 pub fn no_timeout_retry(mut self) -> Self {
101 self.retry_on_timeout_errors = false;
102 self
103 }
104}
105
106pub async fn execute_with_retry<F, Fut, T>(
108 query_fn: F,
109 config: &RetryConfig,
110) -> Result<T, QueryError>
111where
112 F: Fn() -> Fut + Clone,
113 Fut: Future<Output = Result<T, QueryError>>,
114{
115 let mut last_error = None;
116
117 for attempt in 0..=config.max_retries {
118 match query_fn().await {
119 Ok(result) => return Ok(result),
120 Err(error) => {
121 last_error = Some(error.clone());
122
123 if !should_retry_error(&error, config) {
125 return Err(error);
126 }
127
128 if attempt == config.max_retries {
130 break;
131 }
132
133 let delay = calculate_delay(attempt, config);
135
136 sleep(delay).await;
138 }
139 }
140 }
141
142 Err(last_error.unwrap_or_else(|| QueryError::GenericError("Unknown error".to_string())))
143}
144
145pub fn should_retry_error(error: &QueryError, config: &RetryConfig) -> bool {
147 match error {
148 QueryError::NetworkError(_) => config.retry_on_network_errors,
149 QueryError::TimeoutError(_) => config.retry_on_timeout_errors,
150 QueryError::SerializationError(_) | QueryError::DeserializationError(_) => false,
151 QueryError::GenericError(_) => true,
152 QueryError::StorageError(_) => false, }
154}
155
156fn calculate_delay(attempt: usize, config: &RetryConfig) -> Duration {
158 if config.exponential_backoff {
159 let delay_ms = config.base_delay.as_millis() as u64 * (2_u64.pow(attempt as u32));
160 let delay = Duration::from_millis(delay_ms);
161 delay.min(config.max_delay)
162 } else {
163 config.base_delay
164 }
165}
166
167async fn sleep(duration: Duration) {
169 #[cfg(target_arch = "wasm32")]
170 {
171 let promise = js_sys::Promise::new(&mut |resolve, _| {
172 web_sys::window()
173 .unwrap()
174 .set_timeout_with_callback_and_timeout_and_arguments_0(
175 &resolve,
176 duration.as_millis() as i32
177 )
178 .unwrap();
179 });
180
181 wasm_bindgen_futures::JsFuture::from(promise).await.unwrap();
182 }
183
184 #[cfg(not(target_arch = "wasm32"))]
185 {
186 tokio::time::sleep(duration).await;
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_retry_config_builder() {
196 let config = RetryConfig::new(5, Duration::from_secs(2))
197 .with_max_delay(Duration::from_secs(60))
198 .with_fixed_delay()
199 .no_network_retry();
200
201 assert_eq!(config.max_retries, 5);
202 assert_eq!(config.base_delay, Duration::from_secs(2));
203 assert_eq!(config.max_delay, Duration::from_secs(60));
204 assert!(!config.exponential_backoff);
205 assert!(!config.retry_on_network_errors);
206 }
207
208 #[test]
209 fn test_should_retry_error() {
210 let config = RetryConfig::default();
211
212 assert!(should_retry_error(&QueryError::NetworkError("test".to_string()), &config));
213 assert!(should_retry_error(&QueryError::TimeoutError("test".to_string()), &config));
214 assert!(!should_retry_error(&QueryError::SerializationError("test".to_string()), &config));
215 }
216
217 #[test]
218 fn test_calculate_delay() {
219 let config = RetryConfig::new(3, Duration::from_millis(100));
220
221 assert_eq!(calculate_delay(0, &config), Duration::from_millis(100));
223 assert_eq!(calculate_delay(1, &config), Duration::from_millis(200));
224 assert_eq!(calculate_delay(2, &config), Duration::from_millis(400));
225
226 let fixed_config = config.with_fixed_delay();
228 assert_eq!(calculate_delay(0, &fixed_config), Duration::from_millis(100));
229 assert_eq!(calculate_delay(1, &fixed_config), Duration::from_millis(100));
230 assert_eq!(calculate_delay(2, &fixed_config), Duration::from_millis(100));
231 }
232}