mexc-rs 0.3.0

Rust client for the mexc.com exchange
Documentation
use async_trait::async_trait;
use rust_decimal::Decimal;
use chrono::{DateTime, Utc};
use crate::spot::{MexcSpotApiClient, MexcSpotApiClientWithAuthentication, MexcSpotApiEndpoint};
use crate::spot::v3::{ApiResponse, ApiResult};
use crate::spot::v3::enums::OrderType;

#[derive(Debug)]
pub enum ExchangeInformationParams<'a> {
    None,
    Symbol(&'a str),
    Symbols(&'a [&'a str]),
}

#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExchangeInformationSymbol {
    pub symbol: String,
    pub status: String,
    pub base_asset: String,
    pub base_asset_precision: i32,
    pub quote_asset: String,
    pub quote_precision: i32,
    pub quote_asset_precision: i32,
    pub base_commission_precision: i32,
    pub quote_commission_precision: i32,
    pub order_types: Vec<OrderType>,
    pub quote_order_qty_market_allowed: Option<bool>,
    pub is_spot_trading_allowed: bool,
    pub is_margin_trading_allowed: bool,
    pub quote_amount_precision: Decimal,
    pub base_size_precision: Decimal,
    pub permissions: Vec<String>,
    pub filters: Vec<serde_json::Value>,
    pub max_quote_amount: Decimal,
    pub maker_commission: Decimal,
    pub taker_commission: Decimal,
}


#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExchangeInformationOutput {
    pub timezone: String,
    #[serde(with = "chrono::serde::ts_seconds")]
    pub server_time: DateTime<Utc>,
    pub rate_limits: Vec<serde_json::Value>,
    pub exchange_filters: Vec<serde_json::Value>,
    pub symbols: Vec<ExchangeInformationSymbol>,
}

#[derive(Debug, serde::Serialize)]
pub struct ExchangeInformationEndpointQueryParams<'a> {
    pub symbol: Option<&'a str>,
    pub symbols: Option<String>,
}

impl<'a> From<ExchangeInformationParams<'a>> for ExchangeInformationEndpointQueryParams<'a> {
    fn from(value: ExchangeInformationParams<'a>) -> Self {
        match value {
            ExchangeInformationParams::None => Self {
                symbol: None,
                symbols: None,
            },
            ExchangeInformationParams::Symbol(symbol) => Self {
                symbol: Some(symbol),
                symbols: None,
            },
            ExchangeInformationParams::Symbols(symbols) => Self {
                symbol: None,
                symbols: Some(symbols.join(",")),
            },
        }
    }
}

#[async_trait]
pub trait ExchangeInformationEndpoint {
    async fn exchange_information(&self, params: ExchangeInformationParams<'_>) -> ApiResult<ExchangeInformationOutput>;
}

async fn exchange_information_impl(
    endpoint: &MexcSpotApiEndpoint,
    client: &reqwest::Client,
    params: ExchangeInformationParams<'_>,
) -> ApiResult<ExchangeInformationOutput> {
    let endpoint = format!("{}/api/v3/exchangeInfo", endpoint.as_ref());
    let query_params = ExchangeInformationEndpointQueryParams::from(params);
    let response = client.get(&endpoint).query(&query_params).send().await?;
    let api_response = response.json::<ApiResponse<ExchangeInformationOutput>>().await?;
    let output = api_response.into_api_result()?;

    Ok(output)
}

#[async_trait]
impl ExchangeInformationEndpoint for MexcSpotApiClient {
    async fn exchange_information(&self, params: ExchangeInformationParams<'_>) -> ApiResult<ExchangeInformationOutput> {
        exchange_information_impl(&self.endpoint, &self.reqwest_client, params).await
    }
}

#[async_trait]
impl ExchangeInformationEndpoint for MexcSpotApiClientWithAuthentication {
    async fn exchange_information(&self, params: ExchangeInformationParams<'_>) -> ApiResult<ExchangeInformationOutput> {
        exchange_information_impl(&self.endpoint, &self.reqwest_client, params).await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_no_params() {
        let client = MexcSpotApiClient::default();
        let params = ExchangeInformationParams::None;
        let result = client.exchange_information(params).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_single_symbol() {
        let client = MexcSpotApiClient::default();
        let params = ExchangeInformationParams::Symbol("BTCUSDT");
        let result = client.exchange_information(params).await;
        assert!(result.is_ok());
        let output = result.unwrap();
        assert_eq!(output.symbols.len(), 1);
        let first_symbol = &output.symbols[0];
        assert_eq!(first_symbol.symbol, "BTCUSDT");
    }

    #[tokio::test]
    async fn test_multiple_symbols() {
        let client = MexcSpotApiClient::default();
        let params = ExchangeInformationParams::Symbols(&["BTCUSDT", "ETHUSDT"]);
        let result = client.exchange_information(params).await;
        assert!(result.is_ok());
        let output = result.unwrap();
        assert_eq!(output.symbols.len(), 2);
    }
}