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}