1use ccxt_core::error::Error as CoreError;
28use thiserror::Error;
29
30#[cfg(test)]
31use std::error::Error as StdError;
32
33#[derive(Error, Debug, Clone)]
44#[error("Binance API error {code}: {msg}")]
45pub struct BinanceApiError {
46 pub code: i32,
48 pub msg: String,
50}
51
52impl BinanceApiError {
53 pub fn new(code: i32, msg: impl Into<String>) -> Self {
69 Self {
70 code,
71 msg: msg.into(),
72 }
73 }
74
75 pub fn from_json(json: &serde_json::Value) -> Option<Self> {
98 let code = json.get("code")?.as_i64()? as i32;
99 let msg = json.get("msg")?.as_str()?.to_string();
100 Some(Self { code, msg })
101 }
102
103 pub fn is_rate_limit(&self) -> bool {
109 matches!(self.code, -1003 | -1015)
110 }
111
112 pub fn is_ip_banned(&self) -> bool {
116 self.code == -1003 && self.msg.contains("banned")
117 }
118
119 pub fn is_auth_error(&self) -> bool {
121 matches!(self.code, -2014 | -2015 | -1022)
122 }
123
124 pub fn is_invalid_symbol(&self) -> bool {
126 self.code == -1121
127 }
128
129 pub fn is_insufficient_balance(&self) -> bool {
131 matches!(self.code, -2010 | -2011)
132 }
133
134 pub fn is_order_not_found(&self) -> bool {
136 self.code == -2013
137 }
138
139 pub fn to_core_error(&self) -> CoreError {
151 use std::borrow::Cow;
152
153 match self.code {
154 -2014 | -2015 | -1022 => CoreError::authentication(self.msg.clone()),
156 -1003 | -1015 => CoreError::rate_limit(self.msg.clone(), None),
158 -1121 => CoreError::bad_symbol(&self.msg),
160 -2010 | -2011 => CoreError::insufficient_balance(self.msg.clone()),
162 -2013 => CoreError::OrderNotFound(Cow::Owned(self.msg.clone())),
164 -1102 | -1106 | -1111 | -1112 | -1114 | -1115 | -1116 | -1117 | -1118 => {
166 CoreError::InvalidOrder(Cow::Owned(self.msg.clone()))
167 }
168 -1001 => CoreError::network(&self.msg),
170 _ => CoreError::exchange(self.code.to_string(), &self.msg),
172 }
173 }
174}
175
176impl From<BinanceApiError> for CoreError {
177 fn from(e: BinanceApiError) -> Self {
178 e.to_core_error()
179 }
180}
181
182#[derive(Error, Debug)]
195#[non_exhaustive]
196pub enum BinanceWsError {
197 #[error("Stream name missing in message: {raw}")]
202 MissingStream {
203 raw: String,
205 },
206
207 #[error("Unsupported event type: {event}")]
212 UnsupportedEvent {
213 event: String,
215 },
216
217 #[error("Subscription failed for {stream}: {reason}")]
222 SubscriptionFailed {
223 stream: String,
225 reason: String,
227 },
228
229 #[error("WebSocket connection error: {0}")]
233 Connection(String),
234
235 #[error(transparent)]
240 Core(#[from] CoreError),
241}
242
243impl BinanceWsError {
244 pub fn missing_stream(raw: impl Into<String>) -> Self {
258 let mut raw_str = raw.into();
259 if raw_str.len() > 200 {
261 raw_str.truncate(200);
262 raw_str.push_str("...");
263 }
264 Self::MissingStream { raw: raw_str }
265 }
266
267 pub fn unsupported_event(event: impl Into<String>) -> Self {
281 Self::UnsupportedEvent {
282 event: event.into(),
283 }
284 }
285
286 pub fn subscription_failed(stream: impl Into<String>, reason: impl Into<String>) -> Self {
301 Self::SubscriptionFailed {
302 stream: stream.into(),
303 reason: reason.into(),
304 }
305 }
306
307 pub fn connection(message: impl Into<String>) -> Self {
321 Self::Connection(message.into())
322 }
323
324 pub fn stream_name(&self) -> Option<&str> {
343 match self {
344 Self::MissingStream { .. }
345 | Self::UnsupportedEvent { .. }
346 | Self::Connection(_)
347 | Self::Core(_) => None,
348 Self::SubscriptionFailed { stream, .. } => Some(stream),
349 }
350 }
351
352 pub fn event_type(&self) -> Option<&str> {
359 match self {
360 Self::UnsupportedEvent { event } => Some(event),
361 _ => None,
362 }
363 }
364
365 pub fn raw_message(&self) -> Option<&str> {
372 match self {
373 Self::MissingStream { raw } => Some(raw),
374 _ => None,
375 }
376 }
377}
378
379impl From<BinanceWsError> for CoreError {
390 fn from(e: BinanceWsError) -> Self {
391 match e {
392 BinanceWsError::Core(core) => core,
394 other => CoreError::WebSocket(Box::new(other)),
396 }
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_missing_stream_error() {
406 let err = BinanceWsError::missing_stream(r#"{"data": "test"}"#);
407 assert!(matches!(err, BinanceWsError::MissingStream { .. }));
408 assert!(err.to_string().contains("Stream name missing"));
409 assert_eq!(err.stream_name(), None);
410 assert!(err.raw_message().is_some());
411 }
412
413 #[test]
414 fn test_missing_stream_truncation() {
415 let long_message = "x".repeat(300);
416 let err = BinanceWsError::missing_stream(long_message);
417 if let BinanceWsError::MissingStream { raw } = &err {
418 assert!(raw.len() <= 203); assert!(raw.ends_with("..."));
420 } else {
421 panic!("Expected MissingStream variant");
422 }
423 }
424
425 #[test]
426 fn test_unsupported_event_error() {
427 let err = BinanceWsError::unsupported_event("unknownEvent");
428 assert!(matches!(err, BinanceWsError::UnsupportedEvent { .. }));
429 assert!(err.to_string().contains("unknownEvent"));
430 assert_eq!(err.event_type(), Some("unknownEvent"));
431 assert_eq!(err.stream_name(), None);
432 }
433
434 #[test]
435 fn test_subscription_failed_error() {
436 let err = BinanceWsError::subscription_failed("btcusdt@ticker", "Invalid symbol");
437 assert!(matches!(err, BinanceWsError::SubscriptionFailed { .. }));
438 assert!(err.to_string().contains("btcusdt@ticker"));
439 assert!(err.to_string().contains("Invalid symbol"));
440 assert_eq!(err.stream_name(), Some("btcusdt@ticker"));
441 }
442
443 #[test]
444 fn test_connection_error() {
445 let err = BinanceWsError::connection("Connection refused");
446 assert!(matches!(err, BinanceWsError::Connection(_)));
447 assert!(err.to_string().contains("Connection refused"));
448 assert_eq!(err.stream_name(), None);
449 }
450
451 #[test]
452 fn test_core_passthrough() {
453 let core_err = CoreError::authentication("Invalid API key");
454 let ws_err = BinanceWsError::Core(core_err);
455 assert!(matches!(ws_err, BinanceWsError::Core(_)));
456 }
457
458 #[test]
459 fn test_conversion_to_core_error() {
460 let ws_err = BinanceWsError::subscription_failed("btcusdt@ticker", "Invalid");
462 let core_err: CoreError = ws_err.into();
463 assert!(matches!(core_err, CoreError::WebSocket(_)));
464
465 let downcast = core_err.downcast_websocket::<BinanceWsError>();
467 assert!(downcast.is_some());
468 if let Some(binance_err) = downcast {
469 assert_eq!(binance_err.stream_name(), Some("btcusdt@ticker"));
470 }
471 }
472
473 #[test]
474 fn test_core_passthrough_conversion() {
475 let original = CoreError::authentication("Invalid API key");
477 let ws_err = BinanceWsError::Core(original);
478 let core_err: CoreError = ws_err.into();
479
480 assert!(matches!(core_err, CoreError::Authentication(_)));
482 assert!(core_err.to_string().contains("Invalid API key"));
483 }
484
485 #[test]
486 fn test_error_is_send_sync() {
487 fn assert_send_sync<T: Send + Sync>() {}
488 assert_send_sync::<BinanceWsError>();
489 assert_send_sync::<BinanceApiError>();
490 }
491
492 #[test]
493 fn test_error_is_std_error() {
494 fn assert_std_error<T: StdError>() {}
495 assert_std_error::<BinanceWsError>();
496 assert_std_error::<BinanceApiError>();
497 }
498
499 #[test]
502 fn test_binance_api_error_new() {
503 let err = BinanceApiError::new(-1121, "Invalid symbol.");
504 assert_eq!(err.code, -1121);
505 assert_eq!(err.msg, "Invalid symbol.");
506 assert!(err.to_string().contains("-1121"));
507 assert!(err.to_string().contains("Invalid symbol."));
508 }
509
510 #[test]
511 fn test_binance_api_error_from_json() {
512 let json = serde_json::json!({"code": -1121, "msg": "Invalid symbol."});
513 let err = BinanceApiError::from_json(&json);
514 assert!(err.is_some());
515 let err = err.unwrap();
516 assert_eq!(err.code, -1121);
517 assert_eq!(err.msg, "Invalid symbol.");
518 }
519
520 #[test]
521 fn test_binance_api_error_from_json_missing_fields() {
522 let json = serde_json::json!({"msg": "Invalid symbol."});
524 assert!(BinanceApiError::from_json(&json).is_none());
525
526 let json = serde_json::json!({"code": -1121});
528 assert!(BinanceApiError::from_json(&json).is_none());
529
530 let json = serde_json::json!({});
532 assert!(BinanceApiError::from_json(&json).is_none());
533 }
534
535 #[test]
536 fn test_binance_api_error_is_rate_limit() {
537 let err = BinanceApiError::new(-1003, "Too many requests");
538 assert!(err.is_rate_limit());
539
540 let err = BinanceApiError::new(-1015, "Too many orders");
541 assert!(err.is_rate_limit());
542
543 let err = BinanceApiError::new(-1121, "Invalid symbol");
544 assert!(!err.is_rate_limit());
545 }
546
547 #[test]
548 fn test_binance_api_error_is_auth_error() {
549 let err = BinanceApiError::new(-2014, "API-key format invalid");
550 assert!(err.is_auth_error());
551
552 let err = BinanceApiError::new(-2015, "Invalid API-key, IP, or permissions");
553 assert!(err.is_auth_error());
554
555 let err = BinanceApiError::new(-1022, "Signature for this request is not valid");
556 assert!(err.is_auth_error());
557
558 let err = BinanceApiError::new(-1121, "Invalid symbol");
559 assert!(!err.is_auth_error());
560 }
561
562 #[test]
563 fn test_binance_api_error_is_invalid_symbol() {
564 let err = BinanceApiError::new(-1121, "Invalid symbol.");
565 assert!(err.is_invalid_symbol());
566
567 let err = BinanceApiError::new(-1003, "Too many requests");
568 assert!(!err.is_invalid_symbol());
569 }
570
571 #[test]
572 fn test_binance_api_error_is_insufficient_balance() {
573 let err = BinanceApiError::new(-2010, "Account has insufficient balance");
574 assert!(err.is_insufficient_balance());
575
576 let err = BinanceApiError::new(-2011, "Unknown order sent");
577 assert!(err.is_insufficient_balance());
578
579 let err = BinanceApiError::new(-1121, "Invalid symbol");
580 assert!(!err.is_insufficient_balance());
581 }
582
583 #[test]
584 fn test_binance_api_error_is_order_not_found() {
585 let err = BinanceApiError::new(-2013, "Order does not exist");
586 assert!(err.is_order_not_found());
587
588 let err = BinanceApiError::new(-1121, "Invalid symbol");
589 assert!(!err.is_order_not_found());
590 }
591
592 #[test]
593 fn test_binance_api_error_to_core_error_auth() {
594 let err = BinanceApiError::new(-2015, "Invalid API-key");
595 let core_err: CoreError = err.into();
596 assert!(matches!(core_err, CoreError::Authentication(_)));
597 }
598
599 #[test]
600 fn test_binance_api_error_to_core_error_rate_limit() {
601 let err = BinanceApiError::new(-1003, "Too many requests");
602 let core_err: CoreError = err.into();
603 assert!(matches!(core_err, CoreError::RateLimit { .. }));
604 }
605
606 #[test]
607 fn test_binance_api_error_to_core_error_invalid_symbol() {
608 let err = BinanceApiError::new(-1121, "Invalid symbol.");
609 let core_err: CoreError = err.into();
610 assert!(matches!(core_err, CoreError::InvalidRequest(_)));
612 }
613
614 #[test]
615 fn test_binance_api_error_to_core_error_insufficient_funds() {
616 let err = BinanceApiError::new(-2010, "Account has insufficient balance");
617 let core_err: CoreError = err.into();
618 assert!(matches!(core_err, CoreError::InsufficientBalance(_)));
619 }
620
621 #[test]
622 fn test_binance_api_error_to_core_error_order_not_found() {
623 let err = BinanceApiError::new(-2013, "Order does not exist");
624 let core_err: CoreError = err.into();
625 assert!(matches!(core_err, CoreError::OrderNotFound(_)));
626 }
627
628 #[test]
629 fn test_binance_api_error_to_core_error_invalid_order() {
630 let err = BinanceApiError::new(-1102, "Mandatory parameter was not sent");
631 let core_err: CoreError = err.into();
632 assert!(matches!(core_err, CoreError::InvalidOrder(_)));
633 }
634
635 #[test]
636 fn test_binance_api_error_to_core_error_network() {
637 let err = BinanceApiError::new(-1001, "Internal error; unable to process your request");
638 let core_err: CoreError = err.into();
639 assert!(matches!(core_err, CoreError::Network(_)));
640 }
641
642 #[test]
643 fn test_binance_api_error_to_core_error_exchange() {
644 let err = BinanceApiError::new(-9999, "Unknown error");
645 let core_err: CoreError = err.into();
646 assert!(matches!(core_err, CoreError::Exchange(_)));
647 }
648}