asterdex-sdk 0.1.1

AsterDex Futures SDK v3 — Rust async client for REST and WebSocket APIs
Documentation
// US-009: Batch order endpoints

use crate::futures::models::account::{BatchOrderResult, CancelAllResponse};
use crate::futures::models::trading::PlaceOrderParams;
use crate::rest::client::RestClient;
use crate::rest::error::AsterDexError;
use crate::rest::response::ApiResponse;

const BATCH_ORDER_MAX: usize = 5;

impl RestClient {
    /// Place multiple orders (max 5). Orders sent as JSON body.
    /// Returns a mixed array — check each item for order_id (success) or code/msg (error).
    pub async fn place_batch_orders(
        &self,
        orders: Vec<PlaceOrderParams>,
    ) -> Result<ApiResponse<Vec<BatchOrderResult>>, AsterDexError> {
        if orders.len() > BATCH_ORDER_MAX {
            return Err(AsterDexError::InvalidParams {
                message: format!("Too many orders: max {BATCH_ORDER_MAX}, got {}", orders.len()),
            });
        }
        // Serialize directly from the Vec — no `serde_json::Value` intermediate DOM.
        self.signed_post_json("/fapi/v3/batchOrders", &[], &orders).await
    }

    /// Cancel multiple orders by ID list.
    pub async fn cancel_batch_orders(
        &self,
        symbol: &str,
        order_ids: Vec<i64>,
    ) -> Result<ApiResponse<Vec<BatchOrderResult>>, AsterDexError> {
        let ids_json = serde_json::to_string(&order_ids).map_err(|e| AsterDexError::SerdeError {
            message: e.to_string(),
        })?;
        self.signed_delete("/fapi/v3/batchOrders", &[
            ("symbol", symbol),
            ("orderIdList", &ids_json),
        ]).await
    }

    /// Cancel all open orders for a symbol.
    pub async fn cancel_all_open_orders(
        &self,
        symbol: &str,
    ) -> Result<ApiResponse<CancelAllResponse>, AsterDexError> {
        self.signed_delete("/fapi/v3/allOpenOrders", &[("symbol", symbol)]).await
    }
}

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

    // US-009 test 1: place_batch_orders with 6 orders returns InvalidParams before network call
    // (formerly ApiError -1111; reclassified — this is a client-side validation failure,
    // not an exchange-originated error)
    #[tokio::test]
    async fn place_batch_orders_too_many_returns_invalid_params() {
        let client = RestClient::new_public("http://localhost:9999").unwrap();
        let orders: Vec<PlaceOrderParams> = (0..6).map(|_| PlaceOrderParams::default()).collect();
        let result = client.place_batch_orders(orders).await;
        assert!(
            matches!(result, Err(AsterDexError::InvalidParams { .. })),
            "expected InvalidParams, got: {:?}",
            result
        );
    }

    // US-009 test 2: cancel_all_open_orders with mock returns code == 200
    #[tokio::test]
    async fn cancel_all_open_orders_mock() {
        let mut server = mockito::Server::new_async().await;
        let _mock = server
            .mock("DELETE", "/fapi/v3/allOpenOrders")
            .match_query(mockito::Matcher::Any)
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"code":200,"msg":"The operation of cancel orders is done."}"#)
            .create_async()
            .await;

        let creds = Credentials::new(
            "0x0000000000000000000000000000000000000001",
            "0x0000000000000000000000000000000000000002",
            "0000000000000000000000000000000000000000000000000000000000000001",
            714,
        )
        .unwrap();
        let client = RestClient::new(&server.url(), creds).unwrap();
        let resp = client.cancel_all_open_orders("BTCUSDT").await.unwrap();
        assert_eq!(resp.data.code, 200);
    }
}