Skip to main content

binance_api_client/
error.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3use thiserror::Error;
4
5use crate::models::account::{CancelReplaceErrorData, CancelReplaceErrorResponse};
6
7/// Binance API error response structure.
8#[derive(Debug, Deserialize)]
9pub struct BinanceApiError {
10    pub code: i32,
11    pub msg: String,
12    #[serde(flatten)]
13    pub extra: HashMap<String, serde_json::Value>,
14}
15
16impl std::fmt::Display for BinanceApiError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        write!(f, "Binance API error {}: {}", self.code, self.msg)
19    }
20}
21
22impl std::error::Error for BinanceApiError {}
23
24/// Error types for the Binance client library.
25#[derive(Debug, Error)]
26pub enum Error {
27    /// Binance API returned an error response.
28    #[error("Binance API error {code}: {message}")]
29    Api { code: i32, message: String },
30
31    /// Binance API returned a cancel-replace error response.
32    #[error("Binance API cancel-replace error {code}: {message}")]
33    CancelReplace {
34        code: i32,
35        message: String,
36        data: Box<CancelReplaceErrorData>,
37    },
38
39    /// HTTP request error.
40    #[error("HTTP error: {0}")]
41    Http(#[from] reqwest::Error),
42
43    /// HTTP middleware error.
44    #[error("HTTP middleware error: {0}")]
45    Middleware(#[from] reqwest_middleware::Error),
46
47    /// WebSocket error.
48    #[error("WebSocket error: {0}")]
49    WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
50
51    /// JSON serialization/deserialization error.
52    #[error("Serialization error: {0}")]
53    Serialization(#[from] serde_json::Error),
54
55    /// URL parsing error.
56    #[error("URL parse error: {0}")]
57    UrlParse(#[from] url::ParseError),
58
59    /// Invalid configuration.
60    #[error("Invalid configuration: {0}")]
61    InvalidConfig(String),
62
63    /// Authentication is required but credentials were not provided.
64    #[error("Authentication required for this endpoint")]
65    AuthenticationRequired,
66
67    /// System time error (for timestamp generation).
68    #[error("System time error: {0}")]
69    SystemTime(#[from] std::time::SystemTimeError),
70
71    /// Invalid header value.
72    #[error("Invalid header value: {0}")]
73    InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),
74
75    /// Environment variable error.
76    #[error("Environment variable error: {0}")]
77    EnvVar(#[from] std::env::VarError),
78
79    /// Invalid credentials (RSA/Ed25519 key parsing error).
80    #[error("Invalid credentials: {0}")]
81    InvalidCredentials(String),
82}
83
84impl Error {
85    /// Create an API error from a Binance error response.
86    pub fn from_binance_error(error: BinanceApiError) -> Self {
87        Error::Api {
88            code: error.code,
89            message: error.msg,
90        }
91    }
92
93    /// Create a cancel-replace error from a Binance error response.
94    pub fn from_cancel_replace_error(error: CancelReplaceErrorResponse) -> Self {
95        Error::CancelReplace {
96            code: error.code,
97            message: error.msg.clone(),
98            data: Box::new(error.data),
99        }
100    }
101
102    /// Check if this is a rate limit error (code -1003).
103    pub fn is_rate_limit(&self) -> bool {
104        matches!(self, Error::Api { code: -1003, .. })
105    }
106
107    /// Check if this is an invalid signature error (code -1022).
108    pub fn is_invalid_signature(&self) -> bool {
109        matches!(self, Error::Api { code: -1022, .. })
110    }
111
112    /// Check if this is a timestamp out of recv_window error (code -1021).
113    pub fn is_timestamp_error(&self) -> bool {
114        matches!(self, Error::Api { code: -1021, .. })
115    }
116
117    /// Check if this is an unauthorized error (code -1002 or -2015).
118    pub fn is_unauthorized(&self) -> bool {
119        matches!(
120            self,
121            Error::Api {
122                code: -1002 | -2015,
123                ..
124            }
125        )
126    }
127}
128
129/// Result type alias for this library.
130pub type Result<T> = std::result::Result<T, Error>;
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_api_error_display() {
138        let err = Error::Api {
139            code: -1003,
140            message: "Too many requests".to_string(),
141        };
142        assert_eq!(
143            format!("{}", err),
144            "Binance API error -1003: Too many requests"
145        );
146    }
147
148    #[test]
149    fn test_is_rate_limit() {
150        let rate_limit_err = Error::Api {
151            code: -1003,
152            message: "Too many requests".to_string(),
153        };
154        assert!(rate_limit_err.is_rate_limit());
155
156        let other_err = Error::Api {
157            code: -1000,
158            message: "Unknown error".to_string(),
159        };
160        assert!(!other_err.is_rate_limit());
161    }
162
163    #[test]
164    fn test_is_invalid_signature() {
165        let sig_err = Error::Api {
166            code: -1022,
167            message: "Invalid signature".to_string(),
168        };
169        assert!(sig_err.is_invalid_signature());
170    }
171
172    #[test]
173    fn test_is_timestamp_error() {
174        let ts_err = Error::Api {
175            code: -1021,
176            message: "Timestamp outside recv window".to_string(),
177        };
178        assert!(ts_err.is_timestamp_error());
179    }
180
181    #[test]
182    fn test_is_unauthorized() {
183        let unauth_err = Error::Api {
184            code: -2015,
185            message: "Invalid API key".to_string(),
186        };
187        assert!(unauth_err.is_unauthorized());
188
189        let unauth_err2 = Error::Api {
190            code: -1002,
191            message: "Unauthorized".to_string(),
192        };
193        assert!(unauth_err2.is_unauthorized());
194    }
195
196    #[test]
197    fn test_binance_api_error_deserialize() {
198        let json = r#"{"code": -1000, "msg": "Unknown error"}"#;
199        let err: BinanceApiError = serde_json::from_str(json).unwrap();
200        assert_eq!(err.code, -1000);
201        assert_eq!(err.msg, "Unknown error");
202    }
203}