1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use serde::de::{
5 DeserializeSeed, IgnoredAny, IntoDeserializer, MapAccess, Visitor,
6 value::{MapDeserializer, StrDeserializer},
7};
8use serde_json::value::RawValue;
9
10use crate::models::{
11 Board, Candle, CandleBorder, Engine, Index, IndexAnalytics, Market, OrderbookLevel,
12 ParseSecuritySnapshotError, SecId, SecStat, Security, SecurityBoard, SecuritySnapshot, Trade,
13 Turnover,
14};
15#[cfg(feature = "news")]
16use crate::models::{Event, SiteNews};
17#[cfg(feature = "history")]
18use crate::models::{HistoryDates, HistoryRecord};
19
20use super::MoexError;
21use super::constants::*;
22use super::convert::*;
23use super::wire::*;
24
25#[derive(Debug, serde::Deserialize)]
26struct RawIssTableRowsPayload {
27 columns: Vec<Box<str>>,
28 data: Vec<Vec<serde_json::Value>>,
29}
30
31#[derive(Debug)]
32pub struct RawTables {
37 endpoint: Box<str>,
38 blocks: HashMap<Box<str>, Box<RawValue>>,
39}
40
41impl RawTables {
42 pub fn len(&self) -> usize {
44 self.blocks.len()
45 }
46
47 pub fn is_empty(&self) -> bool {
49 self.blocks.is_empty()
50 }
51
52 pub fn table_names(&self) -> impl Iterator<Item = &str> {
57 self.blocks.keys().map(Box::as_ref)
58 }
59
60 pub fn take_rows<T>(&mut self, table: impl AsRef<str>) -> Result<Vec<T>, MoexError>
65 where
66 T: serde::de::DeserializeOwned,
67 {
68 let table = table.as_ref();
69 let raw_table = self
70 .blocks
71 .remove(table)
72 .ok_or_else(|| MoexError::MissingRawTable {
73 endpoint: self.endpoint.clone(),
74 table: table.into(),
75 })?;
76 let table_payload =
78 serde_json::from_str(raw_table.get()).map_err(|source| MoexError::Decode {
79 endpoint: format!("{} (table={})", self.endpoint, table).into_boxed_str(),
80 source,
81 })?;
82 decode_raw_table_rows_payload_with_context(table_payload, self.endpoint.as_ref(), table)
83 }
84}
85
86#[derive(Debug)]
87pub struct RawTableView<'a> {
89 columns: Vec<Cow<'a, str>>,
90 data: Vec<Vec<&'a RawValue>>,
91}
92
93impl<'a> RawTableView<'a> {
94 pub fn columns(&self) -> &[Cow<'a, str>] {
96 &self.columns
97 }
98
99 pub fn rows(&self) -> &[Vec<&'a RawValue>] {
101 &self.data
102 }
103
104 pub fn len(&self) -> usize {
106 self.data.len()
107 }
108
109 pub fn is_empty(&self) -> bool {
111 self.data.is_empty()
112 }
113
114 pub fn column_index(&self, column: &str) -> Option<usize> {
116 self.columns.iter().position(|name| name.as_ref() == column)
117 }
118
119 pub fn raw_value(&self, row: usize, column: &str) -> Option<&'a RawValue> {
121 let column_index = self.column_index(column)?;
122 self.data
123 .get(row)
124 .and_then(|values| values.get(column_index))
125 .copied()
126 }
127
128 pub fn deserialize_value<T>(
132 &self,
133 row: usize,
134 column: &str,
135 ) -> Result<Option<T>, serde_json::Error>
136 where
137 T: serde::de::Deserialize<'a>,
138 {
139 self.raw_value(row, column)
140 .map(|raw| serde_json::from_str(raw.get()))
141 .transpose()
142 }
143}
144
145struct RawIssTableRowsSeed<'a> {
146 table: &'a str,
147}
148
149impl<'de> DeserializeSeed<'de> for RawIssTableRowsSeed<'_> {
150 type Value = Option<RawIssTableRowsPayload>;
151
152 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
153 where
154 D: serde::Deserializer<'de>,
155 {
156 deserializer.deserialize_map(RawIssTableRowsVisitor { table: self.table })
157 }
158}
159
160struct RawIssTableRowsVisitor<'a> {
161 table: &'a str,
162}
163
164impl<'de> Visitor<'de> for RawIssTableRowsVisitor<'_> {
165 type Value = Option<RawIssTableRowsPayload>;
166
167 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 formatter.write_str("ISS JSON object with table blocks")
169 }
170
171 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
172 where
173 A: MapAccess<'de>,
174 {
175 let mut selected = None;
176
177 while let Some(key) = map.next_key::<Cow<'de, str>>()? {
178 if key == self.table && selected.is_none() {
179 selected = Some(map.next_value::<RawIssTableRowsPayload>()?);
180 } else {
181 map.next_value::<IgnoredAny>()?;
182 }
183 }
184
185 Ok(selected)
186 }
187}
188
189#[derive(Debug, serde::Deserialize)]
190struct BorrowedRawIssTableRowsPayload<'a> {
191 #[serde(borrow)]
192 columns: Vec<Cow<'a, str>>,
193 #[serde(borrow)]
194 data: Vec<Vec<&'a RawValue>>,
195}
196
197struct BorrowedRawIssTableRowsSeed<'a> {
198 table: &'a str,
199}
200
201impl<'de> DeserializeSeed<'de> for BorrowedRawIssTableRowsSeed<'_> {
202 type Value = Option<BorrowedRawIssTableRowsPayload<'de>>;
203
204 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
205 where
206 D: serde::Deserializer<'de>,
207 {
208 deserializer.deserialize_map(BorrowedRawIssTableRowsVisitor { table: self.table })
209 }
210}
211
212struct BorrowedRawIssTableRowsVisitor<'a> {
213 table: &'a str,
214}
215
216impl<'de> Visitor<'de> for BorrowedRawIssTableRowsVisitor<'_> {
217 type Value = Option<BorrowedRawIssTableRowsPayload<'de>>;
218
219 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 formatter.write_str("ISS JSON object with table blocks")
221 }
222
223 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
224 where
225 A: MapAccess<'de>,
226 {
227 let mut selected = None;
228
229 while let Some(key) = map.next_key::<Cow<'de, str>>()? {
230 if key == self.table && selected.is_none() {
231 selected = Some(map.next_value::<BorrowedRawIssTableRowsPayload<'de>>()?);
232 } else {
233 map.next_value::<IgnoredAny>()?;
234 }
235 }
236
237 Ok(selected)
238 }
239}
240
241fn decode_single_raw_table_payload(
242 payload: &str,
243 table: &str,
244) -> Result<Option<RawIssTableRowsPayload>, serde_json::Error> {
245 let mut deserializer = serde_json::Deserializer::from_str(payload);
246 RawIssTableRowsSeed { table }.deserialize(&mut deserializer)
247}
248
249fn decode_top_level_raw_blocks(
250 payload: &str,
251) -> Result<HashMap<Box<str>, Box<RawValue>>, serde_json::Error> {
252 serde_json::from_str(payload)
255}
256
257fn decode_single_raw_table_payload_borrowed<'a>(
258 payload: &'a str,
259 table: &str,
260) -> Result<Option<BorrowedRawIssTableRowsPayload<'a>>, serde_json::Error> {
261 let mut deserializer = serde_json::Deserializer::from_str(payload);
262 BorrowedRawIssTableRowsSeed { table }.deserialize(&mut deserializer)
263}
264
265fn decode_raw_table_row<T>(
266 columns: &[Box<str>],
267 values: Vec<serde_json::Value>,
268) -> Result<T, serde_json::Error>
269where
270 T: serde::de::DeserializeOwned,
271{
272 let entries = columns
273 .iter()
274 .map(Box::as_ref)
275 .zip(values)
276 .map(|(column, value)| {
277 (
278 StrDeserializer::<serde_json::Error>::new(column),
279 value.into_deserializer(),
280 )
281 });
282 let deserializer = MapDeserializer::new(entries);
283 <T as serde::Deserialize>::deserialize(deserializer)
284}
285
286fn decode_raw_table_rows_payload_with_context<T>(
287 table_payload: RawIssTableRowsPayload,
288 endpoint: &str,
289 table: &str,
290) -> Result<Vec<T>, MoexError>
291where
292 T: serde::de::DeserializeOwned,
293{
294 let RawIssTableRowsPayload { columns, data } = table_payload;
295 let expected_width = columns.len();
296 let mut rows = Vec::with_capacity(data.len());
297
298 for (row, values) in data.into_iter().enumerate() {
299 let actual_width = values.len();
300 if actual_width != expected_width {
301 return Err(MoexError::InvalidRawTableRowWidth {
302 endpoint: endpoint.into(),
303 table: table.into(),
304 row,
305 expected: expected_width,
306 actual: actual_width,
307 });
308 }
309
310 let decoded = decode_raw_table_row::<T>(&columns, values).map_err(|source| {
311 MoexError::InvalidRawTableRow {
312 endpoint: endpoint.into(),
313 table: table.into(),
314 row,
315 source,
316 }
317 })?;
318 rows.push(decoded);
319 }
320
321 Ok(rows)
322}
323
324pub(super) fn decode_raw_table_rows_json_with_endpoint<T>(
325 payload: &str,
326 endpoint: &str,
327 table: &str,
328) -> Result<Vec<T>, MoexError>
329where
330 T: serde::de::DeserializeOwned,
331{
332 let table_payload = decode_single_raw_table_payload(payload, table)
333 .map_err(|source| MoexError::Decode {
334 endpoint: endpoint.into(),
335 source,
336 })?
337 .ok_or_else(|| MoexError::MissingRawTable {
338 endpoint: endpoint.into(),
339 table: table.into(),
340 })?;
341 decode_raw_table_rows_payload_with_context(table_payload, endpoint, table)
342}
343
344pub(super) fn decode_raw_tables_json_with_endpoint(
345 payload: &str,
346 endpoint: &str,
347) -> Result<RawTables, MoexError> {
348 let endpoint = endpoint.to_owned().into_boxed_str();
349 let raw_blocks = decode_top_level_raw_blocks(payload).map_err(|source| MoexError::Decode {
350 endpoint: endpoint.clone(),
351 source,
352 })?;
353 Ok(RawTables {
354 endpoint,
355 blocks: raw_blocks,
356 })
357}
358
359pub(super) fn decode_raw_table_view_json_with_endpoint<'a>(
360 payload: &'a str,
361 endpoint: &str,
362 table: &str,
363) -> Result<RawTableView<'a>, MoexError> {
364 let endpoint = endpoint.to_owned().into_boxed_str();
365 let table = table.to_owned().into_boxed_str();
366
367 let table_payload = decode_single_raw_table_payload_borrowed(payload, table.as_ref())
368 .map_err(|source| MoexError::Decode {
369 endpoint: endpoint.clone(),
370 source,
371 })?
372 .ok_or_else(|| MoexError::MissingRawTable {
373 endpoint: endpoint.clone(),
374 table: table.clone(),
375 })?;
376
377 let expected_width = table_payload.columns.len();
378 for (row, values) in table_payload.data.iter().enumerate() {
379 let actual_width = values.len();
380 if actual_width != expected_width {
381 return Err(MoexError::InvalidRawTableRowWidth {
382 endpoint: endpoint.clone(),
383 table: table.clone(),
384 row,
385 expected: expected_width,
386 actual: actual_width,
387 });
388 }
389 }
390
391 Ok(RawTableView {
392 columns: table_payload.columns,
393 data: table_payload.data,
394 })
395}
396
397pub(super) fn decode_indexes_json_payload(payload: &str) -> Result<Vec<Index>, MoexError> {
398 let payload: IndexesResponse =
399 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
400 endpoint: INDEXES_ENDPOINT.to_owned().into_boxed_str(),
401 source,
402 })?;
403 convert_index_rows(payload.indices.data, INDEXES_ENDPOINT)
404}
405
406pub(super) fn decode_engines_json_payload(payload: &str) -> Result<Vec<Engine>, MoexError> {
407 let payload: EnginesResponse =
408 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
409 endpoint: ENGINES_ENDPOINT.to_owned().into_boxed_str(),
410 source,
411 })?;
412 convert_engine_rows(payload.engines.data, ENGINES_ENDPOINT)
413}
414
415pub(super) fn decode_markets_json_with_endpoint(
416 payload: &str,
417 endpoint: &str,
418) -> Result<Vec<Market>, MoexError> {
419 let payload: MarketsResponse =
420 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
421 endpoint: endpoint.to_owned().into_boxed_str(),
422 source,
423 })?;
424 convert_market_rows(payload.markets.data, endpoint)
425}
426
427pub(super) fn decode_boards_json_with_endpoint(
428 payload: &str,
429 endpoint: &str,
430) -> Result<Vec<Board>, MoexError> {
431 let payload: BoardsResponse =
432 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
433 endpoint: endpoint.to_owned().into_boxed_str(),
434 source,
435 })?;
436 convert_board_rows(payload.boards.data, endpoint)
437}
438
439pub(super) fn decode_security_boards_json_with_endpoint(
440 payload: &str,
441 endpoint: &str,
442) -> Result<Vec<SecurityBoard>, MoexError> {
443 let payload: SecurityBoardsResponse =
444 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
445 endpoint: endpoint.to_owned().into_boxed_str(),
446 source,
447 })?;
448 convert_security_board_rows(payload.boards.data, endpoint)
449}
450
451pub(super) fn decode_securities_json_with_endpoint(
452 payload: &str,
453 endpoint: &str,
454) -> Result<Vec<Security>, MoexError> {
455 let payload: SecuritiesResponse =
456 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
457 endpoint: endpoint.to_owned().into_boxed_str(),
458 source,
459 })?;
460 convert_security_rows(payload.securities.data, endpoint)
461}
462
463pub(super) fn decode_board_security_snapshots_json_with_endpoint(
464 payload: &str,
465 endpoint: &str,
466) -> Result<Vec<SecuritySnapshot>, MoexError> {
467 let payload: BoardSecuritySnapshotsResponse =
468 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
469 endpoint: endpoint.to_owned().into_boxed_str(),
470 source,
471 })?;
472
473 let mut last_by_secid: HashMap<SecId, (usize, Option<f64>)> =
474 HashMap::with_capacity(payload.marketdata.data.len());
475 for (row, BoardMarketDataRow(secid, last)) in payload.marketdata.data.into_iter().enumerate() {
476 if let Some(last) = last
477 && !last.is_finite()
478 {
479 return Err(MoexError::InvalidSecuritySnapshot {
480 endpoint: endpoint.to_owned().into_boxed_str(),
481 table: "marketdata",
482 row,
483 source: ParseSecuritySnapshotError::NonFiniteLast(last),
484 });
485 }
486 let secid = parse_snapshot_secid(endpoint, "marketdata", row, secid)?;
487 last_by_secid.insert(secid, (row, last));
488 }
489
490 let mut snapshots = Vec::with_capacity(payload.securities.data.len().max(last_by_secid.len()));
491 for (row, BoardSecurityRow(secid, lot_size_raw)) in
492 payload.securities.data.into_iter().enumerate()
493 {
494 let secid = parse_snapshot_secid(endpoint, "securities", row, secid)?;
495 let lot_size = parse_snapshot_lot_size(endpoint, "securities", row, lot_size_raw)?;
496 let last = last_by_secid.remove(&secid).and_then(|(_, last)| last);
497
498 let snapshot =
499 SecuritySnapshot::try_from_parts(secid, lot_size, last).map_err(|source| {
500 MoexError::InvalidSecuritySnapshot {
501 endpoint: endpoint.to_owned().into_boxed_str(),
502 table: "securities",
503 row,
504 source,
505 }
506 })?;
507 snapshots.push(snapshot);
508 }
509
510 for (secid, (row, last)) in last_by_secid {
511 let snapshot = SecuritySnapshot::try_from_parts(secid, None, last).map_err(|source| {
512 MoexError::InvalidSecuritySnapshot {
513 endpoint: endpoint.to_owned().into_boxed_str(),
514 table: "marketdata",
515 row,
516 source,
517 }
518 })?;
519 snapshots.push(snapshot);
520 }
521
522 Ok(snapshots)
523}
524
525pub(super) fn decode_orderbook_json_with_endpoint(
526 payload: &str,
527 endpoint: &str,
528) -> Result<Vec<OrderbookLevel>, MoexError> {
529 let payload: OrderbookResponse =
530 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
531 endpoint: endpoint.to_owned().into_boxed_str(),
532 source,
533 })?;
534 convert_orderbook_rows(payload.orderbook.data, endpoint)
535}
536
537pub(super) fn decode_candle_borders_json_with_endpoint(
538 payload: &str,
539 endpoint: &str,
540) -> Result<Vec<CandleBorder>, MoexError> {
541 let payload: CandleBordersResponse =
542 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
543 endpoint: endpoint.to_owned().into_boxed_str(),
544 source,
545 })?;
546 convert_candle_border_rows(payload.borders.data, endpoint)
547}
548
549pub(super) fn decode_candles_json_with_endpoint(
550 payload: &str,
551 endpoint: &str,
552) -> Result<Vec<Candle>, MoexError> {
553 let payload: CandlesResponse =
554 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
555 endpoint: endpoint.to_owned().into_boxed_str(),
556 source,
557 })?;
558 convert_candle_rows(payload.candles.data, endpoint)
559}
560
561pub(super) fn decode_trades_json_with_endpoint(
562 payload: &str,
563 endpoint: &str,
564) -> Result<Vec<Trade>, MoexError> {
565 let payload: TradesResponse =
566 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
567 endpoint: endpoint.to_owned().into_boxed_str(),
568 source,
569 })?;
570 convert_trade_rows(payload.trades.data, endpoint)
571}
572
573pub(super) fn decode_index_analytics_json_with_endpoint(
574 payload: &str,
575 endpoint: &str,
576) -> Result<Vec<IndexAnalytics>, MoexError> {
577 let payload: IndexAnalyticsResponse =
578 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
579 endpoint: endpoint.to_owned().into_boxed_str(),
580 source,
581 })?;
582 convert_index_analytics_rows(payload.analytics.data, endpoint)
583}
584
585#[cfg(feature = "history")]
586pub(super) fn decode_history_dates_json_with_endpoint(
587 payload: &str,
588 endpoint: &str,
589) -> Result<Vec<HistoryDates>, MoexError> {
590 let payload: HistoryDatesResponse =
591 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
592 endpoint: endpoint.to_owned().into_boxed_str(),
593 source,
594 })?;
595 convert_history_dates_rows(payload.dates.data, endpoint)
596}
597
598#[cfg(feature = "history")]
599pub(super) fn decode_history_json_with_endpoint(
600 payload: &str,
601 endpoint: &str,
602) -> Result<Vec<HistoryRecord>, MoexError> {
603 let payload: HistoryResponse =
604 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
605 endpoint: endpoint.to_owned().into_boxed_str(),
606 source,
607 })?;
608 convert_history_rows(payload.history.data, endpoint)
609}
610
611pub(super) fn decode_turnovers_json_with_endpoint(
612 payload: &str,
613 endpoint: &str,
614) -> Result<Vec<Turnover>, MoexError> {
615 let payload: TurnoversResponse =
616 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
617 endpoint: endpoint.to_owned().into_boxed_str(),
618 source,
619 })?;
620 convert_turnover_rows(payload.turnovers.data, endpoint)
621}
622
623#[cfg(feature = "news")]
624pub(super) fn decode_sitenews_json_with_endpoint(
625 payload: &str,
626 endpoint: &str,
627) -> Result<Vec<SiteNews>, MoexError> {
628 let payload: SiteNewsResponse =
629 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
630 endpoint: endpoint.to_owned().into_boxed_str(),
631 source,
632 })?;
633 convert_sitenews_rows(payload.sitenews.data, endpoint)
634}
635
636#[cfg(feature = "news")]
637pub(super) fn decode_events_json_with_endpoint(
638 payload: &str,
639 endpoint: &str,
640) -> Result<Vec<Event>, MoexError> {
641 let payload: EventsResponse =
642 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
643 endpoint: endpoint.to_owned().into_boxed_str(),
644 source,
645 })?;
646 convert_event_rows(payload.events.data, endpoint)
647}
648
649pub(super) fn decode_secstats_json_with_endpoint(
650 payload: &str,
651 endpoint: &str,
652) -> Result<Vec<SecStat>, MoexError> {
653 let payload: SecStatsResponse =
654 serde_json::from_str(payload).map_err(|source| MoexError::Decode {
655 endpoint: endpoint.to_owned().into_boxed_str(),
656 source,
657 })?;
658 convert_secstats_rows(payload.secstats.data, endpoint)
659}
660
661fn parse_snapshot_secid(
662 endpoint: &str,
663 table: &'static str,
664 row: usize,
665 secid: String,
666) -> Result<SecId, MoexError> {
667 SecId::try_from(secid).map_err(|source| MoexError::InvalidSecuritySnapshot {
668 endpoint: endpoint.to_owned().into_boxed_str(),
669 table,
670 row,
671 source: ParseSecuritySnapshotError::InvalidSecId(source),
672 })
673}
674
675fn parse_snapshot_lot_size(
676 endpoint: &str,
677 table: &'static str,
678 row: usize,
679 lot_size: Option<i64>,
680) -> Result<Option<u32>, MoexError> {
681 match lot_size {
682 None => Ok(None),
683 Some(raw) if raw < 0 => Err(MoexError::InvalidSecuritySnapshot {
684 endpoint: endpoint.to_owned().into_boxed_str(),
685 table,
686 row,
687 source: ParseSecuritySnapshotError::NegativeLotSize(raw),
688 }),
689 Some(raw) => u32::try_from(raw)
690 .map(Some)
691 .map_err(|_| MoexError::InvalidSecuritySnapshot {
692 endpoint: endpoint.to_owned().into_boxed_str(),
693 table,
694 row,
695 source: ParseSecuritySnapshotError::LotSizeOutOfRange(raw),
696 }),
697 }
698}