go_server_rust_sdk/scheduler/
retry_client.rs

1//! Retry client implementation for the scheduler
2
3use serde_json::Value;
4use std::time::Duration;
5use tokio::time::sleep;
6use crate::error::{Result, SdkError};
7use super::client::{Client, ResultResponse};
8
9/// Retry client that wraps the base client with retry functionality
10#[derive(Clone)]
11pub struct RetryClient {
12    client: Client,
13    max_retries: usize,
14    retry_delay: Duration,
15}
16
17impl RetryClient {
18    /// Creates a new retry client
19    /// 
20    /// # Arguments
21    /// 
22    /// * `base_url` - The base URL of the scheduler server
23    /// * `max_retries` - Maximum number of retry attempts
24    /// * `retry_delay` - Delay between retry attempts
25    /// 
26    /// # Example
27    /// 
28    /// ```rust
29    /// use go_server_rust_sdk::scheduler::RetryClient;
30    /// use std::time::Duration;
31    /// 
32    /// let client = RetryClient::new(
33    ///     "http://localhost:8080",
34    ///     3,
35    ///     Duration::from_secs(1)
36    /// );
37    /// ```
38    pub fn new(
39        base_url: impl Into<String>,
40        max_retries: usize,
41        retry_delay: Duration,
42    ) -> Self {
43        Self {
44            client: Client::new(base_url),
45            max_retries,
46            retry_delay,
47        }
48    }
49
50    /// Executes a task with retry logic
51    /// 
52    /// This method will retry the execution up to `max_retries` times
53    /// if the request fails.
54    /// 
55    /// # Arguments
56    /// 
57    /// * `method` - The method name to execute
58    /// * `params` - The parameters for the method
59    /// 
60    /// # Returns
61    /// 
62    /// A `ResultResponse` containing the task ID and initial status
63    /// 
64    /// # Example
65    /// 
66    /// ```rust,no_run
67    /// use go_server_rust_sdk::scheduler::RetryClient;
68    /// use serde_json::json;
69    /// use std::time::Duration;
70    /// 
71    /// # #[tokio::main]
72    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
73    /// let client = RetryClient::new(
74    ///     "http://localhost:8080",
75    ///     3,
76    ///     Duration::from_secs(1)
77    /// );
78    /// let params = json!({"a": 10, "b": 20});
79    /// let response = client.execute_with_retry("add", params).await?;
80    /// println!("Task ID: {}", response.task_id);
81    /// # Ok(())
82    /// # }
83    /// ```
84    pub async fn execute_with_retry(
85        &self,
86        method: impl Into<String> + Clone,
87        params: Value,
88    ) -> Result<ResultResponse> {
89        let mut last_error = None;
90        
91        for attempt in 0..=self.max_retries {
92            match self.client.execute(method.clone(), params.clone()).await {
93                Ok(response) => return Ok(response),
94                Err(e) => {
95                    last_error = Some(e);
96                    if attempt < self.max_retries {
97                        sleep(self.retry_delay).await;
98                    }
99                }
100            }
101        }
102        
103        Err(SdkError::Generic(format!(
104            "Failed after {} retries: {}",
105            self.max_retries,
106            last_error.unwrap()
107        )))
108    }
109
110    /// Executes an encrypted task with retry logic
111    /// 
112    /// This method will retry the execution up to `max_retries` times
113    /// if the request fails.
114    /// 
115    /// # Arguments
116    /// 
117    /// * `method` - The method name to execute
118    /// * `key` - The encryption key
119    /// * `salt` - The salt value for key encryption
120    /// * `params` - The parameters for the method
121    /// 
122    /// # Returns
123    /// 
124    /// A `ResultResponse` containing the task ID and initial status
125    /// 
126    /// # Example
127    /// 
128    /// ```rust,no_run
129    /// use go_server_rust_sdk::scheduler::RetryClient;
130    /// use serde_json::json;
131    /// use std::time::Duration;
132    /// 
133    /// # #[tokio::main]
134    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
135    /// let client = RetryClient::new(
136    ///     "http://localhost:8080",
137    ///     3,
138    ///     Duration::from_secs(1)
139    /// );
140    /// let params = json!({"a": 10, "b": 20});
141    /// let response = client.execute_encrypted_with_retry(
142    ///     "add", 
143    ///     "my-secret-key", 
144    ///     123456, 
145    ///     params
146    /// ).await?;
147    /// println!("Task ID: {}", response.task_id);
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub async fn execute_encrypted_with_retry(
152        &self,
153        method: impl Into<String> + Clone,
154        key: &str,
155        salt: i32,
156        params: Value,
157    ) -> Result<ResultResponse> {
158        let mut last_error = None;
159        
160        for attempt in 0..=self.max_retries {
161            match self.client.execute_encrypted(method.clone(), key, salt, params.clone()).await {
162                Ok(response) => return Ok(response),
163                Err(e) => {
164                    last_error = Some(e);
165                    if attempt < self.max_retries {
166                        sleep(self.retry_delay).await;
167                    }
168                }
169            }
170        }
171        
172        Err(SdkError::Generic(format!(
173            "Failed after {} retries: {}",
174            self.max_retries,
175            last_error.unwrap()
176        )))
177    }
178
179    /// Executes a task synchronously with retry logic and timeout
180    /// 
181    /// This is a convenience method that combines retry logic with synchronous execution.
182    /// 
183    /// # Arguments
184    /// 
185    /// * `method` - The method name to execute
186    /// * `params` - The parameters for the method
187    /// * `timeout_duration` - Maximum time to wait for completion
188    /// 
189    /// # Returns
190    /// 
191    /// A `ResultResponse` containing the final task result
192    /// 
193    /// # Example
194    /// 
195    /// ```rust,no_run
196    /// use go_server_rust_sdk::scheduler::RetryClient;
197    /// use serde_json::json;
198    /// use std::time::Duration;
199    /// 
200    /// # #[tokio::main]
201    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
202    /// let client = RetryClient::new(
203    ///     "http://localhost:8080",
204    ///     3,
205    ///     Duration::from_secs(1)
206    /// );
207    /// let params = json!({"a": 10, "b": 20});
208    /// let result = client.execute_sync_with_retry(
209    ///     "add", 
210    ///     params, 
211    ///     Duration::from_secs(30)
212    /// ).await?;
213    /// println!("Result: {:?}", result.result);
214    /// # Ok(())
215    /// # }
216    /// ```
217    pub async fn execute_sync_with_retry(
218        &self,
219        method: impl Into<String> + Clone,
220        params: Value,
221        _timeout_duration: Duration,
222    ) -> Result<ResultResponse> {
223        // Submit task with retry
224        let exec_response = self.execute_with_retry(method, params).await?;
225
226        // Get result (this already has built-in polling)
227        let result = self.client.get_result(&exec_response.task_id).await?;
228
229        Ok(result)
230    }
231
232    /// Executes an encrypted task synchronously with retry logic, decryption and timeout
233    /// 
234    /// This is a convenience method that combines retry logic with synchronous encrypted execution.
235    /// 
236    /// # Arguments
237    /// 
238    /// * `method` - The method name to execute
239    /// * `key` - The encryption key
240    /// * `salt` - The salt value for key encryption
241    /// * `params` - The parameters for the method
242    /// * `timeout_duration` - Maximum time to wait for completion
243    /// 
244    /// # Returns
245    /// 
246    /// A `ResultResponse` containing the decrypted task result
247    /// 
248    /// # Example
249    /// 
250    /// ```rust,no_run
251    /// use go_server_rust_sdk::scheduler::RetryClient;
252    /// use serde_json::json;
253    /// use std::time::Duration;
254    /// 
255    /// # #[tokio::main]
256    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
257    /// let client = RetryClient::new(
258    ///     "http://localhost:8080",
259    ///     3,
260    ///     Duration::from_secs(1)
261    /// );
262    /// let params = json!({"a": 10, "b": 20});
263    /// let result = client.execute_sync_encrypted_with_retry(
264    ///     "add", 
265    ///     "my-secret-key", 
266    ///     123456, 
267    ///     params, 
268    ///     Duration::from_secs(30)
269    /// ).await?;
270    /// println!("Decrypted result: {:?}", result.result);
271    /// # Ok(())
272    /// # }
273    /// ```
274    pub async fn execute_sync_encrypted_with_retry(
275        &self,
276        method: impl Into<String> + Clone,
277        key: &str,
278        salt: i32,
279        params: Value,
280        _timeout_duration: Duration,
281    ) -> Result<ResultResponse> {
282        // Submit encrypted task with retry
283        let exec_response = self.execute_encrypted_with_retry(method, key, salt, params).await?;
284
285        // Get and decrypt result (this already has built-in polling)
286        let result = self.client.get_result_encrypted(&exec_response.task_id, key, salt).await?;
287
288        Ok(result)
289    }
290
291    /// Get access to the underlying client
292    /// 
293    /// This allows access to methods that don't need retry logic,
294    /// such as `get_result` and `get_result_encrypted`.
295    /// 
296    /// # Returns
297    /// 
298    /// A reference to the underlying `Client`
299    pub fn client(&self) -> &Client {
300        &self.client
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use std::time::Duration;
308
309    #[test]
310    fn test_retry_client_creation() {
311        let client = RetryClient::new(
312            "http://localhost:8080",
313            3,
314            Duration::from_millis(500),
315        );
316        
317        assert_eq!(client.max_retries, 3);
318        assert_eq!(client.retry_delay, Duration::from_millis(500));
319    }
320
321    #[test]
322    fn test_client_access() {
323        let retry_client = RetryClient::new(
324            "http://localhost:8080",
325            3,
326            Duration::from_millis(500),
327        );
328        
329        let _client = retry_client.client();
330        // Should be able to access the underlying client
331    }
332}