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::{ExchangeConfig, Result};
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// Builder for creating Bitget exchange instances.
12///
13/// Provides a fluent API for configuring all aspects of the Bitget exchange,
14/// including authentication, connection settings, and Bitget-specific options.
15///
16/// # Example
17///
18/// ```no_run
19/// use ccxt_exchanges::bitget::BitgetBuilder;
20///
21/// let bitget = BitgetBuilder::new()
22///     .api_key("your-api-key")
23///     .secret("your-secret")
24///     .passphrase("your-passphrase")
25///     .sandbox(true)
26///     .timeout(30)
27///     .build()
28///     .unwrap();
29/// ```
30#[derive(Debug, Clone)]
31pub struct BitgetBuilder {
32    /// Exchange configuration
33    config: ExchangeConfig,
34    /// Bitget-specific options
35    options: BitgetOptions,
36}
37
38impl Default for BitgetBuilder {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl BitgetBuilder {
45    /// Creates a new builder with default configuration.
46    ///
47    /// # Example
48    ///
49    /// ```no_run
50    /// use ccxt_exchanges::bitget::BitgetBuilder;
51    ///
52    /// let builder = BitgetBuilder::new();
53    /// ```
54    pub fn new() -> Self {
55        Self {
56            config: ExchangeConfig {
57                id: "bitget".to_string(),
58                name: "Bitget".to_string(),
59                ..Default::default()
60            },
61            options: BitgetOptions::default(),
62        }
63    }
64
65    /// Sets the API key for authentication.
66    ///
67    /// # Arguments
68    ///
69    /// * `key` - The API key string.
70    pub fn api_key(mut self, key: impl Into<String>) -> Self {
71        self.config.api_key = Some(key.into());
72        self
73    }
74
75    /// Sets the API secret for authentication.
76    ///
77    /// # Arguments
78    ///
79    /// * `secret` - The API secret string.
80    pub fn secret(mut self, secret: impl Into<String>) -> Self {
81        self.config.secret = Some(secret.into());
82        self
83    }
84
85    /// Sets the passphrase for authentication.
86    ///
87    /// Bitget requires a passphrase in addition to API key and secret.
88    ///
89    /// # Arguments
90    ///
91    /// * `passphrase` - The passphrase string.
92    pub fn passphrase(mut self, passphrase: impl Into<String>) -> Self {
93        self.config.password = Some(passphrase.into());
94        self
95    }
96
97    /// Enables or disables sandbox/demo mode.
98    ///
99    /// When enabled, the exchange will connect to Bitget's demo
100    /// environment instead of the production environment.
101    ///
102    /// # Arguments
103    ///
104    /// * `enabled` - Whether to enable sandbox mode.
105    pub fn sandbox(mut self, enabled: bool) -> Self {
106        self.config.sandbox = enabled;
107        self.options.demo = enabled;
108        self
109    }
110
111    /// Sets the product type for trading.
112    ///
113    /// Valid values: "spot", "umcbl" (USDT-M futures), "dmcbl" (Coin-M futures).
114    ///
115    /// # Arguments
116    ///
117    /// * `product_type` - The product type string.
118    pub fn product_type(mut self, product_type: impl Into<String>) -> Self {
119        self.options.product_type = product_type.into();
120        self
121    }
122
123    /// Sets the request timeout in seconds.
124    ///
125    /// # Arguments
126    ///
127    /// * `seconds` - Timeout duration in seconds.
128    pub fn timeout(mut self, seconds: u64) -> Self {
129        self.config.timeout = seconds;
130        self
131    }
132
133    /// Sets the receive window for signed requests.
134    ///
135    /// The receive window specifies how long a request is valid after
136    /// the timestamp. Default is 5000 milliseconds.
137    ///
138    /// # Arguments
139    ///
140    /// * `millis` - Receive window in milliseconds.
141    pub fn recv_window(mut self, millis: u64) -> Self {
142        self.options.recv_window = millis;
143        self
144    }
145
146    /// Enables or disables rate limiting.
147    ///
148    /// # Arguments
149    ///
150    /// * `enabled` - Whether to enable rate limiting.
151    pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
152        self.config.enable_rate_limit = enabled;
153        self
154    }
155
156    /// Sets the HTTP proxy server URL.
157    ///
158    /// # Arguments
159    ///
160    /// * `proxy` - The proxy server URL.
161    pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
162        self.config.proxy = Some(proxy.into());
163        self
164    }
165
166    /// Enables or disables verbose logging.
167    ///
168    /// # Arguments
169    ///
170    /// * `enabled` - Whether to enable verbose logging.
171    pub fn verbose(mut self, enabled: bool) -> Self {
172        self.config.verbose = enabled;
173        self
174    }
175
176    /// Sets a custom option.
177    ///
178    /// # Arguments
179    ///
180    /// * `key` - Option key.
181    /// * `value` - Option value as JSON.
182    pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
183        self.config.options.insert(key.into(), value);
184        self
185    }
186
187    /// Sets multiple custom options.
188    ///
189    /// # Arguments
190    ///
191    /// * `options` - HashMap of option key-value pairs.
192    pub fn options(mut self, options: HashMap<String, Value>) -> Self {
193        self.config.options.extend(options);
194        self
195    }
196
197    /// Returns the current configuration (for testing purposes).
198    #[cfg(test)]
199    pub fn get_config(&self) -> &ExchangeConfig {
200        &self.config
201    }
202
203    /// Returns the current options (for testing purposes).
204    #[cfg(test)]
205    pub fn get_options(&self) -> &BitgetOptions {
206        &self.options
207    }
208
209    /// Builds the Bitget exchange instance.
210    ///
211    /// # Returns
212    ///
213    /// Returns a `Result` containing the configured `Bitget` instance.
214    ///
215    /// # Errors
216    ///
217    /// Returns an error if the exchange cannot be initialized.
218    pub fn build(self) -> Result<Bitget> {
219        Bitget::new_with_options(self.config, self.options)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_builder_default() {
229        let builder = BitgetBuilder::new();
230        assert_eq!(builder.config.id, "bitget");
231        assert_eq!(builder.config.name, "Bitget");
232        assert!(!builder.config.sandbox);
233        assert_eq!(builder.options.product_type, "spot");
234    }
235
236    #[test]
237    fn test_builder_api_key() {
238        let builder = BitgetBuilder::new().api_key("test-key");
239        assert_eq!(builder.config.api_key, Some("test-key".to_string()));
240    }
241
242    #[test]
243    fn test_builder_secret() {
244        let builder = BitgetBuilder::new().secret("test-secret");
245        assert_eq!(builder.config.secret, Some("test-secret".to_string()));
246    }
247
248    #[test]
249    fn test_builder_passphrase() {
250        let builder = BitgetBuilder::new().passphrase("test-passphrase");
251        assert_eq!(builder.config.password, Some("test-passphrase".to_string()));
252    }
253
254    #[test]
255    fn test_builder_sandbox() {
256        let builder = BitgetBuilder::new().sandbox(true);
257        assert!(builder.config.sandbox);
258        assert!(builder.options.demo);
259    }
260
261    #[test]
262    fn test_builder_product_type() {
263        let builder = BitgetBuilder::new().product_type("umcbl");
264        assert_eq!(builder.options.product_type, "umcbl");
265    }
266
267    #[test]
268    fn test_builder_timeout() {
269        let builder = BitgetBuilder::new().timeout(60);
270        assert_eq!(builder.config.timeout, 60);
271    }
272
273    #[test]
274    fn test_builder_recv_window() {
275        let builder = BitgetBuilder::new().recv_window(10000);
276        assert_eq!(builder.options.recv_window, 10000);
277    }
278
279    #[test]
280    fn test_builder_chaining() {
281        let builder = BitgetBuilder::new()
282            .api_key("key")
283            .secret("secret")
284            .passphrase("pass")
285            .sandbox(true)
286            .timeout(30)
287            .recv_window(5000)
288            .product_type("spot");
289
290        assert_eq!(builder.config.api_key, Some("key".to_string()));
291        assert_eq!(builder.config.secret, Some("secret".to_string()));
292        assert_eq!(builder.config.password, Some("pass".to_string()));
293        assert!(builder.config.sandbox);
294        assert_eq!(builder.config.timeout, 30);
295        assert_eq!(builder.options.recv_window, 5000);
296        assert_eq!(builder.options.product_type, "spot");
297    }
298
299    #[test]
300    fn test_builder_build() {
301        let result = BitgetBuilder::new().build();
302        assert!(result.is_ok());
303
304        let bitget = result.unwrap();
305        assert_eq!(bitget.id(), "bitget");
306        assert_eq!(bitget.name(), "Bitget");
307    }
308
309    #[test]
310    fn test_builder_build_with_credentials() {
311        let result = BitgetBuilder::new()
312            .api_key("test-key")
313            .secret("test-secret")
314            .passphrase("test-passphrase")
315            .build();
316
317        assert!(result.is_ok());
318    }
319}