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::types::default_type::{DefaultSubType, DefaultType};
8use ccxt_core::{ExchangeConfig, Result};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// Builder for creating OKX exchange instances.
13///
14/// Provides a fluent API for configuring all aspects of the OKX exchange,
15/// including authentication, connection settings, and OKX-specific options.
16///
17/// # Example
18///
19/// ```no_run
20/// use ccxt_exchanges::okx::OkxBuilder;
21///
22/// let okx = OkxBuilder::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 OkxBuilder {
33    /// Exchange configuration
34    config: ExchangeConfig,
35    /// OKX-specific options
36    options: OkxOptions,
37}
38
39impl Default for OkxBuilder {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl OkxBuilder {
46    /// Creates a new builder with default configuration.
47    ///
48    /// # Example
49    ///
50    /// ```no_run
51    /// use ccxt_exchanges::okx::OkxBuilder;
52    ///
53    /// let builder = OkxBuilder::new();
54    /// ```
55    pub fn new() -> Self {
56        Self {
57            config: ExchangeConfig {
58                id: "okx".to_string(),
59                name: "OKX".to_string(),
60                ..Default::default()
61            },
62            options: OkxOptions::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    /// OKX 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 OKX'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 account mode for trading.
113    ///
114    /// Valid values: "cash" (spot), "cross" (cross margin), "isolated" (isolated margin).
115    ///
116    /// # Arguments
117    ///
118    /// * `mode` - The account mode string.
119    pub fn account_mode(mut self, mode: impl Into<String>) -> Self {
120        self.options.account_mode = mode.into();
121        self
122    }
123
124    /// Sets the default market type for trading.
125    ///
126    /// This determines which instrument type (instType) to use for API calls.
127    /// OKX uses a unified V5 API, so this primarily affects market filtering.
128    ///
129    /// # Arguments
130    ///
131    /// * `default_type` - The default market type (spot, swap, futures, margin, option).
132    ///
133    /// # Example
134    ///
135    /// ```no_run
136    /// use ccxt_exchanges::okx::OkxBuilder;
137    /// use ccxt_core::types::default_type::DefaultType;
138    ///
139    /// let okx = OkxBuilder::new()
140    ///     .default_type(DefaultType::Swap)
141    ///     .build()
142    ///     .unwrap();
143    /// ```
144    pub fn default_type(mut self, default_type: impl Into<DefaultType>) -> Self {
145        self.options.default_type = default_type.into();
146        self
147    }
148
149    /// Sets the default sub-type for contract settlement.
150    ///
151    /// - `Linear`: USDT-margined contracts
152    /// - `Inverse`: Coin-margined contracts
153    ///
154    /// Only applicable when `default_type` is `Swap`, `Futures`, or `Option`.
155    /// Ignored for `Spot` and `Margin` types.
156    ///
157    /// # Arguments
158    ///
159    /// * `sub_type` - The contract settlement type (linear or inverse).
160    ///
161    /// # Example
162    ///
163    /// ```no_run
164    /// use ccxt_exchanges::okx::OkxBuilder;
165    /// use ccxt_core::types::default_type::{DefaultType, DefaultSubType};
166    ///
167    /// let okx = OkxBuilder::new()
168    ///     .default_type(DefaultType::Swap)
169    ///     .default_sub_type(DefaultSubType::Linear)
170    ///     .build()
171    ///     .unwrap();
172    /// ```
173    pub fn default_sub_type(mut self, sub_type: DefaultSubType) -> Self {
174        self.options.default_sub_type = Some(sub_type);
175        self
176    }
177
178    /// Sets the request timeout in seconds.
179    ///
180    /// # Arguments
181    ///
182    /// * `seconds` - Timeout duration in seconds.
183    pub fn timeout(mut self, seconds: u64) -> Self {
184        self.config.timeout = seconds;
185        self
186    }
187
188    /// Enables or disables rate limiting.
189    ///
190    /// # Arguments
191    ///
192    /// * `enabled` - Whether to enable rate limiting.
193    pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
194        self.config.enable_rate_limit = enabled;
195        self
196    }
197
198    /// Sets the HTTP proxy server URL.
199    ///
200    /// # Arguments
201    ///
202    /// * `proxy` - The proxy server URL.
203    pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
204        self.config.proxy = Some(proxy.into());
205        self
206    }
207
208    /// Enables or disables verbose logging.
209    ///
210    /// # Arguments
211    ///
212    /// * `enabled` - Whether to enable verbose logging.
213    pub fn verbose(mut self, enabled: bool) -> Self {
214        self.config.verbose = enabled;
215        self
216    }
217
218    /// Sets a custom option.
219    ///
220    /// # Arguments
221    ///
222    /// * `key` - Option key.
223    /// * `value` - Option value as JSON.
224    pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
225        self.config.options.insert(key.into(), value);
226        self
227    }
228
229    /// Sets multiple custom options.
230    ///
231    /// # Arguments
232    ///
233    /// * `options` - HashMap of option key-value pairs.
234    pub fn options(mut self, options: HashMap<String, Value>) -> Self {
235        self.config.options.extend(options);
236        self
237    }
238
239    /// Returns the current configuration (for testing purposes).
240    #[cfg(test)]
241    pub fn get_config(&self) -> &ExchangeConfig {
242        &self.config
243    }
244
245    /// Returns the current options (for testing purposes).
246    #[cfg(test)]
247    pub fn get_options(&self) -> &OkxOptions {
248        &self.options
249    }
250
251    /// Builds the OKX exchange instance.
252    ///
253    /// # Returns
254    ///
255    /// Returns a `Result` containing the configured `Okx` instance.
256    ///
257    /// # Errors
258    ///
259    /// Returns an error if the exchange cannot be initialized.
260    pub fn build(self) -> Result<Okx> {
261        Okx::new_with_options(self.config, self.options)
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_builder_default() {
271        let builder = OkxBuilder::new();
272        assert_eq!(builder.config.id, "okx");
273        assert_eq!(builder.config.name, "OKX");
274        assert!(!builder.config.sandbox);
275        assert_eq!(builder.options.account_mode, "cash");
276    }
277
278    #[test]
279    fn test_builder_api_key() {
280        let builder = OkxBuilder::new().api_key("test-key");
281        assert_eq!(builder.config.api_key, Some("test-key".to_string()));
282    }
283
284    #[test]
285    fn test_builder_secret() {
286        let builder = OkxBuilder::new().secret("test-secret");
287        assert_eq!(builder.config.secret, Some("test-secret".to_string()));
288    }
289
290    #[test]
291    fn test_builder_passphrase() {
292        let builder = OkxBuilder::new().passphrase("test-passphrase");
293        assert_eq!(builder.config.password, Some("test-passphrase".to_string()));
294    }
295
296    #[test]
297    fn test_builder_sandbox() {
298        let builder = OkxBuilder::new().sandbox(true);
299        assert!(builder.config.sandbox);
300        assert!(builder.options.testnet);
301    }
302
303    #[test]
304    fn test_builder_account_mode() {
305        let builder = OkxBuilder::new().account_mode("cross");
306        assert_eq!(builder.options.account_mode, "cross");
307    }
308
309    #[test]
310    fn test_builder_default_type() {
311        let builder = OkxBuilder::new().default_type(DefaultType::Swap);
312        assert_eq!(builder.options.default_type, DefaultType::Swap);
313    }
314
315    #[test]
316    fn test_builder_default_type_from_string() {
317        let builder = OkxBuilder::new().default_type("futures");
318        assert_eq!(builder.options.default_type, DefaultType::Futures);
319    }
320
321    #[test]
322    fn test_builder_default_sub_type() {
323        let builder = OkxBuilder::new().default_sub_type(DefaultSubType::Inverse);
324        assert_eq!(
325            builder.options.default_sub_type,
326            Some(DefaultSubType::Inverse)
327        );
328    }
329
330    #[test]
331    fn test_builder_default_type_and_sub_type() {
332        let builder = OkxBuilder::new()
333            .default_type(DefaultType::Swap)
334            .default_sub_type(DefaultSubType::Linear);
335        assert_eq!(builder.options.default_type, DefaultType::Swap);
336        assert_eq!(
337            builder.options.default_sub_type,
338            Some(DefaultSubType::Linear)
339        );
340    }
341
342    #[test]
343    fn test_builder_timeout() {
344        let builder = OkxBuilder::new().timeout(60);
345        assert_eq!(builder.config.timeout, 60);
346    }
347
348    #[test]
349    fn test_builder_chaining() {
350        let builder = OkxBuilder::new()
351            .api_key("key")
352            .secret("secret")
353            .passphrase("pass")
354            .sandbox(true)
355            .timeout(30)
356            .account_mode("isolated");
357
358        assert_eq!(builder.config.api_key, Some("key".to_string()));
359        assert_eq!(builder.config.secret, Some("secret".to_string()));
360        assert_eq!(builder.config.password, Some("pass".to_string()));
361        assert!(builder.config.sandbox);
362        assert_eq!(builder.config.timeout, 30);
363        assert_eq!(builder.options.account_mode, "isolated");
364    }
365
366    #[test]
367    fn test_builder_build() {
368        let result = OkxBuilder::new().build();
369        assert!(result.is_ok());
370
371        let okx = result.unwrap();
372        assert_eq!(okx.id(), "okx");
373        assert_eq!(okx.name(), "OKX");
374    }
375
376    #[test]
377    fn test_builder_build_with_credentials() {
378        let result = OkxBuilder::new()
379            .api_key("test-key")
380            .secret("test-secret")
381            .passphrase("test-passphrase")
382            .build();
383
384        assert!(result.is_ok());
385    }
386
387    #[test]
388    fn test_builder_enable_rate_limit() {
389        let builder = OkxBuilder::new().enable_rate_limit(false);
390        assert!(!builder.config.enable_rate_limit);
391    }
392
393    #[test]
394    fn test_builder_proxy() {
395        let builder = OkxBuilder::new().proxy("http://proxy.example.com:8080");
396        assert_eq!(
397            builder.config.proxy,
398            Some("http://proxy.example.com:8080".to_string())
399        );
400    }
401
402    #[test]
403    fn test_builder_verbose() {
404        let builder = OkxBuilder::new().verbose(true);
405        assert!(builder.config.verbose);
406    }
407}