1use rust_decimal::Decimal;
2use serde::Deserialize;
3
4use crate::spot::{KlineInterval, StreamName, Timestamp};
5
6#[derive(PartialEq, Deserialize, Debug)]
7#[serde(untagged)]
8pub enum IncomingMessage {
9 Response {
10 result: Option<serde_json::Value>,
11 id: String,
12 },
13 Error {
14 code: i64,
15 msg: String,
16 id: Option<String>,
17 },
18 StreamEvent(CombinedStreamEvent<StreamEvent>),
19}
20
21#[derive(Debug, Deserialize, PartialEq)]
22pub struct CombinedStreamEvent<T> {
23 pub stream: StreamName,
24 pub data: T,
25}
26
27#[derive(PartialEq, Deserialize, Debug)]
28#[serde(tag = "e")]
29pub enum StreamEvent {
30 #[serde(rename = "aggTrade")]
31 AggTrade(EventAggTrade),
32 #[serde(rename = "trade")]
33 Trade(EventTrade),
34 #[serde(rename = "kline")]
35 Kline(EventKline),
36 #[serde(rename = "24hrMiniTicker")]
37 MiniTicker24(EventMiniTicker24),
38}
39
40#[derive(PartialEq, Deserialize, Debug)]
42pub struct EventAggTrade {
43 #[serde(rename = "E")]
45 pub event_time: Timestamp,
46 #[serde(rename = "s")]
48 pub symbol: String,
49 #[serde(rename = "a")]
51 pub trade_id: i64,
52 #[serde(rename = "p")]
54 pub price: Decimal,
55 #[serde(rename = "q")]
57 pub qty: Decimal,
58 #[serde(rename = "f")]
60 pub first_trade_id: i64,
61 #[serde(rename = "l")]
63 pub last_trade_id: i64,
64 #[serde(rename = "T")]
66 pub trade_time: Timestamp,
67 #[serde(rename = "m")]
69 pub is_buyer: bool,
70}
71
72#[derive(PartialEq, Deserialize, Debug)]
74pub struct EventTrade {
75 #[serde(rename = "E")]
77 pub event_time: Timestamp,
78 #[serde(rename = "s")]
80 pub symbol: String,
81 #[serde(rename = "t")]
83 pub trade_id: i64,
84 #[serde(rename = "p")]
86 pub price: Decimal,
87 #[serde(rename = "q")]
89 pub qty: Decimal,
90 #[serde(rename = "T")]
92 pub trade_time: Timestamp,
93 #[serde(rename = "m")]
95 pub is_buyer: bool,
96}
97
98#[derive(PartialEq, Deserialize, Debug)]
100pub struct EventKline {
101 #[serde(rename = "E")]
103 pub event_time: Timestamp,
104 #[serde(rename = "s")]
106 pub symbol: String,
107 #[serde(rename = "k")]
108 pub kline: KlineMsg,
109}
110#[derive(PartialEq, Deserialize, Debug)]
111pub struct KlineMsg {
112 #[serde(rename = "t")]
114 pub start_time: Timestamp,
115 #[serde(rename = "T")]
117 pub close_time: Timestamp,
118 #[serde(rename = "s")]
120 pub symbol: String,
121 #[serde(rename = "i")]
123 pub interval: KlineInterval,
124 #[serde(rename = "f")]
126 pub first_trade_id: i64,
127 #[serde(rename = "L")]
129 pub last_trade_id: i64,
130 #[serde(rename = "o")]
132 pub open_price: Decimal,
133 #[serde(rename = "c")]
135 pub close_price: Decimal,
136 #[serde(rename = "h")]
138 pub high_price: Decimal,
139 #[serde(rename = "l")]
141 pub low_price: Decimal,
142 #[serde(rename = "v")]
144 pub base_asset_volume: Decimal,
145 #[serde(rename = "n")]
147 pub trade_number: i64,
148 #[serde(rename = "x")]
150 pub is_closed: bool,
151 #[serde(rename = "q")]
153 pub quote_asset_volume: Decimal,
154 #[serde(rename = "V")]
156 pub taker_buy_base_asset_volume: Decimal,
157 #[serde(rename = "Q")]
159 pub taker_buy_quote_asset_volume: Decimal,
160}
161
162#[derive(PartialEq, Deserialize, Debug)]
164pub struct EventMiniTicker24 {
165 #[serde(rename = "E")]
167 pub event_time: Timestamp,
168 #[serde(rename = "s")]
170 pub symbol: String,
171 #[serde(rename = "o")]
173 pub open_price: Decimal,
174 #[serde(rename = "c")]
176 pub close_price: Decimal,
177 #[serde(rename = "h")]
179 pub high_price: Decimal,
180 #[serde(rename = "l")]
182 pub low_price: Decimal,
183 #[serde(rename = "v")]
185 pub total_base_asset_volume: Decimal,
186 #[serde(rename = "q")]
188 pub total_quote_asset_volume: Decimal,
189}
190
191#[cfg(test)]
192mod tests {
193 use rust_decimal::dec;
194
195 use super::*;
196
197 #[test]
198 fn test_deserialize_combined_stream_event() {
199 let json = r#"{
200 "stream": "bnbbtc@trade",
201 "data": "DATA"
202 }"#;
203 let expected = CombinedStreamEvent {
204 stream: StreamName::Trade {
205 symbol: String::from("BNBBTC").to_lowercase(),
206 },
207 data: String::from("DATA"),
208 };
209
210 let current = serde_json::from_str(json).unwrap();
211
212 assert_eq!(expected, current);
213 }
214
215 #[test]
216 fn test_deserialize_stream_event_agg_trade() {
217 let json = r#"{
218 "e": "aggTrade",
219 "E": 1672515782136,
220 "s": "BNBBTC",
221 "a": 12345,
222 "p": "0.001",
223 "q": "100",
224 "f": 100,
225 "l": 105,
226 "T": 1672515782136,
227 "m": true,
228 "M": true
229 }"#;
230 let expected = EventAggTrade {
231 event_time: 1672515782136,
232 symbol: String::from("BNBBTC"),
233 trade_id: 12345,
234 price: dec!(0.001),
235 qty: dec!(100),
236 first_trade_id: 100,
237 last_trade_id: 105,
238 trade_time: 1672515782136,
239 is_buyer: true,
240 };
241
242 let current = serde_json::from_str(json).unwrap();
243
244 assert_eq!(expected, current);
245 }
246
247 #[test]
248 fn test_deserialize_stream_event_trade() {
249 let json = r#"{
250 "e": "trade",
251 "E": 1672515782136,
252 "s": "BNBBTC",
253 "t": 12345,
254 "p": "0.001",
255 "q": "100",
256 "T": 1672515782136,
257 "m": true,
258 "M": true
259 }"#;
260 let expected = EventTrade {
261 event_time: 1672515782136,
262 symbol: String::from("BNBBTC"),
263 trade_id: 12345,
264 price: dec!(0.001),
265 qty: dec!(100),
266 trade_time: 1672515782136,
267 is_buyer: true,
268 };
269
270 let current = serde_json::from_str(json).unwrap();
271
272 assert_eq!(expected, current);
273 }
274
275 #[test]
276 fn test_deserialize_stream_event_kline() {
277 let json = r#"{
278 "e": "kline",
279 "E": 1672515782136,
280 "s": "BNBBTC",
281 "k": {
282 "t": 1672515780000,
283 "T": 1672515839999,
284 "s": "BNBBTC",
285 "i": "1m",
286 "f": 100,
287 "L": 200,
288 "o": "0.0010",
289 "c": "0.0020",
290 "h": "0.0025",
291 "l": "0.0015",
292 "v": "1000",
293 "n": 100,
294 "x": false,
295 "q": "1.0000",
296 "V": "500",
297 "Q": "0.500",
298 "B": "123456"
299 }
300 }"#;
301 let symbol = String::from("BNBBTC");
302 let expected = EventKline {
303 event_time: 1672515782136,
304 symbol: symbol.clone(),
305 kline: KlineMsg {
306 start_time: 1672515780000,
307 close_time: 1672515839999,
308 symbol,
309 interval: KlineInterval::Minute1,
310 first_trade_id: 100,
311 last_trade_id: 200,
312 open_price: dec!(0.0010),
313 close_price: dec!(0.0020),
314 high_price: dec!(0.0025),
315 low_price: dec!(0.0015),
316 base_asset_volume: dec!(1000),
317 trade_number: 100,
318 is_closed: false,
319 quote_asset_volume: dec!(1.0000),
320 taker_buy_base_asset_volume: dec!(500),
321 taker_buy_quote_asset_volume: dec!(0.500),
322 },
323 };
324
325 let current = serde_json::from_str(json).unwrap();
326
327 assert_eq!(expected, current);
328 }
329
330 #[test]
331 fn test_deserialize_stream_event_mini_ticker24() {
332 let json = r#"{
333 "e": "24hrMiniTicker",
334 "E": 1672515782136,
335 "s": "BNBBTC",
336 "c": "0.0025",
337 "o": "0.0010",
338 "h": "0.0025",
339 "l": "0.0010",
340 "v": "10000",
341 "q": "18"
342 }"#;
343 let expected = EventMiniTicker24 {
344 event_time: 1672515782136,
345 symbol: String::from("BNBBTC"),
346 open_price: dec!(0.0010),
347 close_price: dec!(0.0025),
348 high_price: dec!(0.0025),
349 low_price: dec!(0.0010),
350 total_base_asset_volume: dec!(10000),
351 total_quote_asset_volume: dec!(18),
352 };
353
354 let current = serde_json::from_str(json).unwrap();
355
356 assert_eq!(expected, current);
357 }
358}