ccxt_exchanges/bitget/builder.rs
1//! Bitget exchange builder pattern implementation.
2//!
3//! Provides a fluent API for constructing Bitget exchange instances with
4//! type-safe configuration options.
5
6use super::{Bitget, BitgetOptions};
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 Bitget exchange instances.
13///
14/// Provides a fluent API for configuring all aspects of the Bitget exchange,
15/// including authentication, connection settings, and Bitget-specific options.
16///
17/// # Example
18///
19/// ```no_run
20/// use ccxt_exchanges::bitget::BitgetBuilder;
21///
22/// let bitget = BitgetBuilder::new()
23/// .api_key("your-api-key")
24/// .secret("your-secret")
25/// .passphrase("your-passphrase")
26/// .sandbox(true)
27/// .timeout(30)
28/// .build()
29/// .unwrap();
30/// ```
31#[derive(Debug, Clone)]
32pub struct BitgetBuilder {
33 /// Exchange configuration
34 config: ExchangeConfig,
35 /// Bitget-specific options
36 options: BitgetOptions,
37}
38
39impl Default for BitgetBuilder {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl BitgetBuilder {
46 /// Creates a new builder with default configuration.
47 ///
48 /// # Example
49 ///
50 /// ```no_run
51 /// use ccxt_exchanges::bitget::BitgetBuilder;
52 ///
53 /// let builder = BitgetBuilder::new();
54 /// ```
55 pub fn new() -> Self {
56 Self {
57 config: ExchangeConfig {
58 id: "bitget".to_string(),
59 name: "Bitget".to_string(),
60 ..Default::default()
61 },
62 options: BitgetOptions::default(),
63 }
64 }
65
66 /// Sets the API key for authentication.
67 ///
68 /// # Arguments
69 ///
70 /// * `key` - The API key string.
71 pub fn api_key(mut self, key: impl Into<String>) -> Self {
72 self.config.api_key = Some(key.into());
73 self
74 }
75
76 /// Sets the API secret for authentication.
77 ///
78 /// # Arguments
79 ///
80 /// * `secret` - The API secret string.
81 pub fn secret(mut self, secret: impl Into<String>) -> Self {
82 self.config.secret = Some(secret.into());
83 self
84 }
85
86 /// Sets the passphrase for authentication.
87 ///
88 /// Bitget requires a passphrase in addition to API key and secret.
89 ///
90 /// # Arguments
91 ///
92 /// * `passphrase` - The passphrase string.
93 pub fn passphrase(mut self, passphrase: impl Into<String>) -> Self {
94 self.config.password = Some(passphrase.into());
95 self
96 }
97
98 /// Enables or disables sandbox/demo mode.
99 ///
100 /// When enabled, the exchange will connect to Bitget's demo
101 /// environment instead of the production environment.
102 ///
103 /// # Arguments
104 ///
105 /// * `enabled` - Whether to enable sandbox mode.
106 pub fn sandbox(mut self, enabled: bool) -> Self {
107 self.config.sandbox = enabled;
108 self.options.testnet = enabled;
109 self
110 }
111
112 /// Sets the product type for trading.
113 ///
114 /// Valid values: "spot", "umcbl" (USDT-M futures), "dmcbl" (Coin-M futures).
115 ///
116 /// Note: Consider using `default_type()` and `default_sub_type()` instead
117 /// for a more type-safe configuration.
118 ///
119 /// # Arguments
120 ///
121 /// * `product_type` - The product type string.
122 pub fn product_type(mut self, product_type: impl Into<String>) -> Self {
123 self.options.product_type = product_type.into();
124 self
125 }
126
127 /// Sets the default market type for trading.
128 ///
129 /// This determines which product type to use for API calls.
130 /// Bitget uses product_type-based filtering:
131 /// - `Spot` -> product_type=spot
132 /// - `Swap` + Linear -> product_type=umcbl (USDT-M)
133 /// - `Swap` + Inverse -> product_type=dmcbl (Coin-M)
134 /// - `Futures` + Linear -> product_type=umcbl (USDT-M futures)
135 /// - `Futures` + Inverse -> product_type=dmcbl (Coin-M futures)
136 ///
137 /// # Arguments
138 ///
139 /// * `default_type` - The default market type (spot, swap, futures, margin, option).
140 ///
141 /// # Example
142 ///
143 /// ```no_run
144 /// use ccxt_exchanges::bitget::BitgetBuilder;
145 /// use ccxt_core::types::default_type::DefaultType;
146 ///
147 /// let bitget = BitgetBuilder::new()
148 /// .default_type(DefaultType::Swap)
149 /// .build()
150 /// .unwrap();
151 /// ```
152 pub fn default_type(mut self, default_type: impl Into<DefaultType>) -> Self {
153 self.options.default_type = default_type.into();
154 self
155 }
156
157 /// Sets the default sub-type for contract settlement.
158 ///
159 /// - `Linear`: USDT-margined contracts (product_type=umcbl)
160 /// - `Inverse`: Coin-margined contracts (product_type=dmcbl)
161 ///
162 /// Only applicable when `default_type` is `Swap` or `Futures`.
163 /// Ignored for `Spot` and `Option` types.
164 ///
165 /// # Arguments
166 ///
167 /// * `sub_type` - The contract settlement type (linear or inverse).
168 ///
169 /// # Example
170 ///
171 /// ```no_run
172 /// use ccxt_exchanges::bitget::BitgetBuilder;
173 /// use ccxt_core::types::default_type::{DefaultType, DefaultSubType};
174 ///
175 /// let bitget = BitgetBuilder::new()
176 /// .default_type(DefaultType::Swap)
177 /// .default_sub_type(DefaultSubType::Linear)
178 /// .build()
179 /// .unwrap();
180 /// ```
181 pub fn default_sub_type(mut self, sub_type: DefaultSubType) -> Self {
182 self.options.default_sub_type = Some(sub_type);
183 self
184 }
185
186 /// Sets the request timeout in seconds.
187 ///
188 /// # Arguments
189 ///
190 /// * `seconds` - Timeout duration in seconds.
191 pub fn timeout(mut self, seconds: u64) -> Self {
192 self.config.timeout = seconds;
193 self
194 }
195
196 /// Sets the receive window for signed requests.
197 ///
198 /// The receive window specifies how long a request is valid after
199 /// the timestamp. Default is 5000 milliseconds.
200 ///
201 /// # Arguments
202 ///
203 /// * `millis` - Receive window in milliseconds.
204 pub fn recv_window(mut self, millis: u64) -> Self {
205 self.options.recv_window = millis;
206 self
207 }
208
209 /// Enables or disables rate limiting.
210 ///
211 /// # Arguments
212 ///
213 /// * `enabled` - Whether to enable rate limiting.
214 pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
215 self.config.enable_rate_limit = enabled;
216 self
217 }
218
219 /// Sets the HTTP proxy server URL.
220 ///
221 /// # Arguments
222 ///
223 /// * `proxy` - The proxy server URL.
224 pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
225 self.config.proxy = Some(proxy.into());
226 self
227 }
228
229 /// Enables or disables verbose logging.
230 ///
231 /// # Arguments
232 ///
233 /// * `enabled` - Whether to enable verbose logging.
234 pub fn verbose(mut self, enabled: bool) -> Self {
235 self.config.verbose = enabled;
236 self
237 }
238
239 /// Sets a custom option.
240 ///
241 /// # Arguments
242 ///
243 /// * `key` - Option key.
244 /// * `value` - Option value as JSON.
245 pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
246 self.config.options.insert(key.into(), value);
247 self
248 }
249
250 /// Sets multiple custom options.
251 ///
252 /// # Arguments
253 ///
254 /// * `options` - HashMap of option key-value pairs.
255 pub fn options(mut self, options: HashMap<String, Value>) -> Self {
256 self.config.options.extend(options);
257 self
258 }
259
260 /// Returns the current configuration (for testing purposes).
261 #[cfg(test)]
262 pub fn get_config(&self) -> &ExchangeConfig {
263 &self.config
264 }
265
266 /// Returns the current options (for testing purposes).
267 #[cfg(test)]
268 pub fn get_options(&self) -> &BitgetOptions {
269 &self.options
270 }
271
272 /// Builds the Bitget exchange instance.
273 ///
274 /// # Returns
275 ///
276 /// Returns a `Result` containing the configured `Bitget` instance.
277 ///
278 /// # Errors
279 ///
280 /// Returns an error if the exchange cannot be initialized.
281 pub fn build(self) -> Result<Bitget> {
282 Bitget::new_with_options(self.config, self.options)
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_builder_default() {
292 let builder = BitgetBuilder::new();
293 assert_eq!(builder.config.id, "bitget");
294 assert_eq!(builder.config.name, "Bitget");
295 assert!(!builder.config.sandbox);
296 assert_eq!(builder.options.product_type, "spot");
297 }
298
299 #[test]
300 fn test_builder_api_key() {
301 let builder = BitgetBuilder::new().api_key("test-key");
302 assert_eq!(builder.config.api_key, Some("test-key".to_string()));
303 }
304
305 #[test]
306 fn test_builder_secret() {
307 let builder = BitgetBuilder::new().secret("test-secret");
308 assert_eq!(builder.config.secret, Some("test-secret".to_string()));
309 }
310
311 #[test]
312 fn test_builder_passphrase() {
313 let builder = BitgetBuilder::new().passphrase("test-passphrase");
314 assert_eq!(builder.config.password, Some("test-passphrase".to_string()));
315 }
316
317 #[test]
318 fn test_builder_sandbox() {
319 let builder = BitgetBuilder::new().sandbox(true);
320 assert!(builder.config.sandbox);
321 assert!(builder.options.testnet);
322 }
323
324 #[test]
325 fn test_builder_product_type() {
326 let builder = BitgetBuilder::new().product_type("umcbl");
327 assert_eq!(builder.options.product_type, "umcbl");
328 }
329
330 #[test]
331 fn test_builder_default_type() {
332 let builder = BitgetBuilder::new().default_type(DefaultType::Swap);
333 assert_eq!(builder.options.default_type, DefaultType::Swap);
334 }
335
336 #[test]
337 fn test_builder_default_type_from_string() {
338 let builder = BitgetBuilder::new().default_type("futures");
339 assert_eq!(builder.options.default_type, DefaultType::Futures);
340 }
341
342 #[test]
343 fn test_builder_default_sub_type() {
344 let builder = BitgetBuilder::new().default_sub_type(DefaultSubType::Inverse);
345 assert_eq!(
346 builder.options.default_sub_type,
347 Some(DefaultSubType::Inverse)
348 );
349 }
350
351 #[test]
352 fn test_builder_default_type_and_sub_type() {
353 let builder = BitgetBuilder::new()
354 .default_type(DefaultType::Swap)
355 .default_sub_type(DefaultSubType::Linear);
356 assert_eq!(builder.options.default_type, DefaultType::Swap);
357 assert_eq!(
358 builder.options.default_sub_type,
359 Some(DefaultSubType::Linear)
360 );
361 }
362
363 #[test]
364 fn test_builder_timeout() {
365 let builder = BitgetBuilder::new().timeout(60);
366 assert_eq!(builder.config.timeout, 60);
367 }
368
369 #[test]
370 fn test_builder_recv_window() {
371 let builder = BitgetBuilder::new().recv_window(10000);
372 assert_eq!(builder.options.recv_window, 10000);
373 }
374
375 #[test]
376 fn test_builder_chaining() {
377 let builder = BitgetBuilder::new()
378 .api_key("key")
379 .secret("secret")
380 .passphrase("pass")
381 .sandbox(true)
382 .timeout(30)
383 .recv_window(5000)
384 .product_type("spot")
385 .default_type(DefaultType::Swap)
386 .default_sub_type(DefaultSubType::Linear);
387
388 assert_eq!(builder.config.api_key, Some("key".to_string()));
389 assert_eq!(builder.config.secret, Some("secret".to_string()));
390 assert_eq!(builder.config.password, Some("pass".to_string()));
391 assert!(builder.config.sandbox);
392 assert_eq!(builder.config.timeout, 30);
393 assert_eq!(builder.options.recv_window, 5000);
394 assert_eq!(builder.options.product_type, "spot");
395 assert_eq!(builder.options.default_type, DefaultType::Swap);
396 assert_eq!(
397 builder.options.default_sub_type,
398 Some(DefaultSubType::Linear)
399 );
400 }
401
402 #[test]
403 fn test_builder_build() {
404 let result = BitgetBuilder::new().build();
405 assert!(result.is_ok());
406
407 let bitget = result.unwrap();
408 assert_eq!(bitget.id(), "bitget");
409 assert_eq!(bitget.name(), "Bitget");
410 }
411
412 #[test]
413 fn test_builder_build_with_credentials() {
414 let result = BitgetBuilder::new()
415 .api_key("test-key")
416 .secret("test-secret")
417 .passphrase("test-passphrase")
418 .build();
419
420 assert!(result.is_ok());
421 }
422}