ccxt_exchanges/okx/
builder.rs

1//! OKX exchange builder pattern implementation.
2//!
3//! Provides a fluent API for constructing OKX exchange instances with
4//! type-safe configuration options.
5
6use super::{Okx, OkxOptions};
7use ccxt_core::{ExchangeConfig, Result};
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// Builder for creating OKX exchange instances.
12///
13/// Provides a fluent API for configuring all aspects of the OKX exchange,
14/// including authentication, connection settings, and OKX-specific options.
15///
16/// # Example
17///
18/// ```no_run
19/// use ccxt_exchanges::okx::OkxBuilder;
20///
21/// let okx = OkxBuilder::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 OkxBuilder {
32    /// Exchange configuration
33    config: ExchangeConfig,
34    /// OKX-specific options
35    options: OkxOptions,
36}
37
38impl Default for OkxBuilder {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl OkxBuilder {
45    /// Creates a new builder with default configuration.
46    ///
47    /// # Example
48    ///
49    /// ```no_run
50    /// use ccxt_exchanges::okx::OkxBuilder;
51    ///
52    /// let builder = OkxBuilder::new();
53    /// ```
54    pub fn new() -> Self {
55        Self {
56            config: ExchangeConfig {
57                id: "okx".to_string(),
58                name: "OKX".to_string(),
59                ..Default::default()
60            },
61            options: OkxOptions::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    /// OKX 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 OKX'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 account mode for trading.
112    ///
113    /// Valid values: "cash" (spot), "cross" (cross margin), "isolated" (isolated margin).
114    ///
115    /// # Arguments
116    ///
117    /// * `mode` - The account mode string.
118    pub fn account_mode(mut self, mode: impl Into<String>) -> Self {
119        self.options.account_mode = mode.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    /// Enables or disables rate limiting.
134    ///
135    /// # Arguments
136    ///
137    /// * `enabled` - Whether to enable rate limiting.
138    pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
139        self.config.enable_rate_limit = enabled;
140        self
141    }
142
143    /// Sets the HTTP proxy server URL.
144    ///
145    /// # Arguments
146    ///
147    /// * `proxy` - The proxy server URL.
148    pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
149        self.config.proxy = Some(proxy.into());
150        self
151    }
152
153    /// Enables or disables verbose logging.
154    ///
155    /// # Arguments
156    ///
157    /// * `enabled` - Whether to enable verbose logging.
158    pub fn verbose(mut self, enabled: bool) -> Self {
159        self.config.verbose = enabled;
160        self
161    }
162
163    /// Sets a custom option.
164    ///
165    /// # Arguments
166    ///
167    /// * `key` - Option key.
168    /// * `value` - Option value as JSON.
169    pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
170        self.config.options.insert(key.into(), value);
171        self
172    }
173
174    /// Sets multiple custom options.
175    ///
176    /// # Arguments
177    ///
178    /// * `options` - HashMap of option key-value pairs.
179    pub fn options(mut self, options: HashMap<String, Value>) -> Self {
180        self.config.options.extend(options);
181        self
182    }
183
184    /// Returns the current configuration (for testing purposes).
185    #[cfg(test)]
186    pub fn get_config(&self) -> &ExchangeConfig {
187        &self.config
188    }
189
190    /// Returns the current options (for testing purposes).
191    #[cfg(test)]
192    pub fn get_options(&self) -> &OkxOptions {
193        &self.options
194    }
195
196    /// Builds the OKX exchange instance.
197    ///
198    /// # Returns
199    ///
200    /// Returns a `Result` containing the configured `Okx` instance.
201    ///
202    /// # Errors
203    ///
204    /// Returns an error if the exchange cannot be initialized.
205    pub fn build(self) -> Result<Okx> {
206        Okx::new_with_options(self.config, self.options)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_builder_default() {
216        let builder = OkxBuilder::new();
217        assert_eq!(builder.config.id, "okx");
218        assert_eq!(builder.config.name, "OKX");
219        assert!(!builder.config.sandbox);
220        assert_eq!(builder.options.account_mode, "cash");
221    }
222
223    #[test]
224    fn test_builder_api_key() {
225        let builder = OkxBuilder::new().api_key("test-key");
226        assert_eq!(builder.config.api_key, Some("test-key".to_string()));
227    }
228
229    #[test]
230    fn test_builder_secret() {
231        let builder = OkxBuilder::new().secret("test-secret");
232        assert_eq!(builder.config.secret, Some("test-secret".to_string()));
233    }
234
235    #[test]
236    fn test_builder_passphrase() {
237        let builder = OkxBuilder::new().passphrase("test-passphrase");
238        assert_eq!(builder.config.password, Some("test-passphrase".to_string()));
239    }
240
241    #[test]
242    fn test_builder_sandbox() {
243        let builder = OkxBuilder::new().sandbox(true);
244        assert!(builder.config.sandbox);
245        assert!(builder.options.demo);
246    }
247
248    #[test]
249    fn test_builder_account_mode() {
250        let builder = OkxBuilder::new().account_mode("cross");
251        assert_eq!(builder.options.account_mode, "cross");
252    }
253
254    #[test]
255    fn test_builder_timeout() {
256        let builder = OkxBuilder::new().timeout(60);
257        assert_eq!(builder.config.timeout, 60);
258    }
259
260    #[test]
261    fn test_builder_chaining() {
262        let builder = OkxBuilder::new()
263            .api_key("key")
264            .secret("secret")
265            .passphrase("pass")
266            .sandbox(true)
267            .timeout(30)
268            .account_mode("isolated");
269
270        assert_eq!(builder.config.api_key, Some("key".to_string()));
271        assert_eq!(builder.config.secret, Some("secret".to_string()));
272        assert_eq!(builder.config.password, Some("pass".to_string()));
273        assert!(builder.config.sandbox);
274        assert_eq!(builder.config.timeout, 30);
275        assert_eq!(builder.options.account_mode, "isolated");
276    }
277
278    #[test]
279    fn test_builder_build() {
280        let result = OkxBuilder::new().build();
281        assert!(result.is_ok());
282
283        let okx = result.unwrap();
284        assert_eq!(okx.id(), "okx");
285        assert_eq!(okx.name(), "OKX");
286    }
287
288    #[test]
289    fn test_builder_build_with_credentials() {
290        let result = OkxBuilder::new()
291            .api_key("test-key")
292            .secret("test-secret")
293            .passphrase("test-passphrase")
294            .build();
295
296        assert!(result.is_ok());
297    }
298
299    #[test]
300    fn test_builder_enable_rate_limit() {
301        let builder = OkxBuilder::new().enable_rate_limit(false);
302        assert!(!builder.config.enable_rate_limit);
303    }
304
305    #[test]
306    fn test_builder_proxy() {
307        let builder = OkxBuilder::new().proxy("http://proxy.example.com:8080");
308        assert_eq!(
309            builder.config.proxy,
310            Some("http://proxy.example.com:8080".to_string())
311        );
312    }
313
314    #[test]
315    fn test_builder_verbose() {
316        let builder = OkxBuilder::new().verbose(true);
317        assert!(builder.config.verbose);
318    }
319}