1use serde::Serialize;
2
3use crate::{
4 SensitiveString, Timestamp, Topic, create_stream_signature,
5 http::{
6 AmendOrderBatchRequest, AmendOrderRequest, CancelOrderBatchRequest, CancelOrderRequest,
7 PlaceOrderBatchRequest, PlaceOrderRequest,
8 },
9 timestamp,
10};
11
12#[derive(Serialize, Debug)]
14#[serde(tag = "op")]
15pub enum OutgoingMessage {
16 #[serde(rename = "subscribe")]
18 Subscribe {
19 #[serde(skip_serializing_if = "Option::is_none")]
21 req_id: Option<String>,
22 args: Vec<Topic>,
24 },
25 #[serde(rename = "unsubscribe")]
27 Unsubscribe {
28 #[serde(skip_serializing_if = "Option::is_none")]
30 req_id: Option<String>,
31 args: Vec<Topic>,
33 },
34 #[serde(rename = "auth")]
36 Auth {
37 #[serde(skip_serializing_if = "Option::is_none")]
39 req_id: Option<String>,
40 args: (String, Timestamp, String),
42 },
43 #[serde(rename = "ping")]
45 Ping {
46 #[serde(skip_serializing_if = "Option::is_none")]
48 req_id: Option<String>,
49 },
50 #[serde(rename = "pong")]
52 Pong {
53 #[serde(skip_serializing_if = "Option::is_none")]
55 req_id: Option<String>,
56 },
57 #[serde(rename = "order.create")]
59 OrderCreate {
60 #[serde(rename = "reqId")]
62 req_id: String,
63 header: TradeHeader,
65 args: Vec<PlaceOrderRequest>,
67 },
68 #[serde(rename = "order.amend")]
70 OrderAmend {
71 #[serde(rename = "reqId")]
73 req_id: String,
74 header: TradeHeader,
76 args: Vec<AmendOrderRequest>,
78 },
79 #[serde(rename = "order.cancel")]
81 OrderCancel {
82 #[serde(rename = "reqId")]
84 req_id: String,
85 header: TradeHeader,
87 args: Vec<CancelOrderRequest>,
89 },
90 #[serde(rename = "order.create-batch")]
92 OrderCreateBatch {
93 #[serde(rename = "reqId")]
95 req_id: String,
96 header: TradeHeader,
98 args: Vec<PlaceOrderBatchRequest>,
100 },
101 #[serde(rename = "order.amend-batch")]
103 OrderAmendBatch {
104 #[serde(rename = "reqId")]
106 req_id: String,
107 header: TradeHeader,
109 args: Vec<AmendOrderBatchRequest>,
111 },
112 #[serde(rename = "order.cancel-batch")]
114 OrderCancelBatch {
115 #[serde(rename = "reqId")]
117 req_id: String,
118 header: TradeHeader,
120 args: Vec<CancelOrderBatchRequest>,
122 },
123}
124
125#[derive(Serialize, Debug, Clone, PartialEq)]
127pub struct TradeHeader {
128 #[serde(rename = "X-BAPI-TIMESTAMP")]
130 pub timestamp: String,
131 #[serde(rename = "X-BAPI-RECV-WINDOW")]
133 pub recv_window: String,
134 #[serde(rename = "Referer", skip_serializing_if = "Option::is_none")]
136 pub referer: Option<String>,
137}
138
139pub fn create_outgoing_message_auth(
144 api_key: SensitiveString,
145 api_secret: SensitiveString,
146 req_id: Option<String>,
147 recv_window: Timestamp,
148) -> OutgoingMessage {
149 create_outgoing_message_auth_at(api_key, api_secret, req_id, recv_window, timestamp())
150}
151
152pub fn create_outgoing_message_auth_at(
155 api_key: SensitiveString,
156 api_secret: SensitiveString,
157 req_id: Option<String>,
158 recv_window: Timestamp,
159 now: Timestamp,
160) -> OutgoingMessage {
161 let api_key = api_key.expose().to_string();
162 let expires = now + recv_window;
163
164 let signature = create_stream_signature(expires, api_secret);
165
166 OutgoingMessage::Auth {
167 req_id,
168 args: (api_key, expires, signature),
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use crate::*;
175
176 use super::*;
177
178 #[test]
179 fn test_serialize_outgoing_message_subscribe() {
180 let msg = OutgoingMessage::Subscribe {
181 req_id: Some(String::from("request_id")),
182 args: vec![
183 Topic::Ticker(String::from("BTCUSDT")),
184 Topic::Order(Category::Linear),
185 ],
186 };
187 let expected =
188 r#"{"op":"subscribe","req_id":"request_id","args":["tickers.BTCUSDT","order.linear"]}"#;
189 let serialized = serde_json::to_string(&msg).unwrap();
190 assert_eq!(serialized, expected);
191 }
192
193 #[test]
194 fn test_serialize_outgoing_message_unsubscribe() {
195 let msg = OutgoingMessage::Unsubscribe {
196 req_id: Some(String::from("request_id")),
197 args: vec![Topic::Ticker(String::from("BTCUSDT"))],
198 };
199 let expected = r#"{"op":"unsubscribe","req_id":"request_id","args":["tickers.BTCUSDT"]}"#;
200 let serialized = serde_json::to_string(&msg).unwrap();
201 assert_eq!(serialized, expected);
202 }
203
204 #[test]
205 fn test_serialize_outgoing_message_auth() {
206 let msg = OutgoingMessage::Auth {
207 req_id: Some(String::from("request_id")),
208 args: (
209 String::from("api_key"),
210 1662350400000,
211 String::from("signature"),
212 ),
213 };
214 let expected =
215 r#"{"op":"auth","req_id":"request_id","args":["api_key",1662350400000,"signature"]}"#;
216 let serialized = serde_json::to_string(&msg).unwrap();
217 assert_eq!(serialized, expected);
218 }
219}