1use std::fmt;
2
3use serde::de::{MapAccess, SeqAccess, Visitor};
4use serde::{Deserialize, Deserializer, Serialize, de};
5
6#[derive(Debug, Clone, PartialEq, Serialize)]
12pub struct Ticker {
13 pub symbol: String,
15 pub bid: f64,
17 pub bid_size: f64,
19 pub ask: f64,
21 pub ask_size: f64,
23 pub daily_change: f64,
25 pub daily_change_relative: f64,
27 pub last_price: f64,
29 pub volume: f64,
31 pub high: f64,
33 pub low: f64,
35}
36
37impl<'de> Deserialize<'de> for Ticker {
38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39 where
40 D: Deserializer<'de>,
41 {
42 #[derive(Deserialize)]
43 #[serde(field_identifier, rename_all = "snake_case")]
44 enum Field {
45 Symbol,
46 Bid,
47 BidSize,
48 Ask,
49 AskSize,
50 DailyChange,
51 DailyChangeRelative,
52 LastPrice,
53 Volume,
54 High,
55 Low,
56 }
57
58 struct SpotTickerVisitor {}
59 impl<'de> Visitor<'de> for SpotTickerVisitor {
60 type Value = Ticker;
61
62 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
63 formatter.write_str("tuple with length 11")
64 }
65
66 fn visit_seq<V>(self, mut seq: V) -> Result<Ticker, V::Error>
67 where
68 V: SeqAccess<'de>,
69 {
70 let symbol = seq
71 .next_element()?
72 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
73 let bid = seq
74 .next_element()?
75 .ok_or_else(|| de::Error::invalid_length(1, &self))?;
76 let bid_size = seq
77 .next_element()?
78 .ok_or_else(|| de::Error::invalid_length(2, &self))?;
79 let ask = seq
80 .next_element()?
81 .ok_or_else(|| de::Error::invalid_length(3, &self))?;
82 let ask_size = seq
83 .next_element()?
84 .ok_or_else(|| de::Error::invalid_length(4, &self))?;
85 let daily_change = seq
86 .next_element()?
87 .ok_or_else(|| de::Error::invalid_length(5, &self))?;
88 let daily_change_relative = seq
89 .next_element()?
90 .ok_or_else(|| de::Error::invalid_length(6, &self))?;
91 let last_price = seq
92 .next_element()?
93 .ok_or_else(|| de::Error::invalid_length(7, &self))?;
94 let volume = seq
95 .next_element()?
96 .ok_or_else(|| de::Error::invalid_length(8, &self))?;
97 let high = seq
98 .next_element()?
99 .ok_or_else(|| de::Error::invalid_length(9, &self))?;
100 let low = seq
101 .next_element()?
102 .ok_or_else(|| de::Error::invalid_length(10, &self))?;
103
104 let spot_ticker = Ticker {
105 symbol,
106 bid,
107 bid_size,
108 ask,
109 ask_size,
110 daily_change,
111 daily_change_relative,
112 last_price,
113 volume,
114 high,
115 low,
116 };
117 Ok(spot_ticker)
118 }
119
120 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
121 where
122 A: MapAccess<'de>,
123 {
124 let mut symbol = None;
125 let mut bid = None;
126 let mut bid_size = None;
127 let mut ask = None;
128 let mut ask_size = None;
129 let mut daily_change = None;
130 let mut daily_change_relative = None;
131 let mut last_price = None;
132 let mut volume = None;
133 let mut high = None;
134 let mut low = None;
135
136 while let Some(key) = map.next_key()? {
137 match key {
138 Field::Symbol => {
139 if symbol.is_some() {
140 return Err(de::Error::duplicate_field("symbol"));
141 }
142 symbol = Some(map.next_value()?);
143 }
144 Field::Bid => {
145 if bid.is_some() {
146 return Err(de::Error::duplicate_field("bid"));
147 }
148 bid = Some(map.next_value()?);
149 }
150 Field::BidSize => {
151 if bid_size.is_some() {
152 return Err(de::Error::duplicate_field("bid_size"));
153 }
154 bid_size = Some(map.next_value()?);
155 }
156 Field::Ask => {
157 if ask.is_some() {
158 return Err(de::Error::duplicate_field("ask"));
159 }
160 ask = Some(map.next_value()?);
161 }
162 Field::AskSize => {
163 if ask_size.is_some() {
164 return Err(de::Error::duplicate_field("ask_size"));
165 }
166 ask_size = Some(map.next_value()?);
167 }
168 Field::DailyChange => {
169 if daily_change.is_some() {
170 return Err(de::Error::duplicate_field("daily_change"));
171 }
172 daily_change = Some(map.next_value()?);
173 }
174 Field::DailyChangeRelative => {
175 if daily_change_relative.is_some() {
176 return Err(de::Error::duplicate_field("daily_change_relative"));
177 }
178 daily_change_relative = Some(map.next_value()?);
179 }
180 Field::LastPrice => {
181 if last_price.is_some() {
182 return Err(de::Error::duplicate_field("last_price"));
183 }
184 last_price = Some(map.next_value()?);
185 }
186 Field::Volume => {
187 if volume.is_some() {
188 return Err(de::Error::duplicate_field("volume"));
189 }
190 volume = Some(map.next_value()?);
191 }
192 Field::High => {
193 if high.is_some() {
194 return Err(de::Error::duplicate_field("high"));
195 }
196 high = Some(map.next_value()?);
197 }
198 Field::Low => {
199 if low.is_some() {
200 return Err(de::Error::duplicate_field("low"));
201 }
202 low = Some(map.next_value()?);
203 }
204 }
205 }
206
207 let symbol = symbol.ok_or_else(|| de::Error::missing_field("symbol"))?;
208 let bid = bid.ok_or_else(|| de::Error::missing_field("bid"))?;
209 let bid_size = bid_size.ok_or_else(|| de::Error::missing_field("bid_size"))?;
210 let ask = ask.ok_or_else(|| de::Error::missing_field("ask"))?;
211 let ask_size = ask_size.ok_or_else(|| de::Error::missing_field("ask_size"))?;
212 let daily_change =
213 daily_change.ok_or_else(|| de::Error::missing_field("daily_change"))?;
214 let daily_change_relative = daily_change_relative
215 .ok_or_else(|| de::Error::missing_field("daily_change_relative"))?;
216 let last_price =
217 last_price.ok_or_else(|| de::Error::missing_field("last_price"))?;
218 let volume = volume.ok_or_else(|| de::Error::missing_field("volume"))?;
219 let high = high.ok_or_else(|| de::Error::missing_field("high"))?;
220 let low = low.ok_or_else(|| de::Error::missing_field("low"))?;
221
222 let ticker = Ticker {
223 symbol,
224 bid,
225 bid_size,
226 ask,
227 ask_size,
228 daily_change,
229 daily_change_relative,
230 last_price,
231 volume,
232 high,
233 low,
234 };
235 Ok(ticker)
236 }
237 }
238
239 const FIELDS: &[&str] = &[
240 "symbol",
241 "bid",
242 "bid_size",
243 "ask",
244 "ask_size",
245 "daily_change",
246 "daily_change_relative",
247 "last_price",
248 "volume",
249 "high",
250 "low",
251 ];
252 deserializer.deserialize_struct("SpotTicker", FIELDS, SpotTickerVisitor {})
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_deserialize_spot_ticker_from_array() {
262 let json = r#"["tBTCUSD",101740,93.86022424,101750,38.06413103,2132,0.02140175,101750,663.27534767,102760,98740]"#;
263 let spot_ticker: Ticker = serde_json::from_str(json).unwrap();
264
265 let expected = Ticker {
266 symbol: "tBTCUSD".to_string(),
267 bid: 101740.0,
268 bid_size: 93.86022424,
269 ask: 101750.0,
270 ask_size: 38.06413103,
271 daily_change: 2132.0,
272 daily_change_relative: 0.02140175,
273 last_price: 101750.0,
274 volume: 663.27534767,
275 high: 102760.0,
276 low: 98740.0,
277 };
278
279 assert_eq!(spot_ticker, expected);
280 }
281
282 #[test]
283 fn test_deserialize_spot_ticker_from_array_with_invalid_length() {
284 let json = r#"["tBTCUSD",101740,93.86022424,101750,38.06413103,2132,0.02140175,101750,663.27534767]"#;
285 let spot_ticker: Result<Ticker, _> = serde_json::from_str(json);
286
287 assert_eq!(
288 spot_ticker.err().unwrap().to_string(),
289 "invalid length 9, expected tuple with length 11 at line 1 column 85"
290 );
291 }
292
293 #[test]
294 fn test_deserialize_spot_ticker_from_array_with_invalid_value() {
295 let json =
296 r#"[100000,101740,93.86022424,101750,38.06413103,2132,0.02140175,101750,663.27534767]"#;
297 let spot_ticker: Result<Ticker, _> = serde_json::from_str(json);
298
299 assert_eq!(
300 spot_ticker.err().unwrap().to_string(),
301 "invalid type: integer `100000`, expected a string at line 1 column 7"
302 );
303 }
304
305 #[test]
306 fn test_deserialize_spot_ticker_from_map() {
307 let json = r#"{"symbol":"tBTCUSD","bid":101740,"bid_size":93.86022424,"ask":101750,"ask_size":38.06413103,"daily_change":2132,"daily_change_relative":0.02140175,"last_price":101750,"volume":663.27534767,"high":102760,"low":98740.0}"#;
308 let spot_ticker: Ticker = serde_json::from_str(json).unwrap();
309
310 let expected = Ticker {
311 symbol: "tBTCUSD".to_string(),
312 bid: 101740.0,
313 bid_size: 93.86022424,
314 ask: 101750.0,
315 ask_size: 38.06413103,
316 daily_change: 2132.0,
317 daily_change_relative: 0.02140175,
318 last_price: 101750.0,
319 volume: 663.27534767,
320 high: 102760.0,
321 low: 98740.0,
322 };
323
324 assert_eq!(spot_ticker, expected);
325 }
326
327 #[test]
328 fn test_deserialize_spot_ticker_from_map_with_missing_field() {
329 let json = r#"{"bid":101740,"bid_size":93.86022424,"ask":101750,"ask_size":38.06413103,"daily_change":2132,"daily_change_relative":0.02140175,"last_price":101750,"volume":663.27534767,"high":102760,"low":98740.0}"#;
330 let spot_ticker: Result<Ticker, _> = serde_json::from_str(json);
331
332 assert_eq!(
333 spot_ticker.err().unwrap().to_string(),
334 "missing field `symbol` at line 1 column 198"
335 );
336 }
337
338 #[test]
339 fn test_deserialize_spot_ticker_from_map_with_invalid_key() {
340 let json = r#"{"abc":"tBTCUSD","bid":101740,"bid_size":93.86022424,"ask":101750,"ask_size":38.06413103,"daily_change":2132,"daily_change_relative":0.02140175,"last_price":101750,"volume":663.27534767,"high":102760,"low":98740.0}"#;
341 let spot_ticker: Result<Ticker, _> = serde_json::from_str(json);
342
343 assert_eq!(
344 spot_ticker.err().unwrap().to_string(),
345 "unknown field `abc`, expected one of `symbol`, `bid`, `bid_size`, `ask`, `ask_size`, `daily_change`, `daily_change_relative`, `last_price`, `volume`, `high`, `low` at line 1 column 6"
346 );
347 }
348
349 #[test]
350 fn test_deserialize_spot_ticker_from_map_with_invalid_value() {
351 let json = r#"{"symbol": -1000,"bid":101740,"bid_size":93.86022424,"ask":101750,"ask_size":38.06413103,"daily_change":2132,"daily_change_relative":0.02140175,"last_price":101750,"volume":663.27534767,"high":102760,"low":98740.0}"#;
352 let spot_ticker: Result<Ticker, _> = serde_json::from_str(json);
353
354 assert_eq!(
355 spot_ticker.err().unwrap().to_string(),
356 "invalid type: integer `-1000`, expected a string at line 1 column 16"
357 );
358 }
359}