bybit_rust_api/rest/
client.rs

1use crate::consts::{
2    API_REQUEST_KEY, RECV_WINDOW_KEY, SIGNATURE_KEY, SIGN_TYPE_KEY, TIMESTAMP_KEY,
3};
4use crate::rest::api_key_pair::ApiKeyPair;
5use crate::rest::errors::{BybitResult, ErrorCodes};
6use crate::utils::{millis, sign};
7use reqwest::RequestBuilder;
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum SecType {
13    None = 0,
14    Signed = 1,
15}
16
17#[derive(Clone)]
18pub struct RestClient {
19    api_key_pair: ApiKeyPair,
20    base_url: String,
21
22    http_client: reqwest::Client,
23    recv_window: String,
24}
25
26#[derive(Serialize, Deserialize, Debug, Clone)]
27pub struct ServerResponse<T> {
28    #[serde(rename = "retCode")]
29    pub ret_code: i32,
30    #[serde(rename = "retMsg")]
31    pub ret_msg: String,
32    #[serde(rename = "result")]
33    pub result: T,
34    #[serde(rename = "retExtInfo")]
35    pub ret_ext_info: serde_json::Value,
36    #[serde(rename = "time")]
37    pub time: i64,
38}
39
40#[derive(Serialize, Deserialize, Debug, Clone)]
41struct RawServerResponse {
42    #[serde(rename = "retCode")]
43    pub ret_code: i32,
44    #[serde(rename = "retMsg")]
45    pub ret_msg: String,
46    #[serde(rename = "result")]
47    pub result: serde_json::Value,
48    #[serde(rename = "retExtInfo")]
49    pub ret_ext_info: serde_json::Value,
50    #[serde(rename = "time")]
51    pub time: i64,
52}
53
54impl RestClient {
55    pub fn new(api_key_pair: ApiKeyPair, base_url: String) -> RestClient {
56        RestClient {
57            api_key_pair,
58            base_url,
59            http_client: reqwest::Client::new(),
60            recv_window: "5000".to_string(),
61        }
62    }
63
64    pub fn with_recv_window(mut self, recv_window: impl Into<String>) -> Self {
65        self.recv_window = recv_window.into();
66        self
67    }
68
69    fn query_string(&self, query: serde_json::Value) -> BybitResult<String> {
70        let object = query.as_object().ok_or_else(|| {
71            crate::rest::errors::BybitError::Internal(
72                "Query params must be a JSON object".to_string(),
73            )
74        })?;
75        Ok(serde_urlencoded::to_string(object)?)
76    }
77
78    fn sign_request(
79        &self,
80        request_builder: RequestBuilder,
81        query_or_body_param: String,
82    ) -> RequestBuilder {
83        let recv_window = &self.recv_window;
84        let timestamp_str = millis().to_string();
85        let signature = sign(
86            &self.api_key_pair.secret(),
87            &format!(
88                "{}{}{}{}",
89                timestamp_str,
90                self.api_key_pair.key(),
91                recv_window,
92                query_or_body_param
93            ),
94        );
95
96        request_builder
97            .header(SIGN_TYPE_KEY, "2")
98            .header(API_REQUEST_KEY, self.api_key_pair.key())
99            .header(TIMESTAMP_KEY, timestamp_str)
100            .header(RECV_WINDOW_KEY, recv_window)
101            .header(SIGNATURE_KEY, signature)
102    }
103
104    pub async fn get<A: DeserializeOwned>(
105        &self,
106        endpoint: &str,
107        query: serde_json::Value,
108        sec_type: SecType,
109    ) -> BybitResult<ServerResponse<A>> {
110        let mut url = format!("{}/{}", self.base_url, endpoint);
111        let query_string = self.query_string(query)?;
112
113        if !query_string.is_empty() {
114            url.push_str(&format!("?{}", query_string));
115        }
116
117        let mut request_builder = self.http_client.get(&url);
118        if sec_type == SecType::Signed {
119            request_builder = self.sign_request(request_builder, query_string);
120        }
121
122        log::debug!("url: {}", url);
123
124        let r = request_builder.send().await?;
125        let raw_response: RawServerResponse = r.json().await?;
126
127        if raw_response.ret_code != 0 {
128            let code_str = raw_response.ret_code.to_string();
129            // Try to parse into known ErrorCodes, otherwise fallback to generic
130            if let Ok(error_code) = serde_json::from_value(serde_json::json!(code_str)) {
131                return Err(crate::rest::errors::BybitError::Api(error_code));
132            } else {
133                return Err(crate::rest::errors::BybitError::Api(
134                    crate::rest::errors::ErrorCodes::E10001,
135                )); // System error fallback or similar
136            }
137        }
138
139        let result: A = serde_json::from_value(raw_response.result)?;
140        Ok(ServerResponse {
141            ret_code: raw_response.ret_code,
142            ret_msg: raw_response.ret_msg,
143            result,
144            ret_ext_info: raw_response.ret_ext_info,
145            time: raw_response.time,
146        })
147    }
148
149    pub async fn post<A: DeserializeOwned>(
150        &self,
151        endpoint: &str,
152        body: serde_json::Value,
153        sec_type: SecType,
154    ) -> BybitResult<ServerResponse<A>> {
155        let url = format!("{}/{}", self.base_url, endpoint);
156        let mut request_builder = self.http_client.post(&url);
157        if sec_type == SecType::Signed {
158            let body_str =
159                serde_json::to_string(&body).map_err(crate::rest::errors::BybitError::Json)?;
160            request_builder = self.sign_request(request_builder, body_str);
161        }
162
163        let r = request_builder.json(&body).send().await?;
164        let raw_response: RawServerResponse = r.json().await?;
165
166        if raw_response.ret_code != 0 {
167            let code_str = raw_response.ret_code.to_string();
168            if let Ok(error_code) = serde_json::from_value(serde_json::json!(code_str)) {
169                return Err(crate::rest::errors::BybitError::Api(error_code));
170            } else {
171                return Err(crate::rest::errors::BybitError::Api(ErrorCodes::E10001));
172            }
173        }
174
175        let result: A = serde_json::from_value(raw_response.result)?;
176        Ok(ServerResponse {
177            ret_code: raw_response.ret_code,
178            ret_msg: raw_response.ret_msg,
179            result,
180            ret_ext_info: raw_response.ret_ext_info,
181            time: raw_response.time,
182        })
183    }
184}