1use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
2
3use crate::spot::{
4 AggregateTrade, CurrentAveragePrice, GetAggregateTradesParams, GetCurrentAveragePriceParams,
5 GetKlineListParams, GetOlderTradesParams, GetOrderBookParams, GetRecentTradesParams, Kline,
6 OrderBook, RecentTrade, TestConnectivity,
7};
8
9use super::{
10 Error, ExchangeInfo, GetExchangeInfoParams, Headers, Response, ServerTime,
11 crypto::SensitiveString, serde::deserialize_str, url::*,
12};
13
14pub struct ClientConfig {
15 pub base_url: String,
16 pub api_key: Option<SensitiveString>,
17 pub api_secret: Option<SensitiveString>,
18}
19
20pub struct Client {
21 base_url: String,
22 cfg: ClientConfig,
23}
24
25impl Client {
26 pub fn new(cfg: ClientConfig) -> Self {
27 Self {
28 base_url: cfg.base_url.clone(),
29 cfg,
30 }
31 }
32}
33
34impl Client {
36 pub async fn test_connectivity(&self) -> Result<Response<TestConnectivity>, Error> {
38 let url = format!("{}{}", self.base_url, Path::Time);
39
40 let client = reqwest::Client::builder().build()?;
41 let request = client.request(Method::GET, url);
42
43 let response = send(request).await?;
44 Ok(response)
45 }
46
47 pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
48 let url = format!("{}{}", self.base_url, Path::Time);
49
50 let client = reqwest::Client::builder().build()?;
51 let request = client.request(Method::GET, url);
52
53 let response = send(request).await?;
54 Ok(response)
55 }
56
57 pub async fn get_exchange_info(
58 &self,
59 params: GetExchangeInfoParams,
60 ) -> Result<Response<ExchangeInfo>, Error> {
61 let query = serde_urlencoded::to_string(¶ms)?;
62 let url = format!("{}{}?{query}", self.base_url, Path::ExchangeInfo);
63
64 let client = reqwest::Client::builder().build()?;
65 let request = client.request(Method::GET, url);
66
67 let response = send(request).await?;
68 Ok(response)
69 }
70}
71
72impl Client {
74 pub async fn get_order_book(
75 &self,
76 params: GetOrderBookParams,
77 ) -> Result<Response<OrderBook>, Error> {
78 let query = serde_urlencoded::to_string(¶ms)?;
79 let url = format!("{}{}?{query}", self.base_url, Path::ExchangeInfo);
80
81 let client = reqwest::Client::builder().build()?;
82 let request = client.request(Method::GET, url);
83
84 let response = send(request).await?;
85 Ok(response)
86 }
87
88 pub async fn recent_trades_list(
90 &self,
91 params: GetRecentTradesParams,
92 ) -> Result<Response<Vec<RecentTrade>>, Error> {
93 let query = serde_urlencoded::to_string(¶ms)?;
94 let url = format!("{}{}?{query}", self.base_url, Path::Trades);
95
96 let client = reqwest::Client::builder().build()?;
97 let request = client.request(Method::GET, url);
98
99 let response = send(request).await?;
100 Ok(response)
101 }
102
103 pub async fn old_trade_lookup(
105 &self,
106 params: GetOlderTradesParams,
107 ) -> Result<Response<Vec<RecentTrade>>, Error> {
108 let query = serde_urlencoded::to_string(¶ms)?;
109 let url = format!("{}{}?{query}", self.base_url, Path::HistoricalTrades);
110
111 let client = reqwest::Client::builder().build()?;
112 let request = client.request(Method::GET, url);
113
114 let response = send(request).await?;
115 Ok(response)
116 }
117
118 pub async fn aggregate_trades_list(
123 &self,
124 params: GetAggregateTradesParams,
125 ) -> Result<Response<Vec<AggregateTrade>>, Error> {
126 let query = serde_urlencoded::to_string(¶ms)?;
127 let url = format!("{}{}?{query}", self.base_url, Path::AggTrades);
128
129 let client = reqwest::Client::builder().build()?;
130 let request = client.request(Method::GET, url);
131
132 let response = send(request).await?;
133 Ok(response)
134 }
135
136 pub async fn get_kline_list(
146 &self,
147 params: GetKlineListParams,
148 ) -> Result<Response<Vec<Kline>>, Error> {
149 let query = serde_urlencoded::to_string(¶ms)?;
150 let url = format!("{}{}?{query}", self.base_url, Path::KLines);
151
152 let client = reqwest::Client::builder().build()?;
153 let request = client.request(Method::GET, url);
154
155 let response = send(request).await?;
156 Ok(response)
157 }
158
159 pub async fn get_ui_kline_list(
172 &self,
173 params: GetKlineListParams,
174 ) -> Result<Response<Vec<Kline>>, Error> {
175 let query = serde_urlencoded::to_string(¶ms)?;
176 let url = format!("{}{}?{query}", self.base_url, Path::UIKLines);
177
178 let client = reqwest::Client::builder().build()?;
179 let request = client.request(Method::GET, url);
180
181 let response = send(request).await?;
182 Ok(response)
183 }
184
185 pub async fn get_current_average_price(
187 &self,
188 params: GetCurrentAveragePriceParams,
189 ) -> Result<Response<CurrentAveragePrice>, Error> {
190 let query = serde_urlencoded::to_string(¶ms)?;
191 let url = format!("{}{}?{query}", self.base_url, Path::AvgPrice);
192
193 let client = reqwest::Client::builder().build()?;
194 let request = client.request(Method::GET, url);
195
196 let response = send(request).await?;
197 Ok(response)
198 }
199}
200
201async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
202where
203 T: serde::de::DeserializeOwned,
204{
205 let response = request.send().await?;
206 let headers = parse_headers(&response.headers());
207 let json = response.text().await?;
208
209 let result = deserialize_str(&json)?;
210 let response = Response { result, headers };
211 Ok(response)
212}
213
214fn parse_headers(headers: &HeaderMap) -> Headers {
216 let retry_after = headers
217 .get(HEADER_RETRY_AFTER)
218 .map(|h| h.to_str().unwrap_or_default().parse().ok())
219 .flatten();
220
221 Headers { retry_after }
222}