use serde::Deserialize;
use ustr::Ustr;
use crate::common::enums::{BinanceOrderStatus, BinanceSide, BinanceTimeInForce};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceSpotExecutionType {
New,
Canceled,
Replaced,
Rejected,
Trade,
Expired,
TradePrevention,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BinanceSpotExecutionReport {
#[serde(rename = "e")]
pub event_type: String,
#[serde(rename = "E")]
pub event_time: i64,
#[serde(rename = "s")]
pub symbol: Ustr,
#[serde(rename = "c")]
pub client_order_id: String,
#[serde(rename = "S")]
pub side: BinanceSide,
#[serde(rename = "o")]
pub order_type: String,
#[serde(rename = "f")]
pub time_in_force: BinanceTimeInForce,
#[serde(rename = "q")]
pub original_qty: String,
#[serde(rename = "p")]
pub price: String,
#[serde(rename = "P")]
pub stop_price: String,
#[serde(rename = "x")]
pub execution_type: BinanceSpotExecutionType,
#[serde(rename = "X")]
pub order_status: BinanceOrderStatus,
#[serde(rename = "r")]
pub reject_reason: String,
#[serde(rename = "i")]
pub order_id: i64,
#[serde(rename = "l")]
pub last_filled_qty: String,
#[serde(rename = "z")]
pub cumulative_filled_qty: String,
#[serde(rename = "L")]
pub last_filled_price: String,
#[serde(rename = "n")]
pub commission: String,
#[serde(rename = "N", default)]
pub commission_asset: Option<Ustr>,
#[serde(rename = "T")]
pub transaction_time: i64,
#[serde(rename = "t")]
pub trade_id: i64,
#[serde(rename = "w")]
pub is_working: bool,
#[serde(rename = "m")]
pub is_maker: bool,
#[serde(rename = "O")]
pub order_creation_time: i64,
#[serde(rename = "Z")]
pub cumulative_quote_qty: String,
#[serde(rename = "C", default)]
pub original_client_order_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BinanceSpotAccountPositionMsg {
#[serde(rename = "e")]
pub event_type: String,
#[serde(rename = "E")]
pub event_time: i64,
#[serde(rename = "u")]
pub last_update_time: i64,
#[serde(rename = "B")]
pub balances: Vec<BinanceSpotBalanceEntry>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BinanceSpotBalanceEntry {
#[serde(rename = "a")]
pub asset: Ustr,
#[serde(rename = "f")]
pub free: String,
#[serde(rename = "l")]
pub locked: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BinanceSpotBalanceUpdateMsg {
#[serde(rename = "e")]
pub event_type: String,
#[serde(rename = "E")]
pub event_time: i64,
#[serde(rename = "a")]
pub asset: Ustr,
#[serde(rename = "d")]
pub delta: String,
#[serde(rename = "T")]
pub clear_time: i64,
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::common::testing::{load_event_fixture, load_fixture_string};
#[rstest]
fn test_deserialize_execution_report_new() {
let json = load_event_fixture("spot/user_data_json/execution_report_wrapped.json");
let msg: BinanceSpotExecutionReport = serde_json::from_value(json).unwrap();
assert_eq!(msg.event_type, "executionReport");
assert_eq!(msg.symbol.as_str(), "ETHBTC");
assert_eq!(msg.execution_type, BinanceSpotExecutionType::New);
assert_eq!(msg.order_status, BinanceOrderStatus::New);
assert_eq!(msg.order_id, 4293153);
assert_eq!(msg.side, BinanceSide::Buy);
}
#[rstest]
fn test_deserialize_execution_report_trade() {
let json = load_fixture_string("spot/user_data_json/execution_report_trade.json");
let msg: BinanceSpotExecutionReport = serde_json::from_str(&json).unwrap();
assert_eq!(msg.execution_type, BinanceSpotExecutionType::Trade);
assert_eq!(msg.order_status, BinanceOrderStatus::Filled);
assert_eq!(msg.trade_id, 98765432);
assert_eq!(msg.last_filled_qty, "1.00000000");
assert_eq!(msg.last_filled_price, "2500.00000000");
assert!(msg.is_maker);
}
#[rstest]
fn test_deserialize_execution_report_canceled() {
let json = load_fixture_string("spot/user_data_json/execution_report_canceled.json");
let msg: BinanceSpotExecutionReport = serde_json::from_str(&json).unwrap();
assert_eq!(msg.execution_type, BinanceSpotExecutionType::Canceled);
assert_eq!(msg.order_status, BinanceOrderStatus::Canceled);
}
#[rstest]
fn test_deserialize_account_position() {
let json = load_event_fixture("spot/user_data_json/account_position_wrapped.json");
let msg: BinanceSpotAccountPositionMsg = serde_json::from_value(json).unwrap();
assert_eq!(msg.event_type, "outboundAccountPosition");
assert!(!msg.balances.is_empty());
assert_eq!(msg.balances[0].asset.as_str(), "ETH");
}
#[rstest]
fn test_deserialize_balance_update() {
let json = load_event_fixture("spot/user_data_json/balance_update_wrapped.json");
let msg: BinanceSpotBalanceUpdateMsg = serde_json::from_value(json).unwrap();
assert_eq!(msg.event_type, "balanceUpdate");
assert_eq!(msg.asset.as_str(), "BTC");
assert_eq!(msg.delta, "100.00000000");
}
}