barter_data/exchange/bybit/
message.rs

1use std::fmt::Debug;
2
3use crate::{Identifier, exchange::bybit::channel::BybitChannel};
4use barter_integration::subscription::SubscriptionId;
5use chrono::{DateTime, Utc};
6use serde::{
7    Deserialize, Serialize,
8    de::{Error, Unexpected},
9};
10
11/// ### Raw Payload Examples
12/// See docs: <https://bybit-exchange.github.io/docs/v5/websocket/public/trade>
13/// #### Spot Side::Buy Trade
14///```json
15/// {
16///     "topic": "publicTrade.BTCUSDT",
17///     "type": "snapshot",
18///     "ts": 1672304486868,
19///     "data": [
20///         {
21///             "T": 1672304486865,
22///             "s": "BTCUSDT",
23///             "S": "Buy",
24///             "v": "0.001",
25///             "p": "16578.50",
26///             "L": "PlusTick",
27///             "i": "20f43950-d8dd-5b31-9112-a178eb6023af",
28///             "BT": false
29///         }
30///     ]
31/// }
32/// ```
33#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
34pub struct BybitPayload<T> {
35    #[serde(alias = "topic", deserialize_with = "de_message_subscription_id")]
36    pub subscription_id: SubscriptionId,
37
38    #[serde(rename = "type")]
39    pub kind: BybitPayloadKind,
40
41    #[serde(
42        alias = "ts",
43        deserialize_with = "barter_integration::de::de_u64_epoch_ms_as_datetime_utc"
44    )]
45    pub time: DateTime<Utc>,
46
47    pub data: T,
48}
49
50/// Bybit payload kind
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
52#[serde(rename_all = "lowercase")]
53pub enum BybitPayloadKind {
54    Snapshot,
55    Delta,
56}
57
58/// Deserialize a [`BybitPayload`] "s" (eg/ "publicTrade.BTCUSDT") as the associated
59/// [`SubscriptionId`].
60///
61/// eg/ "publicTrade|BTCUSDT"
62pub fn de_message_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
63where
64    D: serde::de::Deserializer<'de>,
65{
66    let input = <&str as serde::Deserialize>::deserialize(deserializer)?;
67    let mut tokens = input.split('.');
68
69    match (tokens.next(), tokens.next(), tokens.next()) {
70        (Some("publicTrade"), Some(market), None) => Ok(SubscriptionId::from(format!(
71            "{}|{market}",
72            BybitChannel::TRADES.0
73        ))),
74        (Some("orderbook"), Some("1"), Some(market)) => Ok(SubscriptionId::from(format!(
75            "{}|{market}",
76            BybitChannel::ORDER_BOOK_L1.0,
77        ))),
78        (Some("orderbook"), Some("50"), Some(market)) => Ok(SubscriptionId::from(format!(
79            "{}|{market}",
80            BybitChannel::ORDER_BOOK_L2.0,
81        ))),
82        _ => Err(Error::invalid_value(
83            Unexpected::Str(input),
84            &"invalid message type expected pattern: <type>.<symbol>",
85        )),
86    }
87}
88
89impl<T> Identifier<Option<SubscriptionId>> for BybitPayload<T> {
90    fn id(&self) -> Option<SubscriptionId> {
91        Some(self.subscription_id.clone())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97
98    mod de {
99        use crate::exchange::bybit::subscription::{BybitResponse, BybitReturnMessage};
100        use barter_integration::error::SocketError;
101
102        #[test]
103        fn test_bybit_pong() {
104            struct TestCase {
105                input: &'static str,
106                expected: Result<BybitResponse, SocketError>,
107            }
108
109            let tests = vec![
110                // TC0: input BybitResponse(Pong) is deserialised
111                TestCase {
112                    input: r#"
113                        {
114                            "success": true,
115                            "ret_msg": "pong",
116                            "conn_id": "0970e817-426e-429a-a679-ff7f55e0b16a",
117                            "op": "ping"
118                        }
119                    "#,
120                    expected: Ok(BybitResponse {
121                        success: true,
122                        ret_msg: BybitReturnMessage::Pong,
123                    }),
124                },
125            ];
126
127            for (index, test) in tests.into_iter().enumerate() {
128                let actual = serde_json::from_str::<BybitResponse>(test.input);
129                match (actual, test.expected) {
130                    (Ok(actual), Ok(expected)) => {
131                        assert_eq!(actual, expected, "TC{} failed", index)
132                    }
133                    (Err(_), Err(_)) => {
134                        // Test passed
135                    }
136                    (actual, expected) => {
137                        // Test failed
138                        panic!(
139                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
140                        );
141                    }
142                }
143            }
144        }
145    }
146}