1use crate::futures::models::market::{
6 AggTrade, BookTickerShape, DepthResponse, ExchangeInfoResponse, FundingInfoResponse,
7 FundingRateRecord, IndexReferencesResponse, Kline, KlineInterval, MarkPriceShape,
8 PingResponse, ServerTimeResponse, Ticker24hrShape, TickerPriceShape, TradeRecord,
9};
10use crate::rest::client::RestClient;
11use crate::rest::error::AsterDexError;
12use crate::rest::response::ApiResponse;
13
14impl RestClient {
15 pub async fn get_server_time(&self) -> Result<ApiResponse<ServerTimeResponse>, AsterDexError> {
20 self.get("/fapi/v3/time", &[]).await
21 }
22
23 pub async fn ping(&self) -> Result<ApiResponse<PingResponse>, AsterDexError> {
31 self.get("/fapi/v3/ping", &[]).await
32 }
33
34 pub async fn get_exchange_info(&self) -> Result<ApiResponse<ExchangeInfoResponse>, AsterDexError> {
38 self.get("/fapi/v3/exchangeInfo", &[]).await
39 }
40
41 pub async fn get_depth(
45 &self,
46 symbol: &str,
47 limit: Option<u32>,
48 ) -> Result<ApiResponse<DepthResponse>, AsterDexError> {
49 let limit_str;
50 let mut params = vec![("symbol", symbol)];
51 if let Some(l) = limit {
52 limit_str = l.to_string();
53 params.push(("limit", &limit_str));
54 }
55 self.get("/fapi/v3/depth", ¶ms).await
56 }
57
58 pub async fn get_trades(
62 &self,
63 symbol: &str,
64 limit: Option<u32>,
65 ) -> Result<ApiResponse<Vec<TradeRecord>>, AsterDexError> {
66 let limit_str;
67 let mut params = vec![("symbol", symbol)];
68 if let Some(l) = limit {
69 limit_str = l.to_string();
70 params.push(("limit", &limit_str));
71 }
72 self.get("/fapi/v3/trades", ¶ms).await
73 }
74
75 pub async fn get_historical_trades(
79 &self,
80 symbol: &str,
81 limit: Option<u32>,
82 from_id: Option<i64>,
83 ) -> Result<ApiResponse<Vec<TradeRecord>>, AsterDexError> {
84 let limit_str;
85 let from_id_str;
86 let mut params = vec![("symbol", symbol)];
87 if let Some(l) = limit {
88 limit_str = l.to_string();
89 params.push(("limit", &limit_str));
90 }
91 if let Some(id) = from_id {
92 from_id_str = id.to_string();
93 params.push(("fromId", &from_id_str));
94 }
95 self.get("/fapi/v3/historicalTrades", ¶ms).await
96 }
97
98 pub async fn get_agg_trades(
102 &self,
103 symbol: &str,
104 from_id: Option<i64>,
105 start_time: Option<u64>,
106 end_time: Option<u64>,
107 limit: Option<u32>,
108 ) -> Result<ApiResponse<Vec<AggTrade>>, AsterDexError> {
109 let from_id_str;
110 let start_time_str;
111 let end_time_str;
112 let limit_str;
113 let mut params = vec![("symbol", symbol)];
114 if let Some(id) = from_id {
115 from_id_str = id.to_string();
116 params.push(("fromId", &from_id_str));
117 }
118 if let Some(st) = start_time {
119 start_time_str = st.to_string();
120 params.push(("startTime", &start_time_str));
121 }
122 if let Some(et) = end_time {
123 end_time_str = et.to_string();
124 params.push(("endTime", &end_time_str));
125 }
126 if let Some(l) = limit {
127 limit_str = l.to_string();
128 params.push(("limit", &limit_str));
129 }
130 self.get("/fapi/v3/aggTrades", ¶ms).await
131 }
132
133 pub async fn get_klines(
137 &self,
138 symbol: &str,
139 interval: KlineInterval,
140 start_time: Option<u64>,
141 end_time: Option<u64>,
142 limit: Option<u32>,
143 ) -> Result<ApiResponse<Vec<Kline>>, AsterDexError> {
144 let start_time_str;
145 let end_time_str;
146 let limit_str;
147 let interval_str = interval.to_str();
148 let mut params = vec![("symbol", symbol), ("interval", interval_str)];
149 if let Some(st) = start_time {
150 start_time_str = st.to_string();
151 params.push(("startTime", &start_time_str));
152 }
153 if let Some(et) = end_time {
154 end_time_str = et.to_string();
155 params.push(("endTime", &end_time_str));
156 }
157 if let Some(l) = limit {
158 limit_str = l.to_string();
159 params.push(("limit", &limit_str));
160 }
161 self.get("/fapi/v3/klines", ¶ms).await
162 }
163
164 pub async fn get_index_price_klines(
168 &self,
169 pair: &str,
170 interval: KlineInterval,
171 start_time: Option<u64>,
172 end_time: Option<u64>,
173 limit: Option<u32>,
174 ) -> Result<ApiResponse<Vec<Kline>>, AsterDexError> {
175 let start_time_str;
176 let end_time_str;
177 let limit_str;
178 let interval_str = interval.to_str();
179 let mut params = vec![("pair", pair), ("interval", interval_str)];
180 if let Some(st) = start_time {
181 start_time_str = st.to_string();
182 params.push(("startTime", &start_time_str));
183 }
184 if let Some(et) = end_time {
185 end_time_str = et.to_string();
186 params.push(("endTime", &end_time_str));
187 }
188 if let Some(l) = limit {
189 limit_str = l.to_string();
190 params.push(("limit", &limit_str));
191 }
192 self.get("/fapi/v3/indexPriceKlines", ¶ms).await
193 }
194
195 pub async fn get_mark_price_klines(
199 &self,
200 symbol: &str,
201 interval: KlineInterval,
202 start_time: Option<u64>,
203 end_time: Option<u64>,
204 limit: Option<u32>,
205 ) -> Result<ApiResponse<Vec<Kline>>, AsterDexError> {
206 let start_time_str;
207 let end_time_str;
208 let limit_str;
209 let interval_str = interval.to_str();
210 let mut params = vec![("symbol", symbol), ("interval", interval_str)];
211 if let Some(st) = start_time {
212 start_time_str = st.to_string();
213 params.push(("startTime", &start_time_str));
214 }
215 if let Some(et) = end_time {
216 end_time_str = et.to_string();
217 params.push(("endTime", &end_time_str));
218 }
219 if let Some(l) = limit {
220 limit_str = l.to_string();
221 params.push(("limit", &limit_str));
222 }
223 self.get("/fapi/v3/markPriceKlines", ¶ms).await
224 }
225
226 pub async fn get_mark_price(
230 &self,
231 symbol: Option<&str>,
232 ) -> Result<ApiResponse<MarkPriceShape>, AsterDexError> {
233 let mut params: Vec<(&str, &str)> = vec![];
234 if let Some(s) = symbol {
235 params.push(("symbol", s));
236 }
237 self.get("/fapi/v3/premiumIndex", ¶ms).await
238 }
239
240 pub async fn get_funding_rate(
244 &self,
245 symbol: Option<&str>,
246 start_time: Option<u64>,
247 end_time: Option<u64>,
248 limit: Option<u32>,
249 ) -> Result<ApiResponse<Vec<FundingRateRecord>>, AsterDexError> {
250 let start_time_str;
251 let end_time_str;
252 let limit_str;
253 let mut params: Vec<(&str, &str)> = vec![];
254 if let Some(s) = symbol {
255 params.push(("symbol", s));
256 }
257 if let Some(st) = start_time {
258 start_time_str = st.to_string();
259 params.push(("startTime", &start_time_str));
260 }
261 if let Some(et) = end_time {
262 end_time_str = et.to_string();
263 params.push(("endTime", &end_time_str));
264 }
265 if let Some(l) = limit {
266 limit_str = l.to_string();
267 params.push(("limit", &limit_str));
268 }
269 self.get("/fapi/v3/fundingRate", ¶ms).await
270 }
271
272 pub async fn get_funding_info(&self) -> Result<ApiResponse<Vec<FundingInfoResponse>>, AsterDexError> {
276 self.get("/fapi/v3/fundingInfo", &[]).await
277 }
278
279 pub async fn get_ticker_24hr(
283 &self,
284 symbol: Option<&str>,
285 ) -> Result<ApiResponse<Ticker24hrShape>, AsterDexError> {
286 let mut params: Vec<(&str, &str)> = vec![];
287 if let Some(s) = symbol {
288 params.push(("symbol", s));
289 }
290 self.get("/fapi/v3/ticker/24hr", ¶ms).await
291 }
292
293 pub async fn get_ticker_price(
297 &self,
298 symbol: Option<&str>,
299 ) -> Result<ApiResponse<TickerPriceShape>, AsterDexError> {
300 let mut params: Vec<(&str, &str)> = vec![];
301 if let Some(s) = symbol {
302 params.push(("symbol", s));
303 }
304 self.get("/fapi/v3/ticker/price", ¶ms).await
305 }
306
307 pub async fn get_book_ticker(
311 &self,
312 symbol: Option<&str>,
313 ) -> Result<ApiResponse<BookTickerShape>, AsterDexError> {
314 let mut params: Vec<(&str, &str)> = vec![];
315 if let Some(s) = symbol {
316 params.push(("symbol", s));
317 }
318 self.get("/fapi/v3/ticker/bookTicker", ¶ms).await
319 }
320
321 pub async fn get_index_references(
325 &self,
326 symbol: Option<&str>,
327 ) -> Result<ApiResponse<Vec<IndexReferencesResponse>>, AsterDexError> {
328 let mut params: Vec<(&str, &str)> = vec![];
329 if let Some(s) = symbol {
330 params.push(("symbol", s));
331 }
332 self.get("/fapi/v3/indexreferences", ¶ms).await
333 }
334}
335
336#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate::rest::client::RestClient;
344
345 #[tokio::test]
347 async fn ping_returns_ok() {
348 let mut server = mockito::Server::new_async().await;
349 let _mock = server
350 .mock("GET", "/fapi/v3/ping")
351 .with_status(200)
352 .with_header("content-type", "application/json")
353 .with_body("{}")
354 .create_async()
355 .await;
356 let client = RestClient::new_public(&server.url()).unwrap();
357 let resp = client.ping().await.unwrap();
358 let _ = resp.data; }
360
361 #[tokio::test]
363 async fn get_depth_returns_bids_asks() {
364 let mut server = mockito::Server::new_async().await;
365 let _mock = server
366 .mock("GET", "/fapi/v3/depth")
367 .match_query(mockito::Matcher::AllOf(vec![
368 mockito::Matcher::UrlEncoded("symbol".to_string(), "BTCUSDT".to_string()),
369 mockito::Matcher::UrlEncoded("limit".to_string(), "20".to_string()),
370 ]))
371 .with_status(200)
372 .with_header("content-type", "application/json")
373 .with_body(r#"{"lastUpdateId":12345,"bids":[["45000.00","1.5"]],"asks":[["45001.00","0.5"]]}"#)
374 .create_async()
375 .await;
376 let client = RestClient::new_public(&server.url()).unwrap();
377 let resp = client.get_depth("BTCUSDT", Some(20)).await.unwrap();
378 assert_eq!(resp.data.last_update_id, 12345);
379 assert!(!resp.data.bids.is_empty());
380 assert!(!resp.data.asks.is_empty());
381 }
382
383 #[tokio::test]
385 async fn get_klines_returns_ohlcv() {
386 let mut server = mockito::Server::new_async().await;
387 let _mock = server
388 .mock("GET", "/fapi/v3/klines")
389 .match_query(mockito::Matcher::AllOf(vec![
390 mockito::Matcher::UrlEncoded("symbol".to_string(), "BTCUSDT".to_string()),
391 mockito::Matcher::UrlEncoded("interval".to_string(), "1m".to_string()),
392 mockito::Matcher::UrlEncoded("limit".to_string(), "1".to_string()),
393 ]))
394 .with_status(200)
395 .with_header("content-type", "application/json")
396 .with_body(r#"[[1700000000000,"45000","46000","44000","45500","100.5",1700000059999,"4550000",500,"50.5","2275000"]]"#)
397 .create_async()
398 .await;
399 let client = RestClient::new_public(&server.url()).unwrap();
400 let resp = client
401 .get_klines("BTCUSDT", KlineInterval::OneMinute, None, None, Some(1))
402 .await
403 .unwrap();
404 assert_eq!(resp.data[0].open_time, 1_700_000_000_000u64);
405 }
406
407 #[tokio::test]
409 async fn get_ticker_24hr_returns_json() {
410 let mut server = mockito::Server::new_async().await;
411 let _mock = server
412 .mock("GET", "/fapi/v3/ticker/24hr")
413 .match_query(mockito::Matcher::UrlEncoded(
414 "symbol".to_string(),
415 "BTCUSDT".to_string(),
416 ))
417 .with_status(200)
418 .with_header("content-type", "application/json")
419 .with_body(r#"{"symbol":"BTCUSDT","lastPrice":"45000.00","priceChange":"100.00","priceChangePercent":"0.22","weightedAvgPrice":"44950.00","prevClosePrice":"44900.00","lastQty":"1.0","openPrice":"44900.00","highPrice":"46000.00","lowPrice":"44500.00","volume":"5000.0","quoteVolume":"224750000.0","openTime":1699999200000,"closeTime":1700085600000,"firstId":1,"lastId":5000,"count":5000}"#)
420 .create_async()
421 .await;
422 let client = RestClient::new_public(&server.url()).unwrap();
423 let resp = client.get_ticker_24hr(Some("BTCUSDT")).await;
424 assert!(resp.is_ok());
425 }
426
427 #[tokio::test]
432 async fn public_endpoint_no_auth_params() {
433 let mut server = mockito::Server::new_async().await;
434 let _mock = server
435 .mock("GET", "/fapi/v3/depth")
436 .match_query(mockito::Matcher::UrlEncoded(
437 "symbol".to_string(),
438 "BTCUSDT".to_string(),
439 ))
440 .with_status(200)
441 .with_header("content-type", "application/json")
442 .with_body(r#"{"lastUpdateId":1,"bids":[],"asks":[]}"#)
443 .create_async()
444 .await;
445 let client = RestClient::new_public(&server.url()).unwrap();
446 let result = client.get_depth("BTCUSDT", None).await;
449 assert!(
450 result.is_ok(),
451 "Expected Ok but got error — auth params may have been injected: {:?}",
452 result
453 );
454 }
455}