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