Skip to main content

binance_sdk/common/
config.rs

1use derive_builder::Builder;
2use reqwest::{Client, ClientBuilder};
3use std::collections::HashMap;
4use std::fmt;
5use std::sync::Arc;
6use tokio_tungstenite::Connector;
7
8use super::models::{ConfigBuildError, TimeUnit, WebsocketMode};
9use super::utils::{SignatureGenerator, build_client};
10
11#[derive(Clone)]
12pub struct AgentConnector(pub Connector);
13
14impl fmt::Debug for AgentConnector {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "Connector(…)")
17    }
18}
19
20/// The result produced by a custom [`WebsocketHandshakeFn`]: either the established
21/// WebSocket stream together with the server's HTTP handshake response, or a
22/// `tungstenite` error describing why the handshake could not be completed.
23///
24/// This mirrors the return type of
25/// [`tokio_tungstenite::connect_async_tls_with_config`], so an implementation can
26/// simply delegate to it after doing any transport-level setup it needs (see
27/// [`WebsocketHandshakeFn`] for details).
28pub type WebsocketHandshakeResult = Result<
29    (
30        tokio_tungstenite::WebSocketStream<
31            tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
32        >,
33        tokio_tungstenite::tungstenite::handshake::client::Response,
34    ),
35    tokio_tungstenite::tungstenite::Error,
36>;
37
38/// A caller-supplied async function that performs the TLS/WebSocket handshake in
39/// place of the SDK's default connection path.
40///
41/// This is useful when the underlying transport needs to be established before the
42/// handshake runs — for example, tunneling the connection through a SOCKS5 proxy or
43/// another custom stream — something the SDK does not implement itself. The
44/// callback is used for the initial connection, reconnects, and connection renewal,
45/// and is subject to the same 10-second timeout and error mapping as the SDK's
46/// built-in handshake.
47///
48/// # Arguments passed to the function
49///
50/// The function receives the same inputs that
51/// [`tokio_tungstenite::connect_async_tls_with_config`] takes, so an implementation
52/// can open its own transport and then delegate to that function (or an equivalent)
53/// to finish the handshake:
54///
55/// 1. `Request` — the outgoing HTTP upgrade request (already carries the target URL
56///    and any headers the SDK has set, such as `User-Agent`).
57/// 2. `Option<WebSocketConfig>` — optional WebSocket protocol configuration.
58/// 3. `bool` — whether to disable Nagle's algorithm on the underlying TCP stream.
59/// 4. `Option<Connector>` — the TLS connector derived from the configuration's
60///    `agent` field, if one was set. A custom handshake function that ignores this
61///    value effectively overrides `agent`; forward it to the delegate call if the
62///    two options are meant to compose.
63///
64/// # Relationship with `agent`
65///
66/// `agent` and `handshake` are independent, composable settings: `agent` only
67/// customizes the TLS connector, while `handshake` replaces how the transport is
68/// opened and the handshake is performed. Setting both is valid — the resolved
69/// `agent` connector is simply passed through as the fourth argument above for the
70/// custom handshake function to use as it sees fit.
71pub type WebsocketHandshakeFn = Arc<
72    dyn Fn(
73            tokio_tungstenite::tungstenite::handshake::client::Request,
74            Option<tokio_tungstenite::tungstenite::protocol::WebSocketConfig>,
75            bool,
76            Option<Connector>,
77        ) -> std::pin::Pin<Box<dyn Future<Output = WebsocketHandshakeResult> + Send>>
78        + Send
79        + Sync,
80>;
81
82#[derive(Clone)]
83pub struct HttpAgent(pub Arc<dyn Fn(ClientBuilder) -> ClientBuilder + Send + Sync>);
84
85impl fmt::Debug for HttpAgent {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "HttpAgent(<custom agent fn>)")
88    }
89}
90
91#[derive(Clone)]
92pub struct ProxyAuth {
93    pub username: String,
94    pub password: String,
95}
96
97#[derive(Debug, Clone)]
98pub struct ProxyConfig {
99    pub host: String,
100    pub port: u16,
101    pub protocol: Option<String>,
102    pub auth: Option<ProxyAuth>,
103}
104
105impl fmt::Debug for ProxyAuth {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        f.debug_struct("ProxyAuth")
108            .field("username", &self.username)
109            .field("password", &"[REDACTED]")
110            .finish()
111    }
112}
113
114#[derive(Clone)]
115pub enum PrivateKey {
116    File(String),
117    Raw(Vec<u8>),
118}
119
120impl fmt::Debug for PrivateKey {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            PrivateKey::File(_) => write!(f, "PrivateKey::File([REDACTED])"),
124            PrivateKey::Raw(_) => write!(f, "PrivateKey::Raw([REDACTED])"),
125        }
126    }
127}
128
129#[derive(Clone, Builder)]
130#[builder(
131    pattern = "owned",
132    build_fn(name = "try_build", error = "ConfigBuildError")
133)]
134pub struct ConfigurationRestApi {
135    #[builder(setter(into, strip_option), default)]
136    pub api_key: Option<String>,
137
138    #[builder(setter(into, strip_option), default)]
139    pub api_secret: Option<String>,
140
141    #[builder(setter(into, strip_option), default)]
142    pub base_path: Option<String>,
143
144    #[builder(default = "1000")]
145    pub timeout: u64,
146
147    #[builder(default = "true")]
148    pub keep_alive: bool,
149
150    #[builder(default = "true")]
151    pub compression: bool,
152
153    #[builder(default = "3")]
154    pub retries: u32,
155
156    #[builder(default = "1000")]
157    pub backoff: u64,
158
159    #[builder(setter(strip_option), default)]
160    pub proxy: Option<ProxyConfig>,
161
162    #[builder(setter(strip_option, into), default)]
163    pub custom_headers: Option<HashMap<String, String>>,
164
165    #[builder(setter(strip_option), default)]
166    pub agent: Option<HttpAgent>,
167
168    #[builder(setter(strip_option), default)]
169    pub private_key: Option<PrivateKey>,
170
171    #[builder(setter(strip_option), default)]
172    pub private_key_passphrase: Option<String>,
173
174    #[builder(setter(strip_option), default)]
175    pub time_unit: Option<TimeUnit>,
176
177    #[builder(setter(skip))]
178    pub(crate) client: Client,
179
180    #[builder(setter(skip))]
181    pub(crate) user_agent: String,
182
183    #[builder(setter(skip))]
184    pub(crate) signature_gen: SignatureGenerator,
185}
186
187impl fmt::Debug for ConfigurationRestApi {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        f.debug_struct("ConfigurationRestApi")
190            .field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]"))
191            .field(
192                "api_secret",
193                &self.api_secret.as_ref().map(|_| "[REDACTED]"),
194            )
195            .field("base_path", &self.base_path)
196            .field("timeout", &self.timeout)
197            .field("keep_alive", &self.keep_alive)
198            .field("compression", &self.compression)
199            .field("retries", &self.retries)
200            .field("backoff", &self.backoff)
201            .field("proxy", &self.proxy)
202            .field("custom_headers", &self.custom_headers)
203            .field("agent", &self.agent)
204            .field(
205                "private_key",
206                &self.private_key.as_ref().map(|_| "[REDACTED]"),
207            )
208            .field(
209                "private_key_passphrase",
210                &self.private_key_passphrase.as_ref().map(|_| "[REDACTED]"),
211            )
212            .field("time_unit", &self.time_unit)
213            .field("client", &"<reqwest::Client>")
214            .field("user_agent", &self.user_agent)
215            .field("signature_gen", &self.signature_gen)
216            .finish()
217    }
218}
219
220impl ConfigurationRestApi {
221    #[must_use]
222    pub fn builder() -> ConfigurationRestApiBuilder {
223        ConfigurationRestApiBuilder::default()
224    }
225}
226
227impl ConfigurationRestApiBuilder {
228    /// Builds a `ConfigurationRestApi` instance with configured HTTP client and signature generator.
229    ///
230    /// # Returns
231    ///
232    /// A `Result` containing the fully configured `ConfigurationRestApi` or a `ConfigBuildError` if configuration fails.
233    ///
234    /// # Errors
235    ///
236    /// Returns a `ConfigBuildError` if the initial configuration build fails or if client setup encounters issues.
237    pub fn build(self) -> Result<ConfigurationRestApi, ConfigBuildError> {
238        let mut cfg = self.try_build()?;
239        cfg.client = build_client(
240            cfg.timeout,
241            cfg.keep_alive,
242            cfg.proxy.as_ref(),
243            cfg.agent.clone(),
244        );
245        cfg.signature_gen = SignatureGenerator::new(
246            cfg.api_secret.clone(),
247            cfg.private_key.clone(),
248            cfg.private_key_passphrase.clone(),
249        );
250
251        Ok(cfg)
252    }
253}
254
255#[derive(Clone, Builder)]
256#[builder(
257    pattern = "owned",
258    build_fn(name = "try_build", error = "ConfigBuildError")
259)]
260pub struct ConfigurationWebsocketApi {
261    #[builder(setter(into, strip_option), default)]
262    pub api_key: Option<String>,
263
264    #[builder(setter(into, strip_option), default)]
265    pub api_secret: Option<String>,
266
267    #[builder(setter(into, strip_option), default)]
268    pub ws_url: Option<String>,
269
270    #[builder(default = "5000")]
271    pub timeout: u64,
272
273    #[builder(default = "5000")]
274    pub reconnect_delay: u64,
275
276    #[builder(default = "WebsocketMode::Single")]
277    pub mode: WebsocketMode,
278
279    #[builder(setter(strip_option), default)]
280    pub agent: Option<AgentConnector>,
281
282    /// Overrides how the SDK performs the TLS/WebSocket handshake. See
283    /// [`WebsocketHandshakeFn`] for details and its interaction with `agent`.
284    #[builder(setter(strip_option), default)]
285    pub handshake: Option<WebsocketHandshakeFn>,
286
287    #[builder(setter(strip_option), default)]
288    pub private_key: Option<PrivateKey>,
289
290    #[builder(setter(strip_option), default)]
291    pub private_key_passphrase: Option<String>,
292
293    #[builder(setter(strip_option), default)]
294    pub time_unit: Option<TimeUnit>,
295
296    #[builder(default = "true")]
297    pub auto_session_relogon: bool,
298
299    #[builder(setter(skip))]
300    pub(crate) user_agent: String,
301
302    #[builder(setter(skip))]
303    pub(crate) signature_gen: SignatureGenerator,
304}
305
306impl fmt::Debug for ConfigurationWebsocketApi {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        f.debug_struct("ConfigurationWebsocketApi")
309            .field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]"))
310            .field(
311                "api_secret",
312                &self.api_secret.as_ref().map(|_| "[REDACTED]"),
313            )
314            .field("ws_url", &self.ws_url)
315            .field("timeout", &self.timeout)
316            .field("reconnect_delay", &self.reconnect_delay)
317            .field("mode", &self.mode)
318            .field("agent", &self.agent)
319            .field(
320                "handshake",
321                &self.handshake.as_ref().map(|_| "<custom handshake fn>"),
322            )
323            .field(
324                "private_key",
325                &self.private_key.as_ref().map(|_| "[REDACTED]"),
326            )
327            .field(
328                "private_key_passphrase",
329                &self.private_key_passphrase.as_ref().map(|_| "[REDACTED]"),
330            )
331            .field("time_unit", &self.time_unit)
332            .field("auto_session_relogon", &self.auto_session_relogon)
333            .field("user_agent", &self.user_agent)
334            .field("signature_gen", &self.signature_gen)
335            .finish()
336    }
337}
338
339impl ConfigurationWebsocketApi {
340    /// Creates a builder for `ConfigurationWebsocketApi` with the specified API key.
341    ///
342    /// # Arguments
343    ///
344    /// * `api_key` - The API key to be used for the WebSocket API configuration
345    ///
346    /// # Returns
347    ///
348    /// A `ConfigurationWebsocketApiBuilder` initialized with the provided API key
349    #[must_use]
350    pub fn builder() -> ConfigurationWebsocketApiBuilder {
351        ConfigurationWebsocketApiBuilder::default()
352    }
353}
354
355impl ConfigurationWebsocketApiBuilder {
356    /// Builds the `ConfigurationWebsocketApi` with a generated signature generator.
357    ///
358    /// This method attempts to build the configuration using the builder's settings
359    /// and then initializes the signature generator with the API secret, private key,
360    /// and private key passphrase.
361    ///
362    /// # Returns
363    ///
364    /// A `Result` containing the fully configured `ConfigurationWebsocketApi` or a
365    /// `ConfigBuildError` if the build process fails.
366    ///
367    /// # Errors
368    ///
369    /// Returns a `ConfigBuildError` if the initial configuration build fails or if signature generation fails.
370    ///
371    pub fn build(self) -> Result<ConfigurationWebsocketApi, ConfigBuildError> {
372        let mut cfg = self.try_build()?;
373        cfg.signature_gen = SignatureGenerator::new(
374            cfg.api_secret.clone(),
375            cfg.private_key.clone(),
376            cfg.private_key_passphrase.clone(),
377        );
378
379        Ok(cfg)
380    }
381}
382
383#[derive(Clone, Builder)]
384#[builder(pattern = "owned", build_fn(error = "ConfigBuildError"))]
385pub struct ConfigurationWebsocketStreams {
386    #[builder(setter(into, strip_option), default)]
387    pub ws_url: Option<String>,
388
389    #[builder(default = "5000")]
390    pub reconnect_delay: u64,
391
392    #[builder(default = "WebsocketMode::Single")]
393    pub mode: WebsocketMode,
394
395    #[builder(setter(strip_option), default)]
396    pub agent: Option<AgentConnector>,
397
398    /// Overrides how the SDK performs the TLS/WebSocket handshake. See
399    /// [`WebsocketHandshakeFn`] for details and its interaction with `agent`.
400    #[builder(setter(strip_option), default)]
401    pub handshake: Option<WebsocketHandshakeFn>,
402
403    #[builder(setter(strip_option), default)]
404    pub time_unit: Option<TimeUnit>,
405
406    #[builder(setter(skip))]
407    pub(crate) user_agent: String,
408}
409
410impl fmt::Debug for ConfigurationWebsocketStreams {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        f.debug_struct("ConfigurationWebsocketStreams")
413            .field("ws_url", &self.ws_url)
414            .field("reconnect_delay", &self.reconnect_delay)
415            .field("mode", &self.mode)
416            .field("agent", &self.agent)
417            .field(
418                "handshake",
419                &self.handshake.as_ref().map(|_| "<custom handshake fn>"),
420            )
421            .field("time_unit", &self.time_unit)
422            .field("user_agent", &self.user_agent)
423            .finish()
424    }
425}
426
427impl ConfigurationWebsocketStreams {
428    #[must_use]
429    /// Creates a builder for `ConfigurationWebsocketStreams` with default settings.
430    ///
431    /// # Returns
432    ///
433    /// A `ConfigurationWebsocketStreamsBuilder` initialized with default values
434    pub fn builder() -> ConfigurationWebsocketStreamsBuilder {
435        ConfigurationWebsocketStreamsBuilder::default()
436    }
437}