Skip to main content

bybit/ws/
outgoing_message.rs

1use serde::Serialize;
2
3use crate::{SensitiveString, Timestamp, Topic, create_stream_signature, timestamp};
4
5#[derive(Serialize, Debug)]
6#[serde(tag = "op")]
7pub enum OutgoingMessage {
8    #[serde(rename = "subscribe")]
9    Subscribe {
10        #[serde(skip_serializing_if = "Option::is_none")]
11        req_id: Option<String>,
12        args: Vec<Topic>,
13    },
14    #[serde(rename = "unsubscribe")]
15    Unsubscribe {
16        #[serde(skip_serializing_if = "Option::is_none")]
17        req_id: Option<String>,
18        args: Vec<Topic>,
19    },
20    #[serde(rename = "auth")]
21    Auth {
22        #[serde(skip_serializing_if = "Option::is_none")]
23        req_id: Option<String>,
24        args: (String, Timestamp, String),
25    },
26    #[serde(rename = "ping")]
27    Ping {
28        #[serde(skip_serializing_if = "Option::is_none")]
29        req_id: Option<String>,
30    },
31    #[serde(rename = "pong")]
32    Pong {
33        #[serde(skip_serializing_if = "Option::is_none")]
34        req_id: Option<String>,
35    },
36}
37
38/// `auth` message for a private stream, valid for `recv_window` ms from now
39/// by the local clock. With a skewed clock use
40/// [`create_outgoing_message_auth_at`] and
41/// [`Client::server_timestamp`](crate::http::Client::server_timestamp).
42pub fn create_outgoing_message_auth(
43    api_key: SensitiveString,
44    api_secret: SensitiveString,
45    req_id: Option<String>,
46    recv_window: Timestamp,
47) -> OutgoingMessage {
48    create_outgoing_message_auth_at(api_key, api_secret, req_id, recv_window, timestamp())
49}
50
51/// `auth` message for a private stream, valid for `recv_window` ms from
52/// `now` (milliseconds, ideally the server time).
53pub fn create_outgoing_message_auth_at(
54    api_key: SensitiveString,
55    api_secret: SensitiveString,
56    req_id: Option<String>,
57    recv_window: Timestamp,
58    now: Timestamp,
59) -> OutgoingMessage {
60    let api_key = api_key.expose().to_string();
61    let expires = now + recv_window;
62
63    let signature = create_stream_signature(expires, api_secret);
64
65    OutgoingMessage::Auth {
66        req_id,
67        args: (api_key, expires, signature),
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use crate::*;
74
75    use super::*;
76
77    #[test]
78    fn test_serialize_outgoing_message_subscribe() {
79        let msg = OutgoingMessage::Subscribe {
80            req_id: Some(String::from("request_id")),
81            args: vec![
82                Topic::Ticker(String::from("BTCUSDT")),
83                Topic::Order(Category::Linear),
84            ],
85        };
86        let expected =
87            r#"{"op":"subscribe","req_id":"request_id","args":["tickers.BTCUSDT","order.linear"]}"#;
88        let serialized = serde_json::to_string(&msg).unwrap();
89        assert_eq!(serialized, expected);
90    }
91
92    #[test]
93    fn test_serialize_outgoing_message_unsubscribe() {
94        let msg = OutgoingMessage::Unsubscribe {
95            req_id: Some(String::from("request_id")),
96            args: vec![Topic::Ticker(String::from("BTCUSDT"))],
97        };
98        let expected = r#"{"op":"unsubscribe","req_id":"request_id","args":["tickers.BTCUSDT"]}"#;
99        let serialized = serde_json::to_string(&msg).unwrap();
100        assert_eq!(serialized, expected);
101    }
102
103    #[test]
104    fn test_serialize_outgoing_message_auth() {
105        let msg = OutgoingMessage::Auth {
106            req_id: Some(String::from("request_id")),
107            args: (
108                String::from("api_key"),
109                1662350400000,
110                String::from("signature"),
111            ),
112        };
113        let expected =
114            r#"{"op":"auth","req_id":"request_id","args":["api_key",1662350400000,"signature"]}"#;
115        let serialized = serde_json::to_string(&msg).unwrap();
116        assert_eq!(serialized, expected);
117    }
118}