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(|v| v.as_i64())
113        .unwrap_or(0);
114
115    let msg = response
116        .get("retMsg")
117        .and_then(|v| v.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 => Error::exchange(code.to_string(), msg),
137        BybitErrorCode::Unknown(_) => Error::exchange(code.to_string(), msg),
138    }
139}
140
141/// Checks if a Bybit API response indicates an error.
142///
143/// Bybit uses 0 as the success code. Any other code indicates an error.
144///
145/// # Arguments
146///
147/// * `response` - The JSON response from Bybit API
148///
149/// # Returns
150///
151/// `true` if the response indicates an error, `false` otherwise.
152pub fn is_error_response(response: &Value) -> bool {
153    response
154        .get("retCode")
155        .and_then(|v| v.as_i64())
156        .map(|code| code != 0)
157        .unwrap_or(true)
158}
159
160/// Extracts the error code from a Bybit API response.
161///
162/// # Arguments
163///
164/// * `response` - The JSON response from Bybit API
165///
166/// # Returns
167///
168/// The error code as an i64, or 0 if not found.
169pub fn extract_error_code(response: &Value) -> i64 {
170    response
171        .get("retCode")
172        .and_then(|v| v.as_i64())
173        .unwrap_or(0)
174}
175
176/// Extracts the error message from a Bybit API response.
177///
178/// # Arguments
179///
180/// * `response` - The JSON response from Bybit API
181///
182/// # Returns
183///
184/// The error message as a string, or "Unknown error" if not found.
185pub fn extract_error_message(response: &Value) -> &str {
186    response
187        .get("retMsg")
188        .and_then(|v| v.as_str())
189        .unwrap_or("Unknown error")
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use serde_json::json;
196
197    #[test]
198    fn test_parse_authentication_error_invalid_api_key() {
199        let response = json!({
200            "retCode": 10003,
201            "retMsg": "Invalid API key"
202        });
203
204        let error = parse_error(&response);
205        assert!(error.as_authentication().is_some());
206        assert!(error.to_string().contains("Invalid API key"));
207    }
208
209    #[test]
210    fn test_parse_authentication_error_invalid_signature() {
211        let response = json!({
212            "retCode": 10004,
213            "retMsg": "Invalid signature"
214        });
215
216        let error = parse_error(&response);
217        assert!(error.as_authentication().is_some());
218        assert!(error.to_string().contains("Invalid signature"));
219    }
220
221    #[test]
222    fn test_parse_authentication_error_permission_denied() {
223        let response = json!({
224            "retCode": 10005,
225            "retMsg": "Permission denied"
226        });
227
228        let error = parse_error(&response);
229        assert!(error.as_authentication().is_some());
230        assert!(error.to_string().contains("Permission denied"));
231    }
232
233    #[test]
234    fn test_parse_authentication_error_ip_not_allowed() {
235        let response = json!({
236            "retCode": 10010,
237            "retMsg": "IP not allowed"
238        });
239
240        let error = parse_error(&response);
241        assert!(error.as_authentication().is_some());
242        assert!(error.to_string().contains("IP not allowed"));
243    }
244
245    #[test]
246    fn test_parse_rate_limit_error() {
247        let response = json!({
248            "retCode": 10006,
249            "retMsg": "Rate limit exceeded"
250        });
251
252        let error = parse_error(&response);
253        assert!(error.as_rate_limit().is_some());
254        let (msg, retry_after) = error.as_rate_limit().unwrap();
255        assert!(msg.contains("Rate limit"));
256        assert!(retry_after.is_some());
257    }
258
259    #[test]
260    fn test_parse_invalid_request_error() {
261        let response = json!({
262            "retCode": 10001,
263            "retMsg": "Invalid request parameters"
264        });
265
266        let error = parse_error(&response);
267        let display = error.to_string();
268        assert!(display.contains("Invalid request"));
269    }
270
271    #[test]
272    fn test_parse_insufficient_funds_error() {
273        let response = json!({
274            "retCode": 110001,
275            "retMsg": "Insufficient balance"
276        });
277
278        let error = parse_error(&response);
279        let display = error.to_string();
280        assert!(display.contains("Insufficient balance"));
281    }
282
283    #[test]
284    fn test_parse_insufficient_available_balance_error() {
285        let response = json!({
286            "retCode": 110007,
287            "retMsg": "Insufficient available balance"
288        });
289
290        let error = parse_error(&response);
291        let display = error.to_string();
292        assert!(display.contains("Insufficient"));
293    }
294
295    #[test]
296    fn test_parse_bad_symbol_error() {
297        let response = json!({
298            "retCode": 10016,
299            "retMsg": "Invalid symbol"
300        });
301
302        let error = parse_error(&response);
303        let display = error.to_string();
304        assert!(display.contains("Bad symbol"));
305    }
306
307    #[test]
308    fn test_parse_order_not_found_error() {
309        let response = json!({
310            "retCode": 110008,
311            "retMsg": "Order not found"
312        });
313
314        let error = parse_error(&response);
315        let display = error.to_string();
316        assert!(display.contains("Order not found"));
317    }
318
319    #[test]
320    fn test_parse_unknown_error() {
321        let response = json!({
322            "retCode": 99999,
323            "retMsg": "Some unknown error"
324        });
325
326        let error = parse_error(&response);
327        let display = error.to_string();
328        assert!(display.contains("Some unknown error"));
329    }
330
331    #[test]
332    fn test_parse_error_missing_code() {
333        let response = json!({
334            "retMsg": "Error without code"
335        });
336
337        let error = parse_error(&response);
338        let display = error.to_string();
339        assert!(display.contains("Error without code"));
340    }
341
342    #[test]
343    fn test_parse_error_missing_message() {
344        let response = json!({
345            "retCode": 10003
346        });
347
348        let error = parse_error(&response);
349        let display = error.to_string();
350        assert!(display.contains("Unknown error"));
351    }
352
353    #[test]
354    fn test_is_error_response_success() {
355        let response = json!({
356            "retCode": 0,
357            "retMsg": "OK",
358            "result": {}
359        });
360
361        assert!(!is_error_response(&response));
362    }
363
364    #[test]
365    fn test_is_error_response_error() {
366        let response = json!({
367            "retCode": 10003,
368            "retMsg": "Invalid API key"
369        });
370
371        assert!(is_error_response(&response));
372    }
373
374    #[test]
375    fn test_is_error_response_missing_code() {
376        let response = json!({
377            "retMsg": "No code field"
378        });
379
380        // Missing code should be treated as error
381        assert!(is_error_response(&response));
382    }
383
384    #[test]
385    fn test_extract_error_code() {
386        let response = json!({
387            "retCode": 10003,
388            "retMsg": "Invalid API key"
389        });
390
391        assert_eq!(extract_error_code(&response), 10003);
392    }
393
394    #[test]
395    fn test_extract_error_code_missing() {
396        let response = json!({
397            "retMsg": "No code"
398        });
399
400        assert_eq!(extract_error_code(&response), 0);
401    }
402
403    #[test]
404    fn test_extract_error_message() {
405        let response = json!({
406            "retCode": 10003,
407            "retMsg": "Invalid API key"
408        });
409
410        assert_eq!(extract_error_message(&response), "Invalid API key");
411    }
412
413    #[test]
414    fn test_extract_error_message_missing() {
415        let response = json!({
416            "retCode": 10003
417        });
418
419        assert_eq!(extract_error_message(&response), "Unknown error");
420    }
421
422    #[test]
423    fn test_bybit_error_code_from_code() {
424        assert_eq!(
425            BybitErrorCode::from_code(10001),
426            BybitErrorCode::InvalidRequest
427        );
428        assert_eq!(
429            BybitErrorCode::from_code(10003),
430            BybitErrorCode::InvalidApiKey
431        );
432        assert_eq!(
433            BybitErrorCode::from_code(10004),
434            BybitErrorCode::InvalidSignature
435        );
436        assert_eq!(
437            BybitErrorCode::from_code(10005),
438            BybitErrorCode::PermissionDenied
439        );
440        assert_eq!(
441            BybitErrorCode::from_code(10006),
442            BybitErrorCode::RateLimitExceeded
443        );
444        assert_eq!(
445            BybitErrorCode::from_code(10010),
446            BybitErrorCode::IpNotAllowed
447        );
448        assert_eq!(BybitErrorCode::from_code(10016), BybitErrorCode::BadSymbol);
449        assert_eq!(
450            BybitErrorCode::from_code(110001),
451            BybitErrorCode::InsufficientFunds
452        );
453        assert_eq!(
454            BybitErrorCode::from_code(110007),
455            BybitErrorCode::InsufficientAvailableBalance
456        );
457        assert_eq!(
458            BybitErrorCode::from_code(110008),
459            BybitErrorCode::OrderNotFound
460        );
461        assert_eq!(
462            BybitErrorCode::from_code(99999),
463            BybitErrorCode::Unknown(99999)
464        );
465    }
466
467    #[test]
468    fn test_bybit_error_code_code() {
469        assert_eq!(BybitErrorCode::InvalidRequest.code(), 10001);
470        assert_eq!(BybitErrorCode::InvalidApiKey.code(), 10003);
471        assert_eq!(BybitErrorCode::InvalidSignature.code(), 10004);
472        assert_eq!(BybitErrorCode::PermissionDenied.code(), 10005);
473        assert_eq!(BybitErrorCode::RateLimitExceeded.code(), 10006);
474        assert_eq!(BybitErrorCode::IpNotAllowed.code(), 10010);
475        assert_eq!(BybitErrorCode::BadSymbol.code(), 10016);
476        assert_eq!(BybitErrorCode::InsufficientFunds.code(), 110001);
477        assert_eq!(BybitErrorCode::InsufficientAvailableBalance.code(), 110007);
478        assert_eq!(BybitErrorCode::OrderNotFound.code(), 110008);
479        assert_eq!(BybitErrorCode::Unknown(12345).code(), 12345);
480    }
481}