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