ccxt_exchanges/bitget/
error.rs

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