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