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