1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3use crate::spot::KlineInterval;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum StreamName {
7 AggTrade { symbol: String },
9 Trade { symbol: String },
11 Depth { symbol: String },
13 Kline {
15 symbol: String,
16 interval: KlineInterval,
17 },
18 MiniTicker24 { symbol: String },
20}
21
22impl Serialize for StreamName {
23 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24 where
25 S: Serializer,
26 {
27 let s = match self {
28 StreamName::AggTrade { symbol } => format!("{symbol}@aggTrade"),
29 StreamName::Trade { symbol } => format!("{symbol}@trade"),
30 StreamName::Depth { symbol } => format!("{symbol}@depth"),
31 StreamName::Kline { symbol, interval } => format!("{symbol}@kline_{interval}"),
32 StreamName::MiniTicker24 { symbol } => format!("{symbol}@24hrMiniTicker"),
33 };
34 serializer.serialize_str(&s)
35 }
36}
37
38impl<'de> Deserialize<'de> for StreamName {
39 fn deserialize<D>(deserializer: D) -> Result<StreamName, D::Error>
40 where
41 D: Deserializer<'de>,
42 {
43 let s: &str = Deserialize::deserialize(deserializer)?;
44 if let Some((symbol, kind)) = s.split_once('@') {
45 match kind {
46 "aggTrade" => Ok(StreamName::AggTrade {
47 symbol: symbol.to_owned(),
48 }),
49 "trade" => Ok(StreamName::Trade {
50 symbol: symbol.to_owned(),
51 }),
52 "depth" => Ok(StreamName::Depth {
53 symbol: symbol.to_owned(),
54 }),
55 "24hrMiniTicker" => Ok(StreamName::MiniTicker24 {
56 symbol: symbol.to_owned(),
57 }),
58 kind => {
59 if let Some((kind, params)) = kind.split_once('_') {
60 match kind {
61 "kline" => {
62 let interval = format!("\"{params}\"");
63 let interval = match serde_json::from_str(&interval) {
64 Ok(interval) => interval,
65 Err(_) => {
66 return Err(serde::de::Error::custom(
67 "invalid stream format",
68 ));
69 }
70 };
71 Ok(StreamName::Kline {
72 symbol: symbol.to_owned(),
73 interval,
74 })
75 }
76 _ => Err(serde::de::Error::custom(format!(
77 "unknown stream type: {}",
78 kind
79 ))),
80 }
81 } else {
82 Err(serde::de::Error::custom("invalid stream format"))
83 }
84 }
85 }
86 } else {
87 Err(serde::de::Error::custom("invalid stream format"))
88 }
89 }
90}
91
92#[derive(Serialize, Debug)]
93#[serde(tag = "method")]
94pub enum OutgoingMessage {
95 Empty,
96 #[serde(rename = "SUBSCRIBE")]
97 Subscribe {
98 id: String,
99 params: Vec<StreamName>,
100 },
101 #[serde(rename = "UNSUBSCRIBE")]
102 Unsubscribe {
103 id: String,
104 params: Vec<StreamName>,
105 },
106 #[serde(rename = "LIST_SUBSCRIPTIONS")]
107 ListSubscriptions {
108 id: String,
109 },
110 #[serde(rename = "SET_PROPERTY")]
111 SetProperty {
112 id: String,
113 params: (String, bool), },
115 #[serde(rename = "GET_PROPERTY")]
116 GetProperty {
117 id: String,
118 params: String, },
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_serialize_stream_name() {
128 let cases = vec![
129 (
130 StreamName::AggTrade {
131 symbol: String::from("btcusdt"),
132 },
133 r#""btcusdt@aggTrade""#,
134 ),
135 (
136 StreamName::Trade {
137 symbol: String::from("btcusdt"),
138 },
139 r#""btcusdt@trade""#,
140 ),
141 (
142 StreamName::Depth {
143 symbol: String::from("btcusdt"),
144 },
145 r#""btcusdt@depth""#,
146 ),
147 (
148 StreamName::Kline {
149 symbol: String::from("btcusdt"),
150 interval: KlineInterval::Minute1,
151 },
152 r#""btcusdt@kline_1m""#,
153 ),
154 (
155 StreamName::MiniTicker24 {
156 symbol: String::from("btcusdt"),
157 },
158 r#""btcusdt@24hrMiniTicker""#,
159 ),
160 ];
161
162 cases.into_iter().for_each(|(stream, expected)| {
163 let serialized = serde_json::to_string(&stream).unwrap();
164 assert_eq!(expected, serialized);
165 });
166 }
167
168 #[test]
169 fn test_deserialize_stream_name() {
170 let cases = vec![
171 (
172 r#""btcusdt@aggTrade""#,
173 StreamName::AggTrade {
174 symbol: String::from("btcusdt"),
175 },
176 ),
177 (
178 r#""btcusdt@trade""#,
179 StreamName::Trade {
180 symbol: String::from("btcusdt"),
181 },
182 ),
183 (
184 r#""btcusdt@depth""#,
185 StreamName::Depth {
186 symbol: String::from("btcusdt"),
187 },
188 ),
189 (
190 r#""btcusdt@kline_1m""#,
191 StreamName::Kline {
192 symbol: String::from("btcusdt"),
193 interval: KlineInterval::Minute1,
194 },
195 ),
196 (
197 r#""btcusdt@24hrMiniTicker""#,
198 StreamName::MiniTicker24 {
199 symbol: String::from("btcusdt"),
200 },
201 ),
202 ];
203
204 cases.into_iter().for_each(|(serialized, expected)| {
205 let stream = serde_json::from_str(serialized).unwrap();
206 assert_eq!(expected, stream);
207 });
208 }
209}