Skip to main content

rs_dapi_client/
executor.rs

1use crate::transport::TransportRequest;
2use crate::{Address, CanRetry, DapiClientError, RequestSettings};
3use dapi_grpc::mock::Mockable;
4use dapi_grpc::tonic::async_trait;
5use std::fmt::Debug;
6
7#[async_trait]
8/// DAPI client executor trait.
9pub trait DapiRequestExecutor {
10    /// Execute request using this DAPI client.
11    async fn execute<R>(
12        &self,
13        request: R,
14        settings: RequestSettings,
15    ) -> ExecutionResult<R::Response, DapiClientError>
16    where
17        R: TransportRequest + Mockable,
18        R::Response: Mockable;
19}
20
21/// Unwrap wrapped types
22pub trait IntoInner<T> {
23    /// Unwrap the inner type.
24    ///
25    /// This function returns inner type, dropping additional context information.
26    /// It is lossy operation, so it should be used with caution.
27    fn into_inner(self) -> T;
28}
29
30/// Convert inner type without losing additional context information of the wrapper.
31pub trait InnerInto<T> {
32    /// Convert inner type without losing additional context information of the wrapper.
33    fn inner_into(self) -> T;
34}
35
36/// Error happened during request execution.
37#[derive(Debug, Clone, thiserror::Error, Eq)]
38#[error("{inner}")]
39pub struct ExecutionError<E> {
40    /// The cause of error
41    pub inner: E,
42    /// How many times the request was retried
43    pub retries: usize,
44    /// The address of the node that was used for the request
45    pub address: Option<Address>,
46}
47
48impl<E: PartialEq> PartialEq for ExecutionError<E> {
49    fn eq(&self, other: &Self) -> bool {
50        self.inner == other.inner && self.retries == other.retries && self.address == other.address
51    }
52}
53
54impl<F, T> InnerInto<ExecutionError<T>> for ExecutionError<F>
55where
56    F: Into<T>,
57{
58    /// Convert inner error type without losing retries and address
59    fn inner_into(self) -> ExecutionError<T> {
60        ExecutionError {
61            inner: self.inner.into(),
62            retries: self.retries,
63            address: self.address,
64        }
65    }
66}
67
68impl<E, I> IntoInner<I> for ExecutionError<E>
69where
70    E: Into<I>,
71{
72    /// Unwrap the error cause
73    fn into_inner(self) -> I {
74        self.inner.into()
75    }
76}
77
78impl<E: CanRetry> CanRetry for ExecutionError<E> {
79    fn can_retry(&self) -> bool {
80        self.inner.can_retry()
81    }
82
83    fn is_no_available_addresses(&self) -> bool {
84        self.inner.is_no_available_addresses()
85    }
86
87    fn rate_limit_ban_duration(&self) -> Option<std::time::Duration> {
88        self.inner.rate_limit_ban_duration()
89    }
90}
91
92/// Request execution response.
93#[derive(Debug, Clone, Eq, PartialEq)]
94pub struct ExecutionResponse<R> {
95    /// The response from the request
96    pub inner: R,
97    /// How many times the request was retried
98    pub retries: usize,
99    /// The address of the node that was used for the request
100    pub address: Address,
101}
102
103#[cfg(feature = "mocks")]
104impl<R: Default> Default for ExecutionResponse<R> {
105    fn default() -> Self {
106        Self {
107            retries: Default::default(),
108            address: "http://127.0.0.1".parse().expect("create mock address"),
109            inner: Default::default(),
110        }
111    }
112}
113
114impl<R, I> IntoInner<I> for ExecutionResponse<R>
115where
116    R: Into<I>,
117{
118    /// Unwrap the response
119    fn into_inner(self) -> I {
120        self.inner.into()
121    }
122}
123
124impl<F, T> InnerInto<ExecutionResponse<T>> for ExecutionResponse<F>
125where
126    F: Into<T>,
127{
128    /// Convert inner response type without losing retries and address
129    fn inner_into(self) -> ExecutionResponse<T> {
130        ExecutionResponse {
131            inner: self.inner.into(),
132            retries: self.retries,
133            address: self.address,
134        }
135    }
136}
137
138/// Result of request execution
139pub type ExecutionResult<R, E> = Result<ExecutionResponse<R>, ExecutionError<E>>;
140
141impl<R, E> From<ExecutionResponse<R>> for ExecutionResult<R, E> {
142    fn from(response: ExecutionResponse<R>) -> Self {
143        ExecutionResult::<R, E>::Ok(response)
144    }
145}
146
147impl<R, E> From<ExecutionError<E>> for ExecutionResult<R, E> {
148    fn from(e: ExecutionError<E>) -> Self {
149        ExecutionResult::<R, E>::Err(e)
150    }
151}
152
153impl<R, E> IntoInner<Result<R, E>> for ExecutionResult<R, E> {
154    fn into_inner(self) -> Result<R, E> {
155        match self {
156            Ok(response) => Ok(response.into_inner()),
157            Err(error) => Err(error.into_inner()),
158        }
159    }
160}
161
162impl<F, FE, T, TE> InnerInto<ExecutionResult<T, TE>> for ExecutionResult<F, FE>
163where
164    F: Into<T>,
165    FE: Into<TE>,
166{
167    fn inner_into(self) -> ExecutionResult<T, TE> {
168        match self {
169            Ok(response) => Ok(response.inner_into()),
170            Err(error) => Err(error.inner_into()),
171        }
172    }
173}
174
175/// Convert Result<T,TE> to ExecutionResult<R,E>, taking context from ExecutionResponse.
176pub trait WrapToExecutionResult<R, RE, W>: Sized {
177    /// Convert self (eg. some [Result]) to [ExecutionResult], taking context information from `W` (eg. ExecutionResponse).
178    ///
179    /// This function simplifies processing of results by wrapping them into ExecutionResult.
180    /// It is useful when you have execution result retrieved in previous step and you want to
181    /// add it to the result of the current step.
182    ///
183    /// Useful when chaining multiple commands and you want to keep track of retries and address.
184    ///
185    /// ## Example
186    ///
187    /// ```rust
188    /// use rs_dapi_client::{ExecutionResponse, ExecutionResult, WrapToExecutionResult};
189    ///
190    /// fn some_request() -> ExecutionResult<i8, String> {
191    ///     Ok(ExecutionResponse {
192    ///         inner: 42,
193    ///         retries: 123,
194    ///         address: "http://127.0.0.1".parse().expect("create mock address"),
195    ///     })
196    /// }
197    ///
198    /// fn next_step() -> Result<i32, String> {
199    ///     Err("next error".to_string())
200    /// }
201    ///
202    /// let response = some_request().expect("request should succeed");
203    /// let result: ExecutionResult<i32, String> = next_step().wrap_to_execution_result(&response);
204    ///
205    /// if let ExecutionResult::Err(error) = result {
206    ///    assert_eq!(error.inner, "next error");
207    ///    assert_eq!(error.retries, 123);
208    /// } else {
209    ///    panic!("Expected error");
210    /// }
211    /// ```
212    fn wrap_to_execution_result(self, result: &W) -> ExecutionResult<R, RE>;
213}
214
215impl<R, RE, TR, IR, IRE> WrapToExecutionResult<R, RE, ExecutionResponse<TR>> for Result<IR, IRE>
216where
217    R: From<IR>,
218    RE: From<IRE>,
219{
220    fn wrap_to_execution_result(self, result: &ExecutionResponse<TR>) -> ExecutionResult<R, RE> {
221        match self {
222            Ok(r) => ExecutionResult::Ok(ExecutionResponse {
223                inner: r.into(),
224                retries: result.retries,
225                address: result.address.clone(),
226            }),
227            Err(e) => ExecutionResult::Err(ExecutionError {
228                inner: e.into(),
229                retries: result.retries,
230                address: Some(result.address.clone()),
231            }),
232        }
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    fn mock_address() -> Address {
241        "http://127.0.0.1:3000".parse().expect("valid address")
242    }
243
244    fn mock_response() -> ExecutionResponse<i32> {
245        ExecutionResponse {
246            inner: 42,
247            retries: 3,
248            address: mock_address(),
249        }
250    }
251
252    fn mock_error() -> ExecutionError<String> {
253        ExecutionError {
254            inner: "test error".to_string(),
255            retries: 2,
256            address: Some(mock_address()),
257        }
258    }
259
260    #[test]
261    fn test_execution_error_partial_eq() {
262        let err1 = mock_error();
263        let err2 = mock_error();
264        assert_eq!(err1, err2);
265
266        let err3 = ExecutionError {
267            inner: "different".to_string(),
268            retries: 2,
269            address: Some(mock_address()),
270        };
271        assert_ne!(err1, err3);
272
273        let err4 = ExecutionError {
274            inner: "test error".to_string(),
275            retries: 5,
276            address: Some(mock_address()),
277        };
278        assert_ne!(err1, err4);
279    }
280
281    #[test]
282    fn test_execution_result_from_response() {
283        let response = mock_response();
284        let result: ExecutionResult<i32, String> = response.into();
285        assert!(result.is_ok());
286        assert_eq!(result.unwrap().inner, 42);
287    }
288
289    #[test]
290    fn test_execution_result_from_error() {
291        let error = mock_error();
292        let result: ExecutionResult<i32, String> = error.into();
293        assert!(result.is_err());
294        assert_eq!(result.unwrap_err().inner, "test error");
295    }
296
297    #[test]
298    fn test_execution_result_inner_into_ok() {
299        let response = mock_response();
300        let result: ExecutionResult<i32, String> = Ok(response);
301        let converted: ExecutionResult<i64, String> = result.inner_into();
302        assert!(converted.is_ok());
303        assert_eq!(converted.unwrap().inner, 42i64);
304    }
305
306    #[test]
307    fn test_execution_result_inner_into_err() {
308        let error = mock_error();
309        let result: ExecutionResult<i32, String> = Err(error);
310        let converted: ExecutionResult<i64, String> = result.inner_into();
311        assert!(converted.is_err());
312        assert_eq!(converted.unwrap_err().inner, "test error");
313    }
314
315    #[test]
316    fn test_wrap_to_execution_result_ok() {
317        let context = mock_response();
318        let inner_result: Result<i64, String> = Ok(100);
319        let wrapped: ExecutionResult<i64, String> = inner_result.wrap_to_execution_result(&context);
320
321        let response = wrapped.unwrap();
322        assert_eq!(response.inner, 100);
323        assert_eq!(response.retries, 3);
324        assert_eq!(response.address, mock_address());
325    }
326
327    #[test]
328    fn test_wrap_to_execution_result_err() {
329        let context = mock_response();
330        let inner_result: Result<i64, String> = Err("wrapped error".to_string());
331        let wrapped: ExecutionResult<i64, String> = inner_result.wrap_to_execution_result(&context);
332
333        let error = wrapped.unwrap_err();
334        assert_eq!(error.inner, "wrapped error");
335        assert_eq!(error.retries, 3);
336        assert_eq!(error.address, Some(mock_address()));
337    }
338
339    #[test]
340    fn test_execution_response_into_inner() {
341        let response = mock_response();
342        let inner: i32 = response.into_inner();
343        assert_eq!(inner, 42);
344    }
345
346    #[test]
347    fn test_execution_response_inner_into() {
348        let response = mock_response();
349        let converted: ExecutionResponse<i64> = response.inner_into();
350        assert_eq!(converted.inner, 42i64);
351        assert_eq!(converted.retries, 3);
352    }
353
354    #[test]
355    fn test_execution_error_into_inner() {
356        let error = mock_error();
357        let inner: String = error.into_inner();
358        assert_eq!(inner, "test error");
359    }
360
361    #[test]
362    fn test_execution_error_inner_into() {
363        let error = mock_error();
364        let converted: ExecutionError<String> = error.inner_into();
365        assert_eq!(converted.inner, "test error");
366        assert_eq!(converted.retries, 2);
367    }
368
369    #[test]
370    fn test_execution_result_into_inner_ok() {
371        let result: ExecutionResult<i32, String> = Ok(mock_response());
372        let inner: Result<i32, String> = result.into_inner();
373        assert_eq!(inner.unwrap(), 42);
374    }
375
376    #[test]
377    fn test_execution_result_into_inner_err() {
378        let result: ExecutionResult<i32, String> = Err(mock_error());
379        let inner: Result<i32, String> = result.into_inner();
380        assert_eq!(inner.unwrap_err(), "test error");
381    }
382
383    #[test]
384    fn test_execution_error_can_retry_delegates() {
385        // Test that ExecutionError's CanRetry impl delegates correctly
386        // We need a type that implements CanRetry
387        use crate::DapiClientError;
388
389        let inner = DapiClientError::NoAvailableAddresses;
390        let error = ExecutionError {
391            inner,
392            retries: 0,
393            address: None,
394        };
395
396        assert!(!error.can_retry());
397        assert!(error.is_no_available_addresses());
398    }
399
400    #[cfg(feature = "mocks")]
401    #[test]
402    fn test_execution_response_default() {
403        let response: ExecutionResponse<i32> = ExecutionResponse::default();
404        assert_eq!(response.inner, 0);
405        assert_eq!(response.retries, 0);
406    }
407}