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
496 .config
497 .api_key
498 .as_ref()
499 .map(ccxt_core::SecretString::expose_secret),
500 Some("test-key")
501 );
502 }
503
504 #[test]
505 fn test_builder_secret() {
506 let builder = BinanceBuilder::new().secret("test-secret");
507 assert_eq!(
508 builder
509 .config
510 .secret
511 .as_ref()
512 .map(ccxt_core::SecretString::expose_secret),
513 Some("test-secret")
514 );
515 }
516
517 #[test]
518 fn test_builder_sandbox() {
519 let builder = BinanceBuilder::new().sandbox(true);
520 assert!(builder.config.sandbox);
521 assert!(builder.options.test);
522 }
523
524 #[test]
525 fn test_builder_timeout() {
526 let builder = BinanceBuilder::new().timeout(Duration::from_secs(60));
527 assert_eq!(builder.config.timeout, Duration::from_secs(60));
528 }
529
530 #[test]
531 fn test_builder_connect_timeout() {
532 let builder = BinanceBuilder::new().connect_timeout(Duration::from_secs(15));
533 assert_eq!(builder.config.connect_timeout, Duration::from_secs(15));
534 }
535
536 #[test]
537 fn test_builder_connect_timeout_secs() {
538 let builder = BinanceBuilder::new().connect_timeout_secs(20);
539 assert_eq!(builder.config.connect_timeout, Duration::from_secs(20));
540 }
541
542 #[test]
543 fn test_builder_recv_window() {
544 let builder = BinanceBuilder::new().recv_window(10000);
545 assert_eq!(builder.options.recv_window, 10000);
546 }
547
548 #[test]
549 fn test_builder_default_type_with_enum() {
550 let builder = BinanceBuilder::new().default_type(DefaultType::Swap);
551 assert_eq!(builder.options.default_type, DefaultType::Swap);
552 }
553
554 #[test]
555 fn test_builder_default_type_with_string() {
556 // Test backward compatibility with string values
557 let builder = BinanceBuilder::new().default_type("swap");
558 assert_eq!(builder.options.default_type, DefaultType::Swap);
559 }
560
561 #[test]
562 fn test_builder_default_type_legacy_future() {
563 // Test backward compatibility with legacy "future" value
564 let builder = BinanceBuilder::new().default_type("future");
565 assert_eq!(builder.options.default_type, DefaultType::Swap);
566 }
567
568 #[test]
569 fn test_builder_default_sub_type() {
570 let builder = BinanceBuilder::new()
571 .default_type(DefaultType::Swap)
572 .default_sub_type(DefaultSubType::Linear);
573 assert_eq!(builder.options.default_type, DefaultType::Swap);
574 assert_eq!(
575 builder.options.default_sub_type,
576 Some(DefaultSubType::Linear)
577 );
578 }
579
580 #[test]
581 fn test_builder_chaining() {
582 let builder = BinanceBuilder::new()
583 .api_key("key")
584 .secret("secret")
585 .sandbox(true)
586 .timeout(Duration::from_secs(30))
587 .recv_window(5000)
588 .default_type(DefaultType::Spot);
589
590 assert_eq!(
591 builder
592 .config
593 .api_key
594 .as_ref()
595 .map(ccxt_core::SecretString::expose_secret),
596 Some("key")
597 );
598 assert_eq!(
599 builder
600 .config
601 .secret
602 .as_ref()
603 .map(ccxt_core::SecretString::expose_secret),
604 Some("secret")
605 );
606 assert!(builder.config.sandbox);
607 assert_eq!(builder.config.timeout, Duration::from_secs(30));
608 assert_eq!(builder.options.recv_window, 5000);
609 assert_eq!(builder.options.default_type, DefaultType::Spot);
610 }
611
612 #[test]
613 fn test_builder_build() {
614 let result = BinanceBuilder::new().build();
615 assert!(result.is_ok());
616
617 let binance = result.expect("Failed to build Binance");
618 assert_eq!(binance.id(), "binance");
619 assert_eq!(binance.name(), "Binance");
620 }
621
622 #[test]
623 fn test_builder_build_with_credentials() {
624 let result = BinanceBuilder::new()
625 .api_key("test-key")
626 .secret("test-secret")
627 .build();
628
629 assert!(result.is_ok());
630 }
631
632 #[test]
633 fn test_builder_time_sync_interval() {
634 let builder = BinanceBuilder::new().time_sync_interval(60);
635 assert_eq!(builder.options.time_sync_interval_secs, 60);
636 }
637
638 #[test]
639 fn test_builder_auto_time_sync() {
640 let builder = BinanceBuilder::new().auto_time_sync(false);
641 assert!(!builder.options.auto_time_sync);
642 }
643
644 #[test]
645 fn test_builder_time_sync_chaining() {
646 let builder = BinanceBuilder::new()
647 .time_sync_interval(120)
648 .auto_time_sync(false);
649
650 assert_eq!(builder.options.time_sync_interval_secs, 120);
651 assert!(!builder.options.auto_time_sync);
652 }
653
654 #[test]
655 fn test_builder_build_with_time_sync_config() {
656 let result = BinanceBuilder::new()
657 .time_sync_interval(60)
658 .auto_time_sync(true)
659 .build();
660
661 assert!(result.is_ok());
662 let binance = result.expect("Failed to build Binance");
663
664 // Verify the TimeSyncManager was created with correct config
665 let time_sync = binance.time_sync();
666 assert_eq!(
667 time_sync.config().sync_interval,
668 std::time::Duration::from_secs(60)
669 );
670 assert!(time_sync.config().auto_sync);
671 }
672
673 #[test]
674 fn test_builder_build_time_sync_disabled() {
675 let result = BinanceBuilder::new().auto_time_sync(false).build();
676
677 assert!(result.is_ok());
678 let binance = result.expect("Failed to build Binance");
679
680 // Verify auto_sync is disabled
681 let time_sync = binance.time_sync();
682 assert!(!time_sync.config().auto_sync);
683 }
684}