1use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
4use serde::de::DeserializeOwned;
5use serde::Serialize;
6use tracing::{debug, warn};
7
8use crate::auth::{generate_signature, get_timestamp};
9use crate::config::ClientConfig;
10use crate::constants::*;
11use crate::error::{BybitError, Result};
12
13#[derive(Debug, serde::Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct ApiResponse<T> {
17 pub ret_code: i32,
19 pub ret_msg: String,
21 pub result: T,
23 #[serde(default)]
25 #[allow(dead_code)]
26 pub ret_ext_info: serde_json::Value,
27 #[allow(dead_code)]
29 pub time: u64,
30}
31
32#[derive(Debug, Clone)]
34pub struct BybitClient {
35 config: ClientConfig,
36 http: reqwest::Client,
37}
38
39impl BybitClient {
40 pub fn new(config: ClientConfig) -> Result<Self> {
42 let http = reqwest::Client::builder()
43 .timeout(config.timeout)
44 .build()
45 .map_err(BybitError::Http)?;
46
47 Ok(Self { config, http })
48 }
49
50 pub fn with_credentials(
52 api_key: impl Into<String>,
53 api_secret: impl Into<String>,
54 ) -> Result<Self> {
55 let config = ClientConfig::builder(api_key, api_secret).build();
56 Self::new(config)
57 }
58
59 pub fn testnet(api_key: impl Into<String>, api_secret: impl Into<String>) -> Result<Self> {
61 let config = ClientConfig::builder(api_key, api_secret)
62 .base_url(TESTNET)
63 .build();
64 Self::new(config)
65 }
66
67 pub fn demo(api_key: impl Into<String>, api_secret: impl Into<String>) -> Result<Self> {
69 let config = ClientConfig::builder(api_key, api_secret)
70 .base_url(DEMO)
71 .build();
72 Self::new(config)
73 }
74
75 pub fn config(&self) -> &ClientConfig {
77 &self.config
78 }
79
80 pub async fn get_public<T: DeserializeOwned>(
82 &self,
83 endpoint: &str,
84 params: &[(&str, &str)],
85 ) -> Result<T> {
86 let url = format!("{}{}", self.config.base_url, endpoint);
87
88 let response = tokio::time::timeout(
89 self.config.timeout,
90 self.http.get(&url).query(params).send(),
91 )
92 .await
93 .map_err(|_| BybitError::Timeout)?
94 .map_err(BybitError::Http)?;
95
96 self.parse_response(response).await
97 }
98
99 pub async fn get<T: DeserializeOwned>(
101 &self,
102 endpoint: &str,
103 params: &[(&str, &str)],
104 ) -> Result<T> {
105 let url = format!("{}{}", self.config.base_url, endpoint);
106 let timestamp = get_timestamp();
107
108 let query_string = params
110 .iter()
111 .map(|(k, v)| format!("{}={}", k, v))
112 .collect::<Vec<_>>()
113 .join("&");
114
115 let signature = generate_signature(
116 &self.config.api_secret,
117 timestamp,
118 &self.config.api_key,
119 self.config.recv_window,
120 &query_string,
121 );
122
123 let headers = self.build_auth_headers(timestamp, &signature);
124
125 let response = tokio::time::timeout(
126 self.config.timeout,
127 self.http.get(&url).query(params).headers(headers).send(),
128 )
129 .await
130 .map_err(|_| BybitError::Timeout)?
131 .map_err(BybitError::Http)?;
132
133 self.parse_response(response).await
134 }
135
136 pub async fn post<T: DeserializeOwned, B: Serialize>(
138 &self,
139 endpoint: &str,
140 body: &B,
141 ) -> Result<T> {
142 let url = format!("{}{}", self.config.base_url, endpoint);
143 let timestamp = get_timestamp();
144
145 let body_str = serde_json::to_string(body).map_err(|e| BybitError::Parse(e.to_string()))?;
146
147 let signature = generate_signature(
148 &self.config.api_secret,
149 timestamp,
150 &self.config.api_key,
151 self.config.recv_window,
152 &body_str,
153 );
154
155 let headers = self.build_auth_headers(timestamp, &signature);
156
157 if self.config.debug {
158 debug!("POST {} body: {}", url, body_str);
159 }
160
161 let response = tokio::time::timeout(
162 self.config.timeout,
163 self.http
164 .post(&url)
165 .headers(headers)
166 .header(CONTENT_TYPE, "application/json")
167 .body(body_str)
168 .send(),
169 )
170 .await
171 .map_err(|_| BybitError::Timeout)?
172 .map_err(BybitError::Http)?;
173
174 self.parse_response(response).await
175 }
176
177 fn build_auth_headers(&self, timestamp: u64, signature: &str) -> HeaderMap {
179 let mut headers = HeaderMap::new();
180
181 headers.insert(
182 HEADER_API_KEY,
183 HeaderValue::from_str(&self.config.api_key)
184 .unwrap_or_else(|_| HeaderValue::from_static("")),
185 );
186 headers.insert(
187 HEADER_TIMESTAMP,
188 HeaderValue::from_str(×tamp.to_string())
189 .unwrap_or_else(|_| HeaderValue::from_static("0")),
190 );
191 headers.insert(
192 HEADER_SIGN,
193 HeaderValue::from_str(signature).unwrap_or_else(|_| HeaderValue::from_static("")),
194 );
195 headers.insert(HEADER_SIGN_TYPE, HeaderValue::from_static("2"));
196 headers.insert(
197 HEADER_RECV_WINDOW,
198 HeaderValue::from_str(&self.config.recv_window.to_string())
199 .unwrap_or_else(|_| HeaderValue::from_static("5000")),
200 );
201
202 headers
203 }
204
205 async fn parse_response<T: DeserializeOwned>(&self, response: reqwest::Response) -> Result<T> {
207 let status = response.status();
208 let text = response.text().await.map_err(BybitError::Http)?;
209
210 if self.config.debug {
211 debug!("Response status: {}, body: {}", status, text);
212 }
213
214 if !status.is_success() {
215 if let Ok(api_resp) = serde_json::from_str::<ApiResponse<serde_json::Value>>(&text) {
217 return Err(BybitError::Api {
218 code: api_resp.ret_code,
219 msg: api_resp.ret_msg,
220 });
221 }
222 return Err(BybitError::Parse(format!(
223 "HTTP {} - {}",
224 status.as_u16(),
225 text
226 )));
227 }
228
229 let api_resp: ApiResponse<T> = serde_json::from_str(&text).map_err(|e| {
231 warn!("Failed to parse response: {}, body: {}", e, text);
232 BybitError::Parse(format!(
233 "JSON parse error: {} - body: {}",
234 e,
235 &text[..text.len().min(200)]
236 ))
237 })?;
238
239 if api_resp.ret_code != 0 {
241 return Err(BybitError::Api {
242 code: api_resp.ret_code,
243 msg: api_resp.ret_msg,
244 });
245 }
246
247 Ok(api_resp.result)
248 }
249}