ccxt_exchanges/bybit/
error.rs

1//! Bybit-specific error handling.
2//!
3//! This module provides error parsing for Bybit API responses,
4//! mapping Bybit error codes to ccxt-core Error types.
5
6use ccxt_core::error::Error;
7use serde_json::Value;
8use std::time::Duration;
9
10/// Bybit error codes and their meanings.
11///
12/// Reference: https://bybit-exchange.github.io/docs/v5/error
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum BybitErrorCode {
15    /// Invalid request (10001)
16    InvalidRequest,
17    /// Invalid API key (10003)
18    InvalidApiKey,
19    /// Invalid signature (10004)
20    InvalidSignature,
21    /// Permission denied (10005)
22    PermissionDenied,
23    /// Rate limit exceeded (10006)
24    RateLimitExceeded,
25    /// IP not allowed (10010)
26    IpNotAllowed,
27    /// Bad symbol / Invalid symbol (10016)
28    BadSymbol,
29    /// Insufficient funds (110001)
30    InsufficientFunds,
31    /// Insufficient available balance (110007)
32    InsufficientAvailableBalance,
33    /// Order not found (110008)
34    OrderNotFound,
35    /// Unknown error code
36    Unknown(i64),
37}
38
39impl BybitErrorCode {
40    /// Parses a Bybit error code into a BybitErrorCode.
41    pub fn from_code(code: i64) -> Self {
42        match code {
43            10001 => BybitErrorCode::InvalidRequest,
44            10003 => BybitErrorCode::InvalidApiKey,
45            10004 => BybitErrorCode::InvalidSignature,
46            10005 => BybitErrorCode::PermissionDenied,
47            10006 => BybitErrorCode::RateLimitExceeded,
48            10010 => BybitErrorCode::IpNotAllowed,
49            10016 => BybitErrorCode::BadSymbol,
50            110001 => BybitErrorCode::InsufficientFunds,
51            110007 => BybitErrorCode::InsufficientAvailableBalance,
52            110008 => BybitErrorCode::OrderNotFound,
53            n => BybitErrorCode::Unknown(n),
54        }
55    }
56
57    /// Returns the numeric code for this error.
58    pub fn code(&self) -> i64 {
59        match self {
60            BybitErrorCode::InvalidRequest => 10001,
61            BybitErrorCode::InvalidApiKey => 10003,
62            BybitErrorCode::InvalidSignature => 10004,
63            BybitErrorCode::PermissionDenied => 10005,
64            BybitErrorCode::RateLimitExceeded => 10006,
65            BybitErrorCode::IpNotAllowed => 10010,
66            BybitErrorCode::BadSymbol => 10016,
67            BybitErrorCode::InsufficientFunds => 110001,
68            BybitErrorCode::InsufficientAvailableBalance => 110007,
69            BybitErrorCode::OrderNotFound => 110008,
70            BybitErrorCode::Unknown(n) => *n,
71        }
72    }
73}
74
75/// Parses a Bybit API error response and converts it to a ccxt-core Error.
76///
77/// Bybit API responses follow this format:
78/// ```json
79/// {
80///     "retCode": 10003,
81///     "retMsg": "Invalid API key",
82///     "result": {},
83///     "time": 1234567890123
84/// }
85/// ```
86///
87/// # Arguments
88///
89/// * `response` - The JSON response from Bybit API
90///
91/// # Returns
92///
93/// A ccxt-core Error mapped from the Bybit error code.
94///
95/// # Example
96///
97/// ```rust
98/// use ccxt_exchanges::bybit::error::parse_error;
99/// use serde_json::json;
100///
101/// let response = json!({
102///     "retCode": 10003,
103///     "retMsg": "Invalid API key"
104/// });
105///
106/// let error = parse_error(&response);
107/// assert!(error.as_authentication().is_some());
108/// ```
109pub fn parse_error(response: &Value) -> Error {
110    let code = response
111        .get("retCode")
112        .and_then(serde_json::Value::as_i64)
113        .unwrap_or(0);
114
115    let msg = response
116        .get("retMsg")
117        .and_then(serde_json::Value::as_str)
118        .unwrap_or("Unknown error");
119
120    let error_code = BybitErrorCode::from_code(code);
121
122    match error_code {
123        BybitErrorCode::InvalidApiKey
124        | BybitErrorCode::InvalidSignature
125        | BybitErrorCode::PermissionDenied
126        | BybitErrorCode::IpNotAllowed => Error::authentication(msg.to_string()),
127        BybitErrorCode::RateLimitExceeded => {
128            // Bybit typically suggests waiting 1 second on rate limit
129            Error::rate_limit(msg.to_string(), Some(Duration::from_secs(1)))
130        }
131        BybitErrorCode::InvalidRequest => Error::invalid_request(msg.to_string()),
132        BybitErrorCode::InsufficientFunds | BybitErrorCode::InsufficientAvailableBalance => {
133            Error::insufficient_balance(msg.to_string())
134        }
135        BybitErrorCode::BadSymbol => Error::bad_symbol(msg.to_string()),
136        BybitErrorCode::OrderNotFound | BybitErrorCode::Unknown(_) => {
137            Error::exchange(code.to_string(), msg)
138        }
139    }
140}
141
142/// Checks if a Bybit API response indicates an error.
143///
144/// Bybit uses 0 as the success code. Any other code indicates an error.
145///
146/// # Arguments
147///
148/// * `response` - The JSON response from Bybit API
149///
150/// # Returns
151///
152/// `true` if the response indicates an error, `false` otherwise.
153pub fn is_error_response(response: &Value) -> bool {
154    response.get("retCode").and_then(serde_json::Value::as_i64) != Some(0)
155}
156
157/// Extracts the error code from a Bybit API response.
158///
159/// # Arguments
160///
161/// * `response` - The JSON response from Bybit API
162///
163/// # Returns
164///
165/// The error code as an i64, or 0 if not found.
166pub fn extract_error_code(response: &Value) -> i64 {
167    response
168        .get("retCode")
169        .and_then(serde_json::Value::as_i64)
170        .unwrap_or(0)
171}
172
173/// Extracts the error message from a Bybit API response.
174///
175/// # Arguments
176///
177/// * `response` - The JSON response from Bybit API
178///
179/// # Returns
180///
181/// The error message as a string, or "Unknown error" if not found.
182pub fn extract_error_message(response: &Value) -> &str {
183    response
184        .get("retMsg")
185        .and_then(serde_json::Value::as_str)
186        .unwrap_or("Unknown error")
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use serde_json::json;
193
194    #[test]
195    fn test_parse_authentication_error_invalid_api_key() {
196        let response = json!({
197            "retCode": 10003,
198            "retMsg": "Invalid API key"
199        });
200
201        let error = parse_error(&response);
202        assert!(error.as_authentication().is_some());
203        assert!(error.to_string().contains("Invalid API key"));
204    }
205
206    #[test]
207    fn test_parse_authentication_error_invalid_signature() {
208        let response = json!({
209            "retCode": 10004,
210            "retMsg": "Invalid signature"
211        });
212
213        let error = parse_error(&response);
214        assert!(error.as_authentication().is_some());
215        assert!(error.to_string().contains("Invalid signature"));
216    }
217
218    #[test]
219    fn test_parse_authentication_error_permission_denied() {
220        let response = json!({
221            "retCode": 10005,
222            "retMsg": "Permission denied"
223        });
224
225        let error = parse_error(&response);
226        assert!(error.as_authentication().is_some());
227        assert!(error.to_string().contains("Permission denied"));
228    }
229
230    #[test]
231    fn test_parse_authentication_error_ip_not_allowed() {
232        let response = json!({
233            "retCode": 10010,
234            "retMsg": "IP not allowed"
235        });
236
237        let error = parse_error(&response);
238        assert!(error.as_authentication().is_some());
239        assert!(error.to_string().contains("IP not allowed"));
240    }
241
242    #[test]
243    fn test_parse_rate_limit_error() {
244        let response = json!({
245            "retCode": 10006,
246            "retMsg": "Rate limit exceeded"
247        });
248
249        let error = parse_error(&response);
250        assert!(error.as_rate_limit().is_some());
251        let (msg, retry_after) = error.as_rate_limit().unwrap();
252        assert!(msg.contains("Rate limit"));
253        assert!(retry_after.is_some());
254    }
255
256    #[test]
257    fn test_parse_invalid_request_error() {
258        let response = json!({
259            "retCode": 10001,
260            "retMsg": "Invalid request parameters"
261        });
262
263        let error = parse_error(&response);
264        let display = error.to_string();
265        assert!(display.contains("Invalid request"));
266    }
267
268    #[test]
269    fn test_parse_insufficient_funds_error() {
270        let response = json!({
271            "retCode": 110001,
272            "retMsg": "Insufficient balance"
273        });
274
275        let error = parse_error(&response);
276        let display = error.to_string();
277        assert!(display.contains("Insufficient balance"));
278    }
279
280    #[test]
281    fn test_parse_insufficient_available_balance_error() {
282        let response = json!({
283            "retCode": 110007,
284            "retMsg": "Insufficient available balance"
285        });
286
287        let error = parse_error(&response);
288        let display = error.to_string();
289        assert!(display.contains("Insufficient"));
290    }
291
292    #[test]
293    fn test_parse_bad_symbol_error() {
294        let response = json!({
295            "retCode": 10016,
296            "retMsg": "Invalid symbol"
297        });
298
299        let error = parse_error(&response);
300        let display = error.to_string();
301        assert!(display.contains("Bad symbol"));
302    }
303
304    #[test]
305    fn test_parse_order_not_found_error() {
306        let response = json!({
307            "retCode": 110008,
308            "retMsg": "Order not found"
309        });
310
311        let error = parse_error(&response);
312        let display = error.to_string();
313        assert!(display.contains("Order not found"));
314    }
315
316    #[test]
317    fn test_parse_unknown_error() {
318        let response = json!({
319            "retCode": 99999,
320            "retMsg": "Some unknown error"
321        });
322
323        let error = parse_error(&response);
324        let display = error.to_string();
325        assert!(display.contains("Some unknown error"));
326    }
327
328    #[test]
329    fn test_parse_error_missing_code() {
330        let response = json!({
331            "retMsg": "Error without code"
332        });
333
334        let error = parse_error(&response);
335        let display = error.to_string();
336        assert!(display.contains("Error without code"));
337    }
338
339    #[test]
340    fn test_parse_error_missing_message() {
341        let response = json!({
342            "retCode": 10003
343        });
344
345        let error = parse_error(&response);
346        let display = error.to_string();
347        assert!(display.contains("Unknown error"));
348    }
349
350    #[test]
351    fn test_is_error_response_success() {
352        let response = json!({
353            "retCode": 0,
354            "retMsg": "OK",
355            "result": {}
356        });
357
358        assert!(!is_error_response(&response));
359    }
360
361    #[test]
362    fn test_is_error_response_error() {
363        let response = json!({
364            "retCode": 10003,
365            "retMsg": "Invalid API key"
366        });
367
368        assert!(is_error_response(&response));
369    }
370
371    #[test]
372    fn test_is_error_response_missing_code() {
373        let response = json!({
374            "retMsg": "No code field"
375        });
376
377        // Missing code should be treated as error
378        assert!(is_error_response(&response));
379    }
380
381    #[test]
382    fn test_extract_error_code() {
383        let response = json!({
384            "retCode": 10003,
385            "retMsg": "Invalid API key"
386        });
387
388        assert_eq!(extract_error_code(&response), 10003);
389    }
390
391    #[test]
392    fn test_extract_error_code_missing() {
393        let response = json!({
394            "retMsg": "No code"
395        });
396
397        assert_eq!(extract_error_code(&response), 0);
398    }
399
400    #[test]
401    fn test_extract_error_message() {
402        let response = json!({
403            "retCode": 10003,
404            "retMsg": "Invalid API key"
405        });
406
407        assert_eq!(extract_error_message(&response), "Invalid API key");
408    }
409
410    #[test]
411    fn test_extract_error_message_missing() {
412        let response = json!({
413            "retCode": 10003
414        });
415
416        assert_eq!(extract_error_message(&response), "Unknown error");
417    }
418
419    #[test]
420    fn test_bybit_error_code_from_code() {
421        assert_eq!(
422            BybitErrorCode::from_code(10001),
423            BybitErrorCode::InvalidRequest
424        );
425        assert_eq!(
426            BybitErrorCode::from_code(10003),
427            BybitErrorCode::InvalidApiKey
428        );
429        assert_eq!(
430            BybitErrorCode::from_code(10004),
431            BybitErrorCode::InvalidSignature
432        );
433        assert_eq!(
434            BybitErrorCode::from_code(10005),
435            BybitErrorCode::PermissionDenied
436        );
437        assert_eq!(
438            BybitErrorCode::from_code(10006),
439            BybitErrorCode::RateLimitExceeded
440        );
441        assert_eq!(
442            BybitErrorCode::from_code(10010),
443            BybitErrorCode::IpNotAllowed
444        );
445        assert_eq!(BybitErrorCode::from_code(10016), BybitErrorCode::BadSymbol);
446        assert_eq!(
447            BybitErrorCode::from_code(110001),
448            BybitErrorCode::InsufficientFunds
449        );
450        assert_eq!(
451            BybitErrorCode::from_code(110007),
452            BybitErrorCode::InsufficientAvailableBalance
453        );
454        assert_eq!(
455            BybitErrorCode::from_code(110008),
456            BybitErrorCode::OrderNotFound
457        );
458        assert_eq!(
459            BybitErrorCode::from_code(99999),
460            BybitErrorCode::Unknown(99999)
461        );
462    }
463
464    #[test]
465    fn test_bybit_error_code_code() {
466        assert_eq!(BybitErrorCode::InvalidRequest.code(), 10001);
467        assert_eq!(BybitErrorCode::InvalidApiKey.code(), 10003);
468        assert_eq!(BybitErrorCode::InvalidSignature.code(), 10004);
469        assert_eq!(BybitErrorCode::PermissionDenied.code(), 10005);
470        assert_eq!(BybitErrorCode::RateLimitExceeded.code(), 10006);
471        assert_eq!(BybitErrorCode::IpNotAllowed.code(), 10010);
472        assert_eq!(BybitErrorCode::BadSymbol.code(), 10016);
473        assert_eq!(BybitErrorCode::InsufficientFunds.code(), 110001);
474        assert_eq!(BybitErrorCode::InsufficientAvailableBalance.code(), 110007);
475        assert_eq!(BybitErrorCode::OrderNotFound.code(), 110008);
476        assert_eq!(BybitErrorCode::Unknown(12345).code(), 12345);
477    }
478}