rs_dapi_client/lib.rs
1//! This crate provides [DapiClient] --- transport layer for a decentralized API for Dash.
2
3#![deny(missing_docs)]
4
5mod address_ban_info;
6mod address_list;
7mod connection_pool;
8mod dapi_client;
9#[cfg(feature = "dump")]
10pub mod dump;
11mod executor;
12#[cfg(feature = "mocks")]
13pub mod mock;
14mod request_settings;
15pub mod transport;
16
17pub use address_ban_info::AddressBanInfo;
18pub use address_list::Address;
19pub use address_list::AddressList;
20pub use address_list::AddressListError;
21pub use address_list::AddressStatus;
22pub use connection_pool::ConnectionPool;
23pub use dapi_client::{update_address_ban_status, DapiClient, DapiClientError};
24#[cfg(feature = "dump")]
25pub use dump::DumpData;
26pub use executor::{
27 DapiRequestExecutor, ExecutionError, ExecutionResponse, ExecutionResult, InnerInto, IntoInner,
28 WrapToExecutionResult,
29};
30use futures::{future::BoxFuture, FutureExt};
31#[cfg(any(target_arch = "wasm32", not(feature = "mocks")))]
32pub use http::Uri;
33#[cfg(all(feature = "mocks", not(target_arch = "wasm32")))]
34pub use http_serde::http::Uri;
35pub use request_settings::RequestSettings;
36
37/// A DAPI request could be executed with an initialized [DapiClient].
38///
39/// # Examples
40/// Requires the `mocks` feature.
41/// ```
42/// # #[cfg(feature = "mocks")]
43/// # {
44/// use rs_dapi_client::{RequestSettings, AddressList, mock::MockDapiClient, DapiClientError, DapiRequest, ExecutionError};
45/// use dapi_grpc::platform::v0::{self as proto};
46///
47/// # let _ = async {
48/// let mut client = MockDapiClient::new();
49/// let request: proto::GetIdentityRequest = proto::get_identity_request::GetIdentityRequestV0 { id: b"0".to_vec(), prove: true }.into();
50/// let response = request.execute(&mut client, RequestSettings::default()).await?;
51/// # Ok::<(), ExecutionError<DapiClientError>>(())
52/// # };
53/// # }
54/// ```
55pub trait DapiRequest {
56 /// Response from DAPI for this specific request.
57 type Response;
58
59 /// Executes the request.
60 fn execute<'c, D: DapiRequestExecutor>(
61 self,
62 dapi_client: &'c D,
63 settings: RequestSettings,
64 ) -> BoxFuture<'c, ExecutionResult<Self::Response, DapiClientError>>
65 where
66 Self: 'c;
67}
68
69/// The trait is intentionally made sealed since it defines what is possible to send to DAPI.
70impl<T: transport::TransportRequest + Send> DapiRequest for T {
71 type Response = T::Response;
72
73 fn execute<'c, D: DapiRequestExecutor>(
74 self,
75 dapi_client: &'c D,
76 settings: RequestSettings,
77 ) -> BoxFuture<'c, ExecutionResult<Self::Response, DapiClientError>>
78 where
79 Self: 'c,
80 {
81 dapi_client.execute(self, settings).boxed()
82 }
83}
84
85/// Returns true if the operation can be retried.
86pub trait CanRetry {
87 /// Returns true if the operation can be retried safely.
88 fn can_retry(&self) -> bool;
89
90 /// Returns true if this error represents a "no available addresses" condition.
91 ///
92 /// When all addresses have been banned due to errors, the client returns this error.
93 /// Retry logic uses this to return the last meaningful error instead of this one.
94 fn is_no_available_addresses(&self) -> bool {
95 false
96 }
97
98 /// If this error is a gRPC `ResourceExhausted` (Envoy rate-limit) that
99 /// carries a `RateLimit-Reset` metadata header, returns the server-advertised
100 /// ban duration (clamped to a safe range). Returns `None` for all other
101 /// errors and for rate-limit errors that carry no usable header (the caller
102 /// falls back to the normal exponential ban ladder in that case).
103 fn rate_limit_ban_duration(&self) -> Option<std::time::Duration> {
104 None
105 }
106
107 /// Get boolean flag that indicates if the error is retryable.
108 ///
109 /// Deprecated in favor of [CanRetry::can_retry].
110 #[deprecated = "Use !can_retry() instead"]
111 fn is_node_failure(&self) -> bool {
112 !self.can_retry()
113 }
114}