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