use crate::futures::models::market::{
AggTrade, BookTickerShape, DepthResponse, ExchangeInfoResponse, FundingInfoResponse,
FundingRateRecord, Kline, KlineInterval, MarkPriceShape, PingResponse, ServerTimeResponse,
Ticker24hrShape, TickerPriceShape, TradeRecord,
};
use crate::rest::client::RestClient;
use crate::rest::error::AsterDexError;
use crate::rest::response::ApiResponse;
impl RestClient {
pub async fn get_server_time(&self) -> Result<ApiResponse<ServerTimeResponse>, AsterDexError> {
self.get("/fapi/v3/time", &[]).await
}
pub async fn ping(&self) -> Result<ApiResponse<PingResponse>, AsterDexError> {
self.get("/fapi/v3/ping", &[]).await
}
pub async fn get_exchange_info(&self) -> Result<ApiResponse<ExchangeInfoResponse>, AsterDexError> {
self.get("/fapi/v3/exchangeInfo", &[]).await
}
pub async fn get_depth(
&self,
symbol: &str,
limit: Option<u32>,
) -> Result<ApiResponse<DepthResponse>, AsterDexError> {
let limit_str;
let mut params = vec![("symbol", symbol)];
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/depth", ¶ms).await
}
pub async fn get_trades(
&self,
symbol: &str,
limit: Option<u32>,
) -> Result<ApiResponse<Vec<TradeRecord>>, AsterDexError> {
let limit_str;
let mut params = vec![("symbol", symbol)];
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/trades", ¶ms).await
}
pub async fn get_historical_trades(
&self,
symbol: &str,
limit: Option<u32>,
from_id: Option<i64>,
) -> Result<ApiResponse<Vec<TradeRecord>>, AsterDexError> {
let limit_str;
let from_id_str;
let mut params = vec![("symbol", symbol)];
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
if let Some(id) = from_id {
from_id_str = id.to_string();
params.push(("fromId", &from_id_str));
}
self.get("/fapi/v3/historicalTrades", ¶ms).await
}
pub async fn get_agg_trades(
&self,
symbol: &str,
from_id: Option<i64>,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u32>,
) -> Result<ApiResponse<Vec<AggTrade>>, AsterDexError> {
let from_id_str;
let start_time_str;
let end_time_str;
let limit_str;
let mut params = vec![("symbol", symbol)];
if let Some(id) = from_id {
from_id_str = id.to_string();
params.push(("fromId", &from_id_str));
}
if let Some(st) = start_time {
start_time_str = st.to_string();
params.push(("startTime", &start_time_str));
}
if let Some(et) = end_time {
end_time_str = et.to_string();
params.push(("endTime", &end_time_str));
}
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/aggTrades", ¶ms).await
}
pub async fn get_klines(
&self,
symbol: &str,
interval: KlineInterval,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u32>,
) -> Result<ApiResponse<Vec<Kline>>, AsterDexError> {
let start_time_str;
let end_time_str;
let limit_str;
let interval_str = interval.to_str();
let mut params = vec![("symbol", symbol), ("interval", interval_str)];
if let Some(st) = start_time {
start_time_str = st.to_string();
params.push(("startTime", &start_time_str));
}
if let Some(et) = end_time {
end_time_str = et.to_string();
params.push(("endTime", &end_time_str));
}
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/klines", ¶ms).await
}
pub async fn get_index_price_klines(
&self,
pair: &str,
interval: KlineInterval,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u32>,
) -> Result<ApiResponse<Vec<Kline>>, AsterDexError> {
let start_time_str;
let end_time_str;
let limit_str;
let interval_str = interval.to_str();
let mut params = vec![("pair", pair), ("interval", interval_str)];
if let Some(st) = start_time {
start_time_str = st.to_string();
params.push(("startTime", &start_time_str));
}
if let Some(et) = end_time {
end_time_str = et.to_string();
params.push(("endTime", &end_time_str));
}
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/indexPriceKlines", ¶ms).await
}
pub async fn get_mark_price_klines(
&self,
symbol: &str,
interval: KlineInterval,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u32>,
) -> Result<ApiResponse<Vec<Kline>>, AsterDexError> {
let start_time_str;
let end_time_str;
let limit_str;
let interval_str = interval.to_str();
let mut params = vec![("symbol", symbol), ("interval", interval_str)];
if let Some(st) = start_time {
start_time_str = st.to_string();
params.push(("startTime", &start_time_str));
}
if let Some(et) = end_time {
end_time_str = et.to_string();
params.push(("endTime", &end_time_str));
}
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/markPriceKlines", ¶ms).await
}
pub async fn get_mark_price(
&self,
symbol: Option<&str>,
) -> Result<ApiResponse<MarkPriceShape>, AsterDexError> {
let mut params: Vec<(&str, &str)> = vec![];
if let Some(s) = symbol {
params.push(("symbol", s));
}
self.get("/fapi/v3/premiumIndex", ¶ms).await
}
pub async fn get_funding_rate(
&self,
symbol: Option<&str>,
start_time: Option<u64>,
end_time: Option<u64>,
limit: Option<u32>,
) -> Result<ApiResponse<Vec<FundingRateRecord>>, AsterDexError> {
let start_time_str;
let end_time_str;
let limit_str;
let mut params: Vec<(&str, &str)> = vec![];
if let Some(s) = symbol {
params.push(("symbol", s));
}
if let Some(st) = start_time {
start_time_str = st.to_string();
params.push(("startTime", &start_time_str));
}
if let Some(et) = end_time {
end_time_str = et.to_string();
params.push(("endTime", &end_time_str));
}
if let Some(l) = limit {
limit_str = l.to_string();
params.push(("limit", &limit_str));
}
self.get("/fapi/v3/fundingRate", ¶ms).await
}
pub async fn get_funding_info(&self) -> Result<ApiResponse<Vec<FundingInfoResponse>>, AsterDexError> {
self.get("/fapi/v3/fundingInfo", &[]).await
}
pub async fn get_ticker_24hr(
&self,
symbol: Option<&str>,
) -> Result<ApiResponse<Ticker24hrShape>, AsterDexError> {
let mut params: Vec<(&str, &str)> = vec![];
if let Some(s) = symbol {
params.push(("symbol", s));
}
self.get("/fapi/v3/ticker/24hr", ¶ms).await
}
pub async fn get_ticker_price(
&self,
symbol: Option<&str>,
) -> Result<ApiResponse<TickerPriceShape>, AsterDexError> {
let mut params: Vec<(&str, &str)> = vec![];
if let Some(s) = symbol {
params.push(("symbol", s));
}
self.get("/fapi/v3/ticker/price", ¶ms).await
}
pub async fn get_book_ticker(
&self,
symbol: Option<&str>,
) -> Result<ApiResponse<BookTickerShape>, AsterDexError> {
let mut params: Vec<(&str, &str)> = vec![];
if let Some(s) = symbol {
params.push(("symbol", s));
}
self.get("/fapi/v3/ticker/bookTicker", ¶ms).await
}
pub async fn get_index_references(
&self,
symbol: Option<&str>,
) -> Result<ApiResponse<serde_json::Value>, AsterDexError> {
let mut params: Vec<(&str, &str)> = vec![];
if let Some(s) = symbol {
params.push(("symbol", s));
}
self.get("/fapi/v3/indexreferences", ¶ms).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::client::RestClient;
#[tokio::test]
async fn ping_returns_ok() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/fapi/v3/ping")
.with_status(200)
.with_header("content-type", "application/json")
.with_body("{}")
.create_async()
.await;
let client = RestClient::new_public(&server.url()).unwrap();
let resp = client.ping().await.unwrap();
let _ = resp.data; }
#[tokio::test]
async fn get_depth_returns_bids_asks() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/fapi/v3/depth")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("symbol".to_string(), "BTCUSDT".to_string()),
mockito::Matcher::UrlEncoded("limit".to_string(), "20".to_string()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"lastUpdateId":12345,"bids":[["45000.00","1.5"]],"asks":[["45001.00","0.5"]]}"#)
.create_async()
.await;
let client = RestClient::new_public(&server.url()).unwrap();
let resp = client.get_depth("BTCUSDT", Some(20)).await.unwrap();
assert_eq!(resp.data.last_update_id, 12345);
assert!(!resp.data.bids.is_empty());
assert!(!resp.data.asks.is_empty());
}
#[tokio::test]
async fn get_klines_returns_ohlcv() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/fapi/v3/klines")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("symbol".to_string(), "BTCUSDT".to_string()),
mockito::Matcher::UrlEncoded("interval".to_string(), "1m".to_string()),
mockito::Matcher::UrlEncoded("limit".to_string(), "1".to_string()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"[[1700000000000,"45000","46000","44000","45500","100.5",1700000059999,"4550000",500,"50.5","2275000"]]"#)
.create_async()
.await;
let client = RestClient::new_public(&server.url()).unwrap();
let resp = client
.get_klines("BTCUSDT", KlineInterval::OneMinute, None, None, Some(1))
.await
.unwrap();
assert_eq!(resp.data[0].open_time, 1_700_000_000_000u64);
}
#[tokio::test]
async fn get_ticker_24hr_returns_json() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/fapi/v3/ticker/24hr")
.match_query(mockito::Matcher::UrlEncoded(
"symbol".to_string(),
"BTCUSDT".to_string(),
))
.with_status(200)
.with_header("content-type", "application/json")
.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}"#)
.create_async()
.await;
let client = RestClient::new_public(&server.url()).unwrap();
let resp = client.get_ticker_24hr(Some("BTCUSDT")).await;
assert!(resp.is_ok());
}
#[tokio::test]
async fn public_endpoint_no_auth_params() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/fapi/v3/depth")
.match_query(mockito::Matcher::UrlEncoded(
"symbol".to_string(),
"BTCUSDT".to_string(),
))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"lastUpdateId":1,"bids":[],"asks":[]}"#)
.create_async()
.await;
let client = RestClient::new_public(&server.url()).unwrap();
let result = client.get_depth("BTCUSDT", None).await;
assert!(
result.is_ok(),
"Expected Ok but got error — auth params may have been injected: {:?}",
result
);
}
}