1use rust_decimal::Decimal;
2use serde::Deserialize;
3
4use crate::{
5 Timestamp,
6 spot::{
7 KlineInterval,
8 ws::{MessageID, StreamName},
9 },
10 ws::ReceivedMessage,
11};
12
13#[derive(PartialEq, Deserialize, Debug)]
14#[serde(untagged)]
15#[allow(clippy::large_enum_variant)]
16pub enum IncomingMessage {
17 CombinedStream(CombinedStreamMessage<StreamMessage>),
18 Stream(StreamMessage),
19 Error(ErrorMessage),
20 Response(ResponseMessage), }
22
23impl ReceivedMessage for IncomingMessage {
24 fn server_shutdown_event_time(&self) -> Option<u64> {
25 match self {
26 IncomingMessage::Stream(StreamMessage::ServerShutdown(ServerShutdownMsg {
27 event_time,
28 })) => Some(*event_time),
29 IncomingMessage::CombinedStream(CombinedStreamMessage {
30 data: StreamMessage::ServerShutdown(ServerShutdownMsg { event_time }),
31 ..
32 }) => Some(*event_time),
33 _ => None,
34 }
35 }
36}
37
38#[derive(PartialEq, Deserialize, Debug)]
39pub struct ResponseMessage {
40 pub id: Option<MessageID>,
41 pub status: Option<i64>,
42 pub result: Option<serde_json::Value>,
43 pub rate_limits: Option<Vec<serde_json::Value>>,
44}
45
46#[derive(Debug, Deserialize, PartialEq)]
47pub struct CombinedStreamMessage<T> {
48 pub stream: StreamName,
49 pub data: T,
50}
51
52#[derive(PartialEq, Deserialize, Debug)]
53#[serde(tag = "e")]
54pub enum StreamMessage {
55 #[serde(rename = "aggTrade")]
56 AggTrade(AggTradeMsg),
57 #[serde(rename = "trade")]
58 Trade(TradeMsg),
59 #[serde(rename = "kline")]
60 Kline(KlineMsg),
61 #[serde(rename = "24hrMiniTicker")]
62 MiniTicker24(MiniTicker24Msg),
63 #[serde(rename = "serverShutdown")]
64 ServerShutdown(ServerShutdownMsg),
65}
66
67#[derive(PartialEq, Deserialize, Debug)]
69pub struct AggTradeMsg {
70 #[serde(rename = "E")]
72 pub event_time: Timestamp,
73 #[serde(rename = "s")]
75 pub symbol: String,
76 #[serde(rename = "a")]
78 pub trade_id: i64,
79 #[serde(rename = "p")]
81 pub price: Decimal,
82 #[serde(rename = "q")]
84 pub qty: Decimal,
85 #[serde(rename = "f")]
87 pub first_trade_id: i64,
88 #[serde(rename = "l")]
90 pub last_trade_id: i64,
91 #[serde(rename = "T")]
93 pub trade_time: Timestamp,
94 #[serde(rename = "m")]
96 pub is_buyer: bool,
97}
98
99#[derive(PartialEq, Deserialize, Debug)]
101pub struct TradeMsg {
102 #[serde(rename = "E")]
104 pub event_time: Timestamp,
105 #[serde(rename = "s")]
107 pub symbol: String,
108 #[serde(rename = "t")]
110 pub trade_id: i64,
111 #[serde(rename = "p")]
113 pub price: Decimal,
114 #[serde(rename = "q")]
116 pub qty: Decimal,
117 #[serde(rename = "T")]
119 pub trade_time: Timestamp,
120 #[serde(rename = "m")]
122 pub is_buyer: bool,
123}
124
125#[derive(PartialEq, Deserialize, Debug)]
127pub struct KlineMsg {
128 #[serde(rename = "E")]
130 pub event_time: Timestamp,
131 #[serde(rename = "s")]
133 pub symbol: String,
134 #[serde(rename = "k")]
135 pub kline: Kline,
136}
137#[derive(PartialEq, Deserialize, Debug)]
138pub struct Kline {
139 #[serde(rename = "t")]
141 pub start_time: Timestamp,
142 #[serde(rename = "T")]
144 pub close_time: Timestamp,
145 #[serde(rename = "s")]
147 pub symbol: String,
148 #[serde(rename = "i")]
150 pub interval: KlineInterval,
151 #[serde(rename = "f")]
153 pub first_trade_id: i64,
154 #[serde(rename = "L")]
156 pub last_trade_id: i64,
157 #[serde(rename = "o")]
159 pub open_price: Decimal,
160 #[serde(rename = "c")]
162 pub close_price: Decimal,
163 #[serde(rename = "h")]
165 pub high_price: Decimal,
166 #[serde(rename = "l")]
168 pub low_price: Decimal,
169 #[serde(rename = "v")]
171 pub base_asset_volume: Decimal,
172 #[serde(rename = "n")]
174 pub trade_number: i64,
175 #[serde(rename = "x")]
177 pub is_closed: bool,
178 #[serde(rename = "q")]
180 pub quote_asset_volume: Decimal,
181 #[serde(rename = "V")]
183 pub taker_buy_base_asset_volume: Decimal,
184 #[serde(rename = "Q")]
186 pub taker_buy_quote_asset_volume: Decimal,
187}
188
189#[derive(PartialEq, Deserialize, Debug)]
191pub struct MiniTicker24Msg {
192 #[serde(rename = "E")]
194 pub event_time: Timestamp,
195 #[serde(rename = "s")]
197 pub symbol: String,
198 #[serde(rename = "o")]
200 pub open_price: Decimal,
201 #[serde(rename = "c")]
203 pub close_price: Decimal,
204 #[serde(rename = "h")]
206 pub high_price: Decimal,
207 #[serde(rename = "l")]
209 pub low_price: Decimal,
210 #[serde(rename = "v")]
212 pub total_base_asset_volume: Decimal,
213 #[serde(rename = "q")]
215 pub total_quote_asset_volume: Decimal,
216}
217
218#[derive(PartialEq, Deserialize, Debug)]
219pub struct ServerShutdownMsg {
220 #[serde(rename = "E")]
222 pub event_time: Timestamp,
223}
224
225#[derive(PartialEq, Deserialize, Debug)]
226pub struct ErrorMessage {
227 pub error: ErrorValueMessage,
228 pub id: Option<MessageID>,
229}
230
231#[derive(PartialEq, Deserialize, Debug)]
232pub struct ErrorValueMessage {
233 pub code: i64,
234 pub msg: String,
235}
236
237#[cfg(test)]
238mod tests {
239 use rust_decimal::dec;
240
241 use crate::serde::deserialize_json;
242
243 use super::*;
244
245 #[test]
246 fn test_deserialize_combined_stream_event() {
247 let json = r#"{
248 "stream": "bnbbtc@trade",
249 "data": "DATA"
250 }"#;
251 let expected = CombinedStreamMessage {
252 stream: StreamName::Trade {
253 symbol: String::from("BNBBTC").to_lowercase(),
254 },
255 data: String::from("DATA"),
256 };
257
258 let current = deserialize_json(json).unwrap();
259
260 assert_eq!(expected, current);
261 }
262
263 #[test]
264 fn test_deserialize_stream_message_agg_trade() {
265 let json = r#"{
266 "e": "aggTrade",
267 "E": 1672515782136,
268 "s": "BNBBTC",
269 "a": 12345,
270 "p": "0.001",
271 "q": "100",
272 "f": 100,
273 "l": 105,
274 "T": 1672515782136,
275 "m": true,
276 "M": true
277 }"#;
278 let expected = AggTradeMsg {
279 event_time: 1672515782136,
280 symbol: String::from("BNBBTC"),
281 trade_id: 12345,
282 price: dec!(0.001),
283 qty: dec!(100),
284 first_trade_id: 100,
285 last_trade_id: 105,
286 trade_time: 1672515782136,
287 is_buyer: true,
288 };
289
290 let current = deserialize_json(json).unwrap();
291
292 assert_eq!(expected, current);
293 }
294
295 #[test]
296 fn test_deserialize_stream_message_trade() {
297 let json = r#"{
298 "e": "trade",
299 "E": 1672515782136,
300 "s": "BNBBTC",
301 "t": 12345,
302 "p": "0.001",
303 "q": "100",
304 "T": 1672515782136,
305 "m": true,
306 "M": true
307 }"#;
308 let expected = TradeMsg {
309 event_time: 1672515782136,
310 symbol: String::from("BNBBTC"),
311 trade_id: 12345,
312 price: dec!(0.001),
313 qty: dec!(100),
314 trade_time: 1672515782136,
315 is_buyer: true,
316 };
317
318 let current = deserialize_json(json).unwrap();
319
320 assert_eq!(expected, current);
321 }
322
323 #[test]
324 fn test_deserialize_stream_message_kline() {
325 let json = r#"{
326 "e": "kline",
327 "E": 1672515782136,
328 "s": "BNBBTC",
329 "k": {
330 "t": 1672515780000,
331 "T": 1672515839999,
332 "s": "BNBBTC",
333 "i": "1m",
334 "f": 100,
335 "L": 200,
336 "o": "0.0010",
337 "c": "0.0020",
338 "h": "0.0025",
339 "l": "0.0015",
340 "v": "1000",
341 "n": 100,
342 "x": false,
343 "q": "1.0000",
344 "V": "500",
345 "Q": "0.500",
346 "B": "123456"
347 }
348 }"#;
349 let symbol = String::from("BNBBTC");
350 let expected = KlineMsg {
351 event_time: 1672515782136,
352 symbol: symbol.clone(),
353 kline: Kline {
354 start_time: 1672515780000,
355 close_time: 1672515839999,
356 symbol,
357 interval: KlineInterval::Minute1,
358 first_trade_id: 100,
359 last_trade_id: 200,
360 open_price: dec!(0.0010),
361 close_price: dec!(0.0020),
362 high_price: dec!(0.0025),
363 low_price: dec!(0.0015),
364 base_asset_volume: dec!(1000),
365 trade_number: 100,
366 is_closed: false,
367 quote_asset_volume: dec!(1.0000),
368 taker_buy_base_asset_volume: dec!(500),
369 taker_buy_quote_asset_volume: dec!(0.500),
370 },
371 };
372
373 let current = deserialize_json(json).unwrap();
374
375 assert_eq!(expected, current);
376 }
377
378 #[test]
379 fn test_deserialize_stream_message_mini_ticker24() {
380 let json = r#"{
381 "e": "24hrMiniTicker",
382 "E": 1672515782136,
383 "s": "BNBBTC",
384 "c": "0.0025",
385 "o": "0.0010",
386 "h": "0.0025",
387 "l": "0.0010",
388 "v": "10000",
389 "q": "18"
390 }"#;
391 let expected = MiniTicker24Msg {
392 event_time: 1672515782136,
393 symbol: String::from("BNBBTC"),
394 open_price: dec!(0.0010),
395 close_price: dec!(0.0025),
396 high_price: dec!(0.0025),
397 low_price: dec!(0.0010),
398 total_base_asset_volume: dec!(10000),
399 total_quote_asset_volume: dec!(18),
400 };
401
402 let current = deserialize_json(json).unwrap();
403
404 assert_eq!(expected, current);
405 }
406
407 #[test]
408 fn test_deserialize_stream_message_response() {
409 let json = r#"{"result":null,"id":"message-id"}"#;
410 let parsed: IncomingMessage = deserialize_json(json).unwrap();
411 assert!(
412 matches!(parsed, IncomingMessage::Response(_)),
413 "expected Response variant, got {parsed:?}",
414 );
415 }
416}