1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum BybitError {
9 #[error("API error (code={code}): {message}")]
11 Api {
12 code: i32,
13 message: String,
14 #[source]
15 source: Option<Box<dyn std::error::Error + Send + Sync>>,
16 },
17
18 #[error("HTTP error: {0}")]
20 Http(#[from] reqwest::Error),
21
22 #[error("Serialization error: {0}")]
24 Serialization(#[from] serde_json::Error),
25
26 #[error("URL error: {0}")]
28 Url(#[from] url::ParseError),
29
30 #[error("Authentication error: {0}")]
32 Auth(String),
33
34 #[error("Invalid parameter: {0}")]
36 InvalidParameter(String),
37
38 #[error("WebSocket error: {0}")]
40 WebSocket(String),
41
42 #[error("Rate limit exceeded: {message}")]
44 RateLimit {
45 message: String,
46 retry_after_ms: Option<u64>,
47 },
48
49 #[error("Request timeout")]
51 Timeout,
52
53 #[error("Configuration error: {0}")]
55 Config(String),
56}
57
58impl BybitError {
59 pub fn api_error(code: i32, message: impl Into<String>) -> Self {
61 BybitError::Api {
62 code,
63 message: message.into(),
64 source: None,
65 }
66 }
67
68 pub fn is_retryable(&self) -> bool {
70 match self {
71 BybitError::Http(e) => e.is_timeout() || e.is_connect(),
72 BybitError::RateLimit { .. } => true,
73 BybitError::Timeout => true,
74 BybitError::Api { code, .. } => {
75 matches!(code, 10002 | 10006 | 30034 | 30035 | 130035 | 130150)
76 }
77 _ => false,
78 }
79 }
80
81 pub fn api_code(&self) -> Option<i32> {
83 match self {
84 BybitError::Api { code, .. } => Some(*code),
85 _ => None,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct ApiResponse<T> {
94 pub ret_code: i32,
96 pub ret_msg: String,
98 pub result: T,
100 #[serde(default)]
102 pub ret_ext_info: serde_json::Value,
103 #[serde(default)]
105 pub time: u64,
106}
107
108impl<T> ApiResponse<T> {
109 pub fn is_success(&self) -> bool {
111 self.ret_code == 0
112 }
113
114 pub fn into_result(self) -> Result<T, BybitError> {
116 if self.is_success() {
117 Ok(self.result)
118 } else {
119 Err(BybitError::api_error(self.ret_code, self.ret_msg))
120 }
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct ListResult<T> {
128 #[serde(default)]
130 pub category: Option<String>,
131 pub list: Vec<T>,
133 #[serde(default)]
135 pub next_page_cursor: Option<String>,
136}
137
138impl<T> ListResult<T> {
139 pub fn has_next_page(&self) -> bool {
141 self.next_page_cursor
142 .as_ref()
143 .map(|c| !c.is_empty())
144 .unwrap_or(false)
145 }
146}
147
148pub mod error_codes {
150 pub const SUCCESS: i32 = 0;
152
153 pub const PARAMS_MISSING_OR_WRONG: i32 = 10001;
155
156 pub const REQUEST_TIMEOUT: i32 = 10002;
158
159 pub const INVALID_API_KEY: i32 = 10003;
161
162 pub const INVALID_SIGNATURE: i32 = 10004;
164
165 pub const INCORRECT_PERMISSIONS: i32 = 10005;
167
168 pub const IP_RATE_LIMIT: i32 = 10006;
170
171 pub const INCORRECT_IP: i32 = 10010;
173
174 pub const ACCOUNT_NOT_UNIFIED: i32 = 10020;
176
177 pub const ORDER_NOT_FOUND: i32 = 20001;
179
180 pub const INSUFFICIENT_BALANCE: i32 = 30031;
182
183 pub const V5_ORDER_NOT_FOUND: i32 = 110001;
185
186 pub const V5_INSUFFICIENT_BALANCE: i32 = 110007;
188
189 pub const V5_POSITION_NOT_FOUND: i32 = 110009;
191
192 pub const V5_QTY_EXCEEDS_LIMIT: i32 = 110012;
194
195 pub const V5_PRICE_OUT_OF_RANGE: i32 = 110013;
197
198 pub const V5_REDUCE_ONLY_VIOLATION: i32 = 110017;
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_api_response_success() {
208 let json = r#"{
209 "retCode": 0,
210 "retMsg": "OK",
211 "result": {"value": 123},
212 "time": 1234567890
213 }"#;
214
215 let response: ApiResponse<serde_json::Value> = match serde_json::from_str(json) {
216 Ok(value) => value,
217 Err(err) => panic!("Failed to parse response JSON: {}", err),
218 };
219 assert!(response.is_success());
220 assert_eq!(response.result["value"], 123);
221 }
222
223 #[test]
224 fn test_api_response_error() {
225 let json = r#"{
226 "retCode": 10001,
227 "retMsg": "Param error",
228 "result": {},
229 "time": 1234567890
230 }"#;
231
232 let response: ApiResponse<serde_json::Value> = match serde_json::from_str(json) {
233 Ok(value) => value,
234 Err(err) => panic!("Failed to parse response JSON: {}", err),
235 };
236 assert!(!response.is_success());
237
238 let err = match response.into_result() {
239 Ok(_) => panic!("Expected error response"),
240 Err(err) => err,
241 };
242 assert_eq!(err.api_code(), Some(10001));
243 }
244
245 #[test]
246 fn test_error_retryable() {
247 let api_err = BybitError::api_error(10002, "timeout");
248 assert!(api_err.is_retryable());
249
250 let param_err = BybitError::api_error(10001, "param error");
251 assert!(!param_err.is_retryable());
252 }
253}