ccxt_exchanges/binance/
builder.rs

1//! Binance exchange builder pattern implementation.
2//!
3//! Provides a fluent API for constructing Binance exchange instances with
4//! type-safe configuration options.
5
6use super::{Binance, BinanceOptions};
7use ccxt_core::config::{ProxyConfig, RetryPolicy};
8use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
9use ccxt_core::{ExchangeConfig, Result};
10use serde_json::Value;
11use std::collections::HashMap;
12use std::time::Duration;
13
14/// Builder for creating Binance exchange instances.
15///
16/// Provides a fluent API for configuring all aspects of the Binance exchange,
17/// including authentication, connection settings, and Binance-specific options.
18///
19/// # Example
20///
21/// ```no_run
22/// use ccxt_exchanges::binance::BinanceBuilder;
23///
24/// let binance = BinanceBuilder::new()
25///     .api_key("your-api-key")
26///     .secret("your-secret")
27///     .sandbox(true)
28///     .timeout(30)
29///     .build()
30///     .unwrap();
31/// ```
32#[derive(Debug, Clone)]
33pub struct BinanceBuilder {
34    /// Exchange configuration
35    config: ExchangeConfig,
36    /// Binance-specific options
37    options: BinanceOptions,
38}
39
40impl Default for BinanceBuilder {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl BinanceBuilder {
47    /// Creates a new builder with default configuration.
48    ///
49    /// # Example
50    ///
51    /// ```no_run
52    /// use ccxt_exchanges::binance::BinanceBuilder;
53    ///
54    /// let builder = BinanceBuilder::new();
55    /// ```
56    pub fn new() -> Self {
57        Self {
58            config: ExchangeConfig {
59                id: "binance".to_string(),
60                name: "Binance".to_string(),
61                ..Default::default()
62            },
63            options: BinanceOptions::default(),
64        }
65    }
66
67    /// Sets the API key for authentication.
68    ///
69    /// # Arguments
70    ///
71    /// * `key` - The API key string.
72    ///
73    /// # Example
74    ///
75    /// ```no_run
76    /// use ccxt_exchanges::binance::BinanceBuilder;
77    ///
78    /// let builder = BinanceBuilder::new()
79    ///     .api_key("your-api-key");
80    /// ```
81    pub fn api_key(mut self, key: impl Into<String>) -> Self {
82        self.config.api_key = Some(ccxt_core::SecretString::new(key));
83        self
84    }
85
86    /// Sets the API secret for authentication.
87    ///
88    /// # Arguments
89    ///
90    /// * `secret` - The API secret string.
91    ///
92    /// # Example
93    ///
94    /// ```no_run
95    /// use ccxt_exchanges::binance::BinanceBuilder;
96    ///
97    /// let builder = BinanceBuilder::new()
98    ///     .secret("your-secret");
99    /// ```
100    pub fn secret(mut self, secret: impl Into<String>) -> Self {
101        self.config.secret = Some(ccxt_core::SecretString::new(secret));
102        self
103    }
104
105    /// Enables or disables sandbox/testnet mode.
106    ///
107    /// When enabled, the exchange will connect to Binance's testnet
108    /// instead of the production environment.
109    ///
110    /// # Arguments
111    ///
112    /// * `enabled` - Whether to enable sandbox mode.
113    ///
114    /// # Example
115    ///
116    /// ```no_run
117    /// use ccxt_exchanges::binance::BinanceBuilder;
118    ///
119    /// let builder = BinanceBuilder::new()
120    ///     .sandbox(true);
121    /// ```
122    pub fn sandbox(mut self, enabled: bool) -> Self {
123        self.config.sandbox = enabled;
124        self.options.test = enabled;
125        self
126    }
127
128    /// Sets the request timeout.
129    ///
130    /// # Arguments
131    ///
132    /// * `timeout` - Timeout duration.
133    ///
134    /// # Example
135    ///
136    /// ```no_run
137    /// use ccxt_exchanges::binance::BinanceBuilder;
138    /// use std::time::Duration;
139    ///
140    /// let builder = BinanceBuilder::new()
141    ///     .timeout(Duration::from_secs(60));
142    /// ```
143    pub fn timeout(mut self, timeout: Duration) -> Self {
144        self.config.timeout = timeout;
145        self
146    }
147
148    /// Sets the request timeout in seconds (convenience method).
149    ///
150    /// # Arguments
151    ///
152    /// * `seconds` - Timeout duration in seconds.
153    pub fn timeout_secs(mut self, seconds: u64) -> Self {
154        self.config.timeout = Duration::from_secs(seconds);
155        self
156    }
157
158    /// Sets the TCP connection timeout.
159    ///
160    /// # Arguments
161    ///
162    /// * `timeout` - Connection timeout duration.
163    ///
164    /// # Example
165    ///
166    /// ```no_run
167    /// use ccxt_exchanges::binance::BinanceBuilder;
168    /// use std::time::Duration;
169    ///
170    /// let builder = BinanceBuilder::new()
171    ///     .connect_timeout(Duration::from_secs(15));
172    /// ```
173    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
174        self.config.connect_timeout = timeout;
175        self
176    }
177
178    /// Sets the TCP connection timeout in seconds (convenience method).
179    ///
180    /// # Arguments
181    ///
182    /// * `seconds` - Connection timeout duration in seconds.
183    pub fn connect_timeout_secs(mut self, seconds: u64) -> Self {
184        self.config.connect_timeout = Duration::from_secs(seconds);
185        self
186    }
187
188    /// Sets the retry policy.
189    pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
190        self.config.retry_policy = Some(policy);
191        self
192    }
193
194    /// Sets the receive window for signed requests.
195    ///
196    /// The receive window specifies how long a request is valid after
197    /// the timestamp. Default is 5000 milliseconds.
198    ///
199    /// # Arguments
200    ///
201    /// * `millis` - Receive window in milliseconds.
202    ///
203    /// # Example
204    ///
205    /// ```no_run
206    /// use ccxt_exchanges::binance::BinanceBuilder;
207    ///
208    /// let builder = BinanceBuilder::new()
209    ///     .recv_window(10000);
210    /// ```
211    pub fn recv_window(mut self, millis: u64) -> Self {
212        self.options.recv_window = millis;
213        self
214    }
215
216    /// Sets the default trading type.
217    ///
218    /// Accepts both `DefaultType` enum values and string values for backward compatibility.
219    /// Valid string values: "spot", "margin", "swap", "futures", "option".
220    /// Legacy values "future" and "delivery" are also supported.
221    ///
222    /// # Arguments
223    ///
224    /// * `trading_type` - The default trading type (DefaultType or string).
225    ///
226    /// # Example
227    ///
228    /// ```no_run
229    /// use ccxt_exchanges::binance::BinanceBuilder;
230    /// use ccxt_core::types::default_type::DefaultType;
231    ///
232    /// // Using DefaultType enum (recommended)
233    /// let builder = BinanceBuilder::new()
234    ///     .default_type(DefaultType::Swap);
235    ///
236    /// // Using string (backward compatible)
237    /// let builder = BinanceBuilder::new()
238    ///     .default_type("swap");
239    /// ```
240    pub fn default_type(mut self, trading_type: impl Into<DefaultType>) -> Self {
241        self.options.default_type = trading_type.into();
242        self
243    }
244
245    /// Sets the default sub-type for contract settlement.
246    ///
247    /// - `Linear`: USDT-margined contracts (FAPI)
248    /// - `Inverse`: Coin-margined contracts (DAPI)
249    ///
250    /// Only applicable when `default_type` is `Swap`, `Futures`, or `Option`.
251    ///
252    /// # Arguments
253    ///
254    /// * `sub_type` - The default sub-type for contract settlement.
255    ///
256    /// # Example
257    ///
258    /// ```no_run
259    /// use ccxt_exchanges::binance::BinanceBuilder;
260    /// use ccxt_core::types::default_type::{DefaultType, DefaultSubType};
261    ///
262    /// let builder = BinanceBuilder::new()
263    ///     .default_type(DefaultType::Swap)
264    ///     .default_sub_type(DefaultSubType::Linear);
265    /// ```
266    pub fn default_sub_type(mut self, sub_type: DefaultSubType) -> Self {
267        self.options.default_sub_type = Some(sub_type);
268        self
269    }
270
271    /// Enables or disables time synchronization adjustment.
272    ///
273    /// When enabled, the exchange will adjust for time differences
274    /// between the local system and Binance servers.
275    ///
276    /// # Arguments
277    ///
278    /// * `enabled` - Whether to enable time adjustment.
279    ///
280    /// # Example
281    ///
282    /// ```no_run
283    /// use ccxt_exchanges::binance::BinanceBuilder;
284    ///
285    /// let builder = BinanceBuilder::new()
286    ///     .adjust_for_time_difference(true);
287    /// ```
288    pub fn adjust_for_time_difference(mut self, enabled: bool) -> Self {
289        self.options.adjust_for_time_difference = enabled;
290        self
291    }
292
293    /// Sets the time sync interval in seconds.
294    ///
295    /// Controls how often the time offset is refreshed when auto sync is enabled.
296    /// Default is 30 seconds.
297    ///
298    /// # Arguments
299    ///
300    /// * `seconds` - Sync interval in seconds.
301    ///
302    /// # Example
303    ///
304    /// ```no_run
305    /// use ccxt_exchanges::binance::BinanceBuilder;
306    ///
307    /// let builder = BinanceBuilder::new()
308    ///     .time_sync_interval(60); // Sync every 60 seconds
309    /// ```
310    pub fn time_sync_interval(mut self, seconds: u64) -> Self {
311        self.options.time_sync_interval_secs = seconds;
312        self
313    }
314
315    /// Enables or disables automatic periodic time sync.
316    ///
317    /// When enabled, the time offset will be automatically refreshed
318    /// based on the configured sync interval.
319    /// Default is true.
320    ///
321    /// # Arguments
322    ///
323    /// * `enabled` - Whether to enable automatic time sync.
324    ///
325    /// # Example
326    ///
327    /// ```no_run
328    /// use ccxt_exchanges::binance::BinanceBuilder;
329    ///
330    /// // Disable automatic sync for manual control
331    /// let builder = BinanceBuilder::new()
332    ///     .auto_time_sync(false);
333    /// ```
334    pub fn auto_time_sync(mut self, enabled: bool) -> Self {
335        self.options.auto_time_sync = enabled;
336        self
337    }
338
339    /// Enables or disables rate limiting.
340    ///
341    /// # Arguments
342    ///
343    /// * `enabled` - Whether to enable rate limiting.
344    ///
345    /// # Example
346    ///
347    /// ```no_run
348    /// use ccxt_exchanges::binance::BinanceBuilder;
349    ///
350    /// let builder = BinanceBuilder::new()
351    ///     .enable_rate_limit(true);
352    /// ```
353    pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
354        self.config.enable_rate_limit = enabled;
355        self
356    }
357
358    /// Sets the HTTP proxy configuration.
359    ///
360    /// # Arguments
361    ///
362    /// * `proxy` - The proxy configuration.
363    pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
364        self.config.proxy = Some(proxy);
365        self
366    }
367
368    /// Sets the HTTP proxy URL (convenience method).
369    ///
370    /// # Arguments
371    ///
372    /// * `url` - The proxy server URL.
373    ///
374    /// # Example
375    ///
376    /// ```no_run
377    /// use ccxt_exchanges::binance::BinanceBuilder;
378    ///
379    /// let builder = BinanceBuilder::new()
380    ///     .proxy_url("http://proxy.example.com:8080");
381    /// ```
382    pub fn proxy_url(mut self, url: impl Into<String>) -> Self {
383        self.config.proxy = Some(ProxyConfig::new(url));
384        self
385    }
386
387    /// Enables or disables verbose logging.
388    ///
389    /// # Arguments
390    ///
391    /// * `enabled` - Whether to enable verbose logging.
392    ///
393    /// # Example
394    ///
395    /// ```no_run
396    /// use ccxt_exchanges::binance::BinanceBuilder;
397    ///
398    /// let builder = BinanceBuilder::new()
399    ///     .verbose(true);
400    /// ```
401    pub fn verbose(mut self, enabled: bool) -> Self {
402        self.config.verbose = enabled;
403        self
404    }
405
406    /// Sets a custom option.
407    ///
408    /// # Arguments
409    ///
410    /// * `key` - Option key.
411    /// * `value` - Option value as JSON.
412    ///
413    /// # Example
414    ///
415    /// ```no_run
416    /// use ccxt_exchanges::binance::BinanceBuilder;
417    /// use serde_json::json;
418    ///
419    /// let builder = BinanceBuilder::new()
420    ///     .option("customOption", json!("value"));
421    /// ```
422    pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
423        self.config.options.insert(key.into(), value);
424        self
425    }
426
427    /// Sets multiple custom options.
428    ///
429    /// # Arguments
430    ///
431    /// * `options` - HashMap of option key-value pairs.
432    ///
433    /// # Example
434    ///
435    /// ```no_run
436    /// use ccxt_exchanges::binance::BinanceBuilder;
437    /// use serde_json::json;
438    /// use std::collections::HashMap;
439    ///
440    /// let mut options = HashMap::new();
441    /// options.insert("option1".to_string(), json!("value1"));
442    /// options.insert("option2".to_string(), json!(42));
443    ///
444    /// let builder = BinanceBuilder::new()
445    ///     .options(options);
446    /// ```
447    pub fn options(mut self, options: HashMap<String, Value>) -> Self {
448        self.config.options.extend(options);
449        self
450    }
451
452    /// Builds the Binance exchange instance.
453    ///
454    /// # Returns
455    ///
456    /// Returns a `Result` containing the configured `Binance` instance.
457    ///
458    /// # Errors
459    ///
460    /// Returns an error if the exchange cannot be initialized.
461    ///
462    /// # Example
463    ///
464    /// ```no_run
465    /// use ccxt_exchanges::binance::BinanceBuilder;
466    ///
467    /// let binance = BinanceBuilder::new()
468    ///     .api_key("your-api-key")
469    ///     .secret("your-secret")
470    ///     .build()
471    ///     .unwrap();
472    /// ```
473    pub fn build(self) -> Result<Binance> {
474        Binance::new_with_options(self.config, self.options)
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    #[test]
483    fn test_builder_default() {
484        let builder = BinanceBuilder::new();
485        assert_eq!(builder.config.id, "binance");
486        assert_eq!(builder.config.name, "Binance");
487        assert!(!builder.config.sandbox);
488        assert_eq!(builder.options.default_type, DefaultType::Spot);
489    }
490
491    #[test]
492    fn test_builder_api_key() {
493        let builder = BinanceBuilder::new().api_key("test-key");
494        assert_eq!(
495            builder.config.api_key.as_ref().map(|s| s.expose_secret()),
496            Some("test-key")
497        );
498    }
499
500    #[test]
501    fn test_builder_secret() {
502        let builder = BinanceBuilder::new().secret("test-secret");
503        assert_eq!(
504            builder.config.secret.as_ref().map(|s| s.expose_secret()),
505            Some("test-secret")
506        );
507    }
508
509    #[test]
510    fn test_builder_sandbox() {
511        let builder = BinanceBuilder::new().sandbox(true);
512        assert!(builder.config.sandbox);
513        assert!(builder.options.test);
514    }
515
516    #[test]
517    fn test_builder_timeout() {
518        let builder = BinanceBuilder::new().timeout(Duration::from_secs(60));
519        assert_eq!(builder.config.timeout, Duration::from_secs(60));
520    }
521
522    #[test]
523    fn test_builder_connect_timeout() {
524        let builder = BinanceBuilder::new().connect_timeout(Duration::from_secs(15));
525        assert_eq!(builder.config.connect_timeout, Duration::from_secs(15));
526    }
527
528    #[test]
529    fn test_builder_connect_timeout_secs() {
530        let builder = BinanceBuilder::new().connect_timeout_secs(20);
531        assert_eq!(builder.config.connect_timeout, Duration::from_secs(20));
532    }
533
534    #[test]
535    fn test_builder_recv_window() {
536        let builder = BinanceBuilder::new().recv_window(10000);
537        assert_eq!(builder.options.recv_window, 10000);
538    }
539
540    #[test]
541    fn test_builder_default_type_with_enum() {
542        let builder = BinanceBuilder::new().default_type(DefaultType::Swap);
543        assert_eq!(builder.options.default_type, DefaultType::Swap);
544    }
545
546    #[test]
547    fn test_builder_default_type_with_string() {
548        // Test backward compatibility with string values
549        let builder = BinanceBuilder::new().default_type("swap");
550        assert_eq!(builder.options.default_type, DefaultType::Swap);
551    }
552
553    #[test]
554    fn test_builder_default_type_legacy_future() {
555        // Test backward compatibility with legacy "future" value
556        let builder = BinanceBuilder::new().default_type("future");
557        assert_eq!(builder.options.default_type, DefaultType::Swap);
558    }
559
560    #[test]
561    fn test_builder_default_sub_type() {
562        let builder = BinanceBuilder::new()
563            .default_type(DefaultType::Swap)
564            .default_sub_type(DefaultSubType::Linear);
565        assert_eq!(builder.options.default_type, DefaultType::Swap);
566        assert_eq!(
567            builder.options.default_sub_type,
568            Some(DefaultSubType::Linear)
569        );
570    }
571
572    #[test]
573    fn test_builder_chaining() {
574        let builder = BinanceBuilder::new()
575            .api_key("key")
576            .secret("secret")
577            .sandbox(true)
578            .timeout(Duration::from_secs(30))
579            .recv_window(5000)
580            .default_type(DefaultType::Spot);
581
582        assert_eq!(
583            builder.config.api_key.as_ref().map(|s| s.expose_secret()),
584            Some("key")
585        );
586        assert_eq!(
587            builder.config.secret.as_ref().map(|s| s.expose_secret()),
588            Some("secret")
589        );
590        assert!(builder.config.sandbox);
591        assert_eq!(builder.config.timeout, Duration::from_secs(30));
592        assert_eq!(builder.options.recv_window, 5000);
593        assert_eq!(builder.options.default_type, DefaultType::Spot);
594    }
595
596    #[test]
597    fn test_builder_build() {
598        let result = BinanceBuilder::new().build();
599        assert!(result.is_ok());
600
601        let binance = result.unwrap();
602        assert_eq!(binance.id(), "binance");
603        assert_eq!(binance.name(), "Binance");
604    }
605
606    #[test]
607    fn test_builder_build_with_credentials() {
608        let result = BinanceBuilder::new()
609            .api_key("test-key")
610            .secret("test-secret")
611            .build();
612
613        assert!(result.is_ok());
614    }
615
616    #[test]
617    fn test_builder_time_sync_interval() {
618        let builder = BinanceBuilder::new().time_sync_interval(60);
619        assert_eq!(builder.options.time_sync_interval_secs, 60);
620    }
621
622    #[test]
623    fn test_builder_auto_time_sync() {
624        let builder = BinanceBuilder::new().auto_time_sync(false);
625        assert!(!builder.options.auto_time_sync);
626    }
627
628    #[test]
629    fn test_builder_time_sync_chaining() {
630        let builder = BinanceBuilder::new()
631            .time_sync_interval(120)
632            .auto_time_sync(false);
633
634        assert_eq!(builder.options.time_sync_interval_secs, 120);
635        assert!(!builder.options.auto_time_sync);
636    }
637
638    #[test]
639    fn test_builder_build_with_time_sync_config() {
640        let result = BinanceBuilder::new()
641            .time_sync_interval(60)
642            .auto_time_sync(true)
643            .build();
644
645        assert!(result.is_ok());
646        let binance = result.unwrap();
647
648        // Verify the TimeSyncManager was created with correct config
649        let time_sync = binance.time_sync();
650        assert_eq!(
651            time_sync.config().sync_interval,
652            std::time::Duration::from_secs(60)
653        );
654        assert!(time_sync.config().auto_sync);
655    }
656
657    #[test]
658    fn test_builder_build_time_sync_disabled() {
659        let result = BinanceBuilder::new().auto_time_sync(false).build();
660
661        assert!(result.is_ok());
662        let binance = result.unwrap();
663
664        // Verify auto_sync is disabled
665        let time_sync = binance.time_sync();
666        assert!(!time_sync.config().auto_sync);
667    }
668}