binance_api_client/
error.rs1use serde::Deserialize;
2use std::collections::HashMap;
3use thiserror::Error;
4
5use crate::models::account::{CancelReplaceErrorData, CancelReplaceErrorResponse};
6
7#[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#[derive(Debug, Error)]
26pub enum Error {
27 #[error("Binance API error {code}: {message}")]
29 Api { code: i32, message: String },
30
31 #[error("Binance API cancel-replace error {code}: {message}")]
33 CancelReplace {
34 code: i32,
35 message: String,
36 data: Box<CancelReplaceErrorData>,
37 },
38
39 #[error("HTTP error: {0}")]
41 Http(#[from] reqwest::Error),
42
43 #[error("HTTP middleware error: {0}")]
45 Middleware(#[from] reqwest_middleware::Error),
46
47 #[error("WebSocket error: {0}")]
49 WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
50
51 #[error("Serialization error: {0}")]
53 Serialization(#[from] serde_json::Error),
54
55 #[error("URL parse error: {0}")]
57 UrlParse(#[from] url::ParseError),
58
59 #[error("Invalid configuration: {0}")]
61 InvalidConfig(String),
62
63 #[error("Authentication required for this endpoint")]
65 AuthenticationRequired,
66
67 #[error("System time error: {0}")]
69 SystemTime(#[from] std::time::SystemTimeError),
70
71 #[error("Invalid header value: {0}")]
73 InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),
74
75 #[error("Environment variable error: {0}")]
77 EnvVar(#[from] std::env::VarError),
78
79 #[error("Invalid credentials: {0}")]
81 InvalidCredentials(String),
82}
83
84impl Error {
85 pub fn from_binance_error(error: BinanceApiError) -> Self {
87 Error::Api {
88 code: error.code,
89 message: error.msg,
90 }
91 }
92
93 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 pub fn is_rate_limit(&self) -> bool {
104 matches!(self, Error::Api { code: -1003, .. })
105 }
106
107 pub fn is_invalid_signature(&self) -> bool {
109 matches!(self, Error::Api { code: -1022, .. })
110 }
111
112 pub fn is_timestamp_error(&self) -> bool {
114 matches!(self, Error::Api { code: -1021, .. })
115 }
116
117 pub fn is_unauthorized(&self) -> bool {
119 matches!(
120 self,
121 Error::Api {
122 code: -1002 | -2015,
123 ..
124 }
125 )
126 }
127}
128
129pub 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}