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(serde_json::Value::as_str)
101        .unwrap_or("unknown");
102
103    let msg = response
104        .get("msg")
105        .and_then(serde_json::Value::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 | BitgetErrorCode::Unknown(_) => Error::exchange(code, msg),
122    }
123}
124
125/// Checks if a Bitget API response indicates an error.
126///
127/// Bitget uses "00000" as the success code. Any other code indicates an error.
128///
129/// # Arguments
130///
131/// * `response` - The JSON response from Bitget API
132///
133/// # Returns
134///
135/// `true` if the response indicates an error, `false` otherwise.
136pub fn is_error_response(response: &Value) -> bool {
137    response.get("code").and_then(serde_json::Value::as_str) != Some("00000")
138}
139
140/// Extracts the error code from a Bitget API response.
141///
142/// # Arguments
143///
144/// * `response` - The JSON response from Bitget API
145///
146/// # Returns
147///
148/// The error code as a string, or "unknown" if not found.
149pub fn extract_error_code(response: &Value) -> &str {
150    response
151        .get("code")
152        .and_then(serde_json::Value::as_str)
153        .unwrap_or("unknown")
154}
155
156/// Extracts the error message from a Bitget API response.
157///
158/// # Arguments
159///
160/// * `response` - The JSON response from Bitget API
161///
162/// # Returns
163///
164/// The error message as a string, or "Unknown error" if not found.
165pub fn extract_error_message(response: &Value) -> &str {
166    response
167        .get("msg")
168        .and_then(serde_json::Value::as_str)
169        .unwrap_or("Unknown error")
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use serde_json::json;
176
177    #[test]
178    fn test_parse_authentication_error_invalid_api_key() {
179        let response = json!({
180            "code": "40001",
181            "msg": "Invalid API key"
182        });
183
184        let error = parse_error(&response);
185        assert!(error.as_authentication().is_some());
186        assert!(error.to_string().contains("Invalid API key"));
187    }
188
189    #[test]
190    fn test_parse_authentication_error_invalid_signature() {
191        let response = json!({
192            "code": "40002",
193            "msg": "Invalid signature"
194        });
195
196        let error = parse_error(&response);
197        assert!(error.as_authentication().is_some());
198        assert!(error.to_string().contains("Invalid signature"));
199    }
200
201    #[test]
202    fn test_parse_rate_limit_error() {
203        let response = json!({
204            "code": "40003",
205            "msg": "Rate limit exceeded"
206        });
207
208        let error = parse_error(&response);
209        assert!(error.as_rate_limit().is_some());
210        let (msg, retry_after) = error.as_rate_limit().unwrap();
211        assert!(msg.contains("Rate limit"));
212        assert!(retry_after.is_some());
213    }
214
215    #[test]
216    fn test_parse_invalid_request_error() {
217        let response = json!({
218            "code": "40004",
219            "msg": "Invalid request parameters"
220        });
221
222        let error = parse_error(&response);
223        let display = error.to_string();
224        assert!(display.contains("Invalid request"));
225    }
226
227    #[test]
228    fn test_parse_insufficient_funds_error() {
229        let response = json!({
230            "code": "40005",
231            "msg": "Insufficient balance"
232        });
233
234        let error = parse_error(&response);
235        let display = error.to_string();
236        assert!(display.contains("Insufficient balance"));
237    }
238
239    #[test]
240    fn test_parse_bad_symbol_error() {
241        let response = json!({
242            "code": "40006",
243            "msg": "Invalid trading pair"
244        });
245
246        let error = parse_error(&response);
247        let display = error.to_string();
248        assert!(display.contains("Bad symbol"));
249    }
250
251    #[test]
252    fn test_parse_order_not_found_error() {
253        let response = json!({
254            "code": "40007",
255            "msg": "Order not found"
256        });
257
258        let error = parse_error(&response);
259        let display = error.to_string();
260        assert!(display.contains("Order not found"));
261    }
262
263    #[test]
264    fn test_parse_unknown_error() {
265        let response = json!({
266            "code": "99999",
267            "msg": "Some unknown error"
268        });
269
270        let error = parse_error(&response);
271        let display = error.to_string();
272        assert!(display.contains("Some unknown error"));
273    }
274
275    #[test]
276    fn test_parse_error_missing_code() {
277        let response = json!({
278            "msg": "Error without code"
279        });
280
281        let error = parse_error(&response);
282        // Should default to exchange error with "unknown" code
283        let display = error.to_string();
284        assert!(display.contains("Error without code"));
285    }
286
287    #[test]
288    fn test_parse_error_missing_message() {
289        let response = json!({
290            "code": "40001"
291        });
292
293        let error = parse_error(&response);
294        // Should default to "Unknown error" message
295        let display = error.to_string();
296        assert!(display.contains("Unknown error"));
297    }
298
299    #[test]
300    fn test_is_error_response_success() {
301        let response = json!({
302            "code": "00000",
303            "msg": "success",
304            "data": {}
305        });
306
307        assert!(!is_error_response(&response));
308    }
309
310    #[test]
311    fn test_is_error_response_error() {
312        let response = json!({
313            "code": "40001",
314            "msg": "Invalid API key"
315        });
316
317        assert!(is_error_response(&response));
318    }
319
320    #[test]
321    fn test_is_error_response_missing_code() {
322        let response = json!({
323            "msg": "No code field"
324        });
325
326        // Missing code should be treated as error
327        assert!(is_error_response(&response));
328    }
329
330    #[test]
331    fn test_extract_error_code() {
332        let response = json!({
333            "code": "40001",
334            "msg": "Invalid API key"
335        });
336
337        assert_eq!(extract_error_code(&response), "40001");
338    }
339
340    #[test]
341    fn test_extract_error_code_missing() {
342        let response = json!({
343            "msg": "No code"
344        });
345
346        assert_eq!(extract_error_code(&response), "unknown");
347    }
348
349    #[test]
350    fn test_extract_error_message() {
351        let response = json!({
352            "code": "40001",
353            "msg": "Invalid API key"
354        });
355
356        assert_eq!(extract_error_message(&response), "Invalid API key");
357    }
358
359    #[test]
360    fn test_extract_error_message_missing() {
361        let response = json!({
362            "code": "40001"
363        });
364
365        assert_eq!(extract_error_message(&response), "Unknown error");
366    }
367
368    #[test]
369    fn test_bitget_error_code_from_code() {
370        assert_eq!(
371            BitgetErrorCode::from_code("40001"),
372            BitgetErrorCode::InvalidApiKey
373        );
374        assert_eq!(
375            BitgetErrorCode::from_code("40002"),
376            BitgetErrorCode::InvalidSignature
377        );
378        assert_eq!(
379            BitgetErrorCode::from_code("40003"),
380            BitgetErrorCode::RateLimitExceeded
381        );
382        assert_eq!(
383            BitgetErrorCode::from_code("40004"),
384            BitgetErrorCode::InvalidRequest
385        );
386        assert_eq!(
387            BitgetErrorCode::from_code("40005"),
388            BitgetErrorCode::InsufficientFunds
389        );
390        assert_eq!(
391            BitgetErrorCode::from_code("40006"),
392            BitgetErrorCode::BadSymbol
393        );
394        assert_eq!(
395            BitgetErrorCode::from_code("40007"),
396            BitgetErrorCode::OrderNotFound
397        );
398        assert_eq!(
399            BitgetErrorCode::from_code("99999"),
400            BitgetErrorCode::Unknown(99999)
401        );
402        assert_eq!(
403            BitgetErrorCode::from_code("invalid"),
404            BitgetErrorCode::Unknown(0)
405        );
406    }
407
408    #[test]
409    fn test_bitget_error_code_code() {
410        assert_eq!(BitgetErrorCode::InvalidApiKey.code(), 40001);
411        assert_eq!(BitgetErrorCode::InvalidSignature.code(), 40002);
412        assert_eq!(BitgetErrorCode::RateLimitExceeded.code(), 40003);
413        assert_eq!(BitgetErrorCode::InvalidRequest.code(), 40004);
414        assert_eq!(BitgetErrorCode::InsufficientFunds.code(), 40005);
415        assert_eq!(BitgetErrorCode::BadSymbol.code(), 40006);
416        assert_eq!(BitgetErrorCode::OrderNotFound.code(), 40007);
417        assert_eq!(BitgetErrorCode::Unknown(12345).code(), 12345);
418    }
419}