use rust_decimal::Decimal;
use serde::Deserialize;
use crate::{
Timestamp,
spot::{
KlineInterval,
http::OrderLevel,
ws::{MessageID, StreamName},
},
ws::ReceivedMessage,
};
#[derive(PartialEq, Deserialize, Debug)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum IncomingMessage {
CombinedStream(CombinedStreamMessage<StreamMessage>),
Stream(StreamMessage),
Error(ErrorMessage),
Response(ResponseMessage), }
impl ReceivedMessage for IncomingMessage {
fn server_shutdown_event_time(&self) -> Option<u64> {
match self {
IncomingMessage::Stream(StreamMessage::ServerShutdown(ServerShutdownMsg {
event_time,
})) => Some(*event_time),
IncomingMessage::CombinedStream(CombinedStreamMessage {
data: StreamMessage::ServerShutdown(ServerShutdownMsg { event_time }),
..
}) => Some(*event_time),
_ => None,
}
}
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct ResponseMessage {
pub id: Option<MessageID>,
pub status: Option<i64>,
pub result: Option<serde_json::Value>,
pub rate_limits: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct CombinedStreamMessage<T> {
pub stream: StreamName,
pub data: T,
}
#[derive(PartialEq, Deserialize, Debug)]
#[serde(tag = "e")]
pub enum StreamMessage {
#[serde(rename = "aggTrade")]
AggTrade(AggTradeMsg),
#[serde(rename = "trade")]
Trade(TradeMsg),
#[serde(rename = "kline")]
Kline(KlineMsg),
#[serde(rename = "24hrMiniTicker")]
MiniTicker24(MiniTicker24Msg),
#[serde(rename = "depthUpdate")]
DepthUpdate(DepthUpdateMsg),
#[serde(rename = "serverShutdown")]
ServerShutdown(ServerShutdownMsg),
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct AggTradeMsg {
#[serde(rename = "E")]
pub event_time: Timestamp,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "a")]
pub trade_id: i64,
#[serde(rename = "p")]
pub price: Decimal,
#[serde(rename = "q")]
pub qty: Decimal,
#[serde(rename = "f")]
pub first_trade_id: i64,
#[serde(rename = "l")]
pub last_trade_id: i64,
#[serde(rename = "T")]
pub trade_time: Timestamp,
#[serde(rename = "m")]
pub is_buyer_maker: bool,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct TradeMsg {
#[serde(rename = "E")]
pub event_time: Timestamp,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "t")]
pub trade_id: i64,
#[serde(rename = "p")]
pub price: Decimal,
#[serde(rename = "q")]
pub qty: Decimal,
#[serde(rename = "T")]
pub trade_time: Timestamp,
#[serde(rename = "m")]
pub is_buyer_maker: bool,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct KlineMsg {
#[serde(rename = "E")]
pub event_time: Timestamp,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "k")]
pub kline: Kline,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct Kline {
#[serde(rename = "t")]
pub start_time: Timestamp,
#[serde(rename = "T")]
pub close_time: Timestamp,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "i")]
pub interval: KlineInterval,
#[serde(rename = "f")]
pub first_trade_id: i64,
#[serde(rename = "L")]
pub last_trade_id: i64,
#[serde(rename = "o")]
pub open_price: Decimal,
#[serde(rename = "c")]
pub close_price: Decimal,
#[serde(rename = "h")]
pub high_price: Decimal,
#[serde(rename = "l")]
pub low_price: Decimal,
#[serde(rename = "v")]
pub base_asset_volume: Decimal,
#[serde(rename = "n")]
pub trade_number: i64,
#[serde(rename = "x")]
pub is_closed: bool,
#[serde(rename = "q")]
pub quote_asset_volume: Decimal,
#[serde(rename = "V")]
pub taker_buy_base_asset_volume: Decimal,
#[serde(rename = "Q")]
pub taker_buy_quote_asset_volume: Decimal,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct MiniTicker24Msg {
#[serde(rename = "E")]
pub event_time: Timestamp,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "o")]
pub open_price: Decimal,
#[serde(rename = "c")]
pub close_price: Decimal,
#[serde(rename = "h")]
pub high_price: Decimal,
#[serde(rename = "l")]
pub low_price: Decimal,
#[serde(rename = "v")]
pub total_base_asset_volume: Decimal,
#[serde(rename = "q")]
pub total_quote_asset_volume: Decimal,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct DepthUpdateMsg {
#[serde(rename = "E")]
pub event_time: Timestamp,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "U")]
pub first_update_id: i64,
#[serde(rename = "u")]
pub final_update_id: i64,
#[serde(rename = "b")]
pub bids: Vec<OrderLevel>,
#[serde(rename = "a")]
pub asks: Vec<OrderLevel>,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct ServerShutdownMsg {
#[serde(rename = "E")]
pub event_time: Timestamp,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct ErrorMessage {
pub error: ErrorValueMessage,
pub id: Option<MessageID>,
}
#[derive(PartialEq, Deserialize, Debug)]
pub struct ErrorValueMessage {
pub code: i64,
pub msg: String,
}
#[cfg(test)]
mod tests {
use rust_decimal::dec;
use crate::serde::deserialize_json;
use super::*;
#[test]
fn test_deserialize_combined_stream_event() {
let json = r#"{
"stream": "bnbbtc@trade",
"data": "DATA"
}"#;
let expected = CombinedStreamMessage {
stream: StreamName::Trade {
symbol: String::from("BNBBTC").to_lowercase(),
},
data: String::from("DATA"),
};
let current = deserialize_json(json).unwrap();
assert_eq!(expected, current);
}
#[test]
fn test_deserialize_stream_message_agg_trade() {
let json = r#"{
"e": "aggTrade",
"E": 1672515782136,
"s": "BNBBTC",
"a": 12345,
"p": "0.001",
"q": "100",
"f": 100,
"l": 105,
"T": 1672515782136,
"m": true,
"M": true
}"#;
let expected = AggTradeMsg {
event_time: 1672515782136,
symbol: String::from("BNBBTC"),
trade_id: 12345,
price: dec!(0.001),
qty: dec!(100),
first_trade_id: 100,
last_trade_id: 105,
trade_time: 1672515782136,
is_buyer_maker: true,
};
let current = deserialize_json(json).unwrap();
assert_eq!(expected, current);
}
#[test]
fn test_deserialize_stream_message_trade() {
let json = r#"{
"e": "trade",
"E": 1672515782136,
"s": "BNBBTC",
"t": 12345,
"p": "0.001",
"q": "100",
"T": 1672515782136,
"m": true,
"M": true
}"#;
let expected = TradeMsg {
event_time: 1672515782136,
symbol: String::from("BNBBTC"),
trade_id: 12345,
price: dec!(0.001),
qty: dec!(100),
trade_time: 1672515782136,
is_buyer_maker: true,
};
let current = deserialize_json(json).unwrap();
assert_eq!(expected, current);
}
#[test]
fn test_deserialize_stream_message_kline() {
let json = r#"{
"e": "kline",
"E": 1672515782136,
"s": "BNBBTC",
"k": {
"t": 1672515780000,
"T": 1672515839999,
"s": "BNBBTC",
"i": "1m",
"f": 100,
"L": 200,
"o": "0.0010",
"c": "0.0020",
"h": "0.0025",
"l": "0.0015",
"v": "1000",
"n": 100,
"x": false,
"q": "1.0000",
"V": "500",
"Q": "0.500",
"B": "123456"
}
}"#;
let symbol = String::from("BNBBTC");
let expected = KlineMsg {
event_time: 1672515782136,
symbol: symbol.clone(),
kline: Kline {
start_time: 1672515780000,
close_time: 1672515839999,
symbol,
interval: KlineInterval::Minute1,
first_trade_id: 100,
last_trade_id: 200,
open_price: dec!(0.0010),
close_price: dec!(0.0020),
high_price: dec!(0.0025),
low_price: dec!(0.0015),
base_asset_volume: dec!(1000),
trade_number: 100,
is_closed: false,
quote_asset_volume: dec!(1.0000),
taker_buy_base_asset_volume: dec!(500),
taker_buy_quote_asset_volume: dec!(0.500),
},
};
let current = deserialize_json(json).unwrap();
assert_eq!(expected, current);
}
#[test]
fn test_deserialize_stream_message_mini_ticker24() {
let json = r#"{
"e": "24hrMiniTicker",
"E": 1672515782136,
"s": "BNBBTC",
"c": "0.0025",
"o": "0.0010",
"h": "0.0025",
"l": "0.0010",
"v": "10000",
"q": "18"
}"#;
let expected = MiniTicker24Msg {
event_time: 1672515782136,
symbol: String::from("BNBBTC"),
open_price: dec!(0.0010),
close_price: dec!(0.0025),
high_price: dec!(0.0025),
low_price: dec!(0.0010),
total_base_asset_volume: dec!(10000),
total_quote_asset_volume: dec!(18),
};
let current = deserialize_json(json).unwrap();
assert_eq!(expected, current);
}
#[test]
fn test_deserialize_stream_message_response() {
let json = r#"{"result":null,"id":"message-id"}"#;
let parsed: IncomingMessage = deserialize_json(json).unwrap();
assert!(
matches!(parsed, IncomingMessage::Response(_)),
"expected Response variant, got {parsed:?}",
);
}
}