1use std::num::NonZeroU32;
2#[cfg(any(feature = "blocking", feature = "async"))]
3use std::sync::Mutex;
4use std::time::Duration;
5
6#[cfg(feature = "blocking")]
7use reqwest::blocking::{Client, ClientBuilder};
8use reqwest::{Url, header::HeaderMap};
9
10use crate::models::{
11 Board, BoardId, Candle, CandleBorder, CandleQuery, Engine, EngineName, Index, IndexAnalytics,
12 IndexId, Market, MarketName, OrderbookLevel, PageRequest, Pagination, ParseBoardIdError,
13 ParseEngineNameError, ParseIndexError, ParseMarketNameError, ParseSecIdError, SecId, SecStat,
14 Security, SecurityBoard, SecuritySnapshot, Trade, Turnover,
15};
16#[cfg(feature = "news")]
17use crate::models::{Event, SiteNews};
18#[cfg(feature = "history")]
19use crate::models::{HistoryDates, HistoryRecord};
20
21use super::constants::*;
22use super::payload::{
23 decode_board_security_snapshots_json_with_endpoint, decode_boards_json_with_endpoint,
24 decode_candle_borders_json_with_endpoint, decode_candles_json_with_endpoint,
25 decode_engines_json_payload, decode_index_analytics_json_with_endpoint,
26 decode_indexes_json_payload, decode_markets_json_with_endpoint,
27 decode_orderbook_json_with_endpoint, decode_raw_table_rows_json_with_endpoint,
28 decode_secstats_json_with_endpoint, decode_securities_json_with_endpoint,
29 decode_security_boards_json_with_endpoint, decode_trades_json_with_endpoint,
30 decode_turnovers_json_with_endpoint,
31};
32#[cfg(feature = "news")]
33use super::payload::{decode_events_json_with_endpoint, decode_sitenews_json_with_endpoint};
34#[cfg(feature = "history")]
35use super::payload::{decode_history_dates_json_with_endpoint, decode_history_json_with_endpoint};
36use super::{
37 IssEndpoint, IssRequestOptions, IssToggle, MoexError, RawIssResponse, RepeatPagePolicy,
38};
39#[cfg(any(feature = "blocking", feature = "async"))]
40use super::{RateLimit, RateLimiter};
41
42#[cfg(feature = "async")]
43type AsyncSleepFuture = std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 'static>>;
44
45#[cfg(feature = "async")]
46type AsyncRateLimitSleep = std::sync::Arc<dyn Fn(Duration) -> AsyncSleepFuture + Send + Sync>;
47
48enum PaginationAdvance {
49 YieldPage,
50 EndOfPages,
51}
52
53#[cfg(feature = "blocking")]
58pub struct BlockingMoexClient {
59 base_url: Url,
60 metadata: bool,
61 client: Client,
62 rate_limiter: Option<Mutex<RateLimiter>>,
63}
64
65#[cfg(feature = "blocking")]
66impl BlockingMoexClient {
67 pub fn builder() -> BlockingMoexClientBuilder {
69 BlockingMoexClientBuilder {
70 base_url: None,
71 metadata: false,
72 client: None,
73 http_client: Client::builder(),
74 rate_limit: None,
75 }
76 }
77
78 pub fn new() -> Result<Self, MoexError> {
80 Self::builder().build()
81 }
82
83 pub fn new_with_metadata() -> Result<Self, MoexError> {
85 Self::builder().metadata(true).build()
86 }
87
88 pub fn with_client(client: Client) -> Result<Self, MoexError> {
92 Self::builder().client(client).build()
93 }
94
95 pub fn with_client_with_metadata(client: Client) -> Result<Self, MoexError> {
97 Self::builder().metadata(true).client(client).build()
98 }
99
100 pub fn with_base_url(client: Client, base_url: Url) -> Self {
102 Self::with_base_url_and_rate_limit(client, base_url, false, None)
103 }
104
105 pub fn with_base_url_with_metadata(client: Client, base_url: Url) -> Self {
107 Self::with_base_url_and_rate_limit(client, base_url, true, None)
108 }
109
110 pub fn rate_limit(&self) -> Option<RateLimit> {
112 self.rate_limiter
113 .as_ref()
114 .map(|limiter| lock_rate_limiter(limiter).limit())
115 }
116
117 fn with_base_url_and_rate_limit(
118 client: Client,
119 base_url: Url,
120 metadata: bool,
121 rate_limit: Option<RateLimit>,
122 ) -> Self {
123 Self {
124 base_url,
125 metadata,
126 client,
127 rate_limiter: rate_limit.map(|limit| Mutex::new(RateLimiter::new(limit))),
128 }
129 }
130
131 pub fn raw(&self) -> RawIssRequestBuilder<'_> {
133 RawIssRequestBuilder {
134 client: self,
135 path: None,
136 query: Vec::new(),
137 }
138 }
139
140 pub fn raw_endpoint(&self, endpoint: IssEndpoint<'_>) -> RawIssRequestBuilder<'_> {
144 let path = endpoint.path();
145 let request = self.raw().path(path);
146 match endpoint.default_table() {
147 Some(table) => request.only(table),
148 None => request,
149 }
150 }
151
152 pub fn indexes(&self) -> Result<Vec<Index>, MoexError> {
154 let payload = self.get_payload(
155 INDEXES_ENDPOINT,
156 &[
157 (ISS_META_PARAM, metadata_value(self.metadata)),
158 (ISS_ONLY_PARAM, "indices"),
159 (INDICES_COLUMNS_PARAM, INDICES_COLUMNS),
160 ],
161 )?;
162 decode_indexes_json_payload(&payload)
163 }
164
165 pub fn index_analytics_query(
167 &self,
168 indexid: &IndexId,
169 page_request: PageRequest,
170 ) -> Result<Vec<IndexAnalytics>, MoexError> {
171 match page_request {
172 PageRequest::FirstPage => {
173 self.fetch_index_analytics_page(indexid, Pagination::default())
174 }
175 PageRequest::Page(pagination) => self.fetch_index_analytics_page(indexid, pagination),
176 PageRequest::All { page_limit } => {
177 self.index_analytics_pages(indexid, page_limit).all()
178 }
179 }
180 }
181
182 pub fn index_analytics_pages<'a>(
184 &'a self,
185 indexid: &'a IndexId,
186 page_limit: NonZeroU32,
187 ) -> IndexAnalyticsPages<'a> {
188 IndexAnalyticsPages {
189 client: self,
190 indexid,
191 pagination: PaginationTracker::new(
192 index_analytics_endpoint(indexid),
193 page_limit,
194 RepeatPagePolicy::Error,
195 ),
196 }
197 }
198
199 pub fn turnovers(&self) -> Result<Vec<Turnover>, MoexError> {
201 let payload = self.get_payload(
202 TURNOVERS_ENDPOINT,
203 &[
204 (ISS_META_PARAM, metadata_value(self.metadata)),
205 (ISS_ONLY_PARAM, "turnovers"),
206 (TURNOVERS_COLUMNS_PARAM, TURNOVERS_COLUMNS),
207 ],
208 )?;
209 decode_turnovers_json_with_endpoint(&payload, TURNOVERS_ENDPOINT)
210 }
211
212 pub fn engine_turnovers(&self, engine: &EngineName) -> Result<Vec<Turnover>, MoexError> {
214 let endpoint = engine_turnovers_endpoint(engine);
215 let payload = self.get_payload(
216 endpoint.as_str(),
217 &[
218 (ISS_META_PARAM, metadata_value(self.metadata)),
219 (ISS_ONLY_PARAM, "turnovers"),
220 (TURNOVERS_COLUMNS_PARAM, TURNOVERS_COLUMNS),
221 ],
222 )?;
223 decode_turnovers_json_with_endpoint(&payload, endpoint.as_str())
224 }
225
226 #[cfg(feature = "news")]
227 pub fn sitenews_query(&self, page_request: PageRequest) -> Result<Vec<SiteNews>, MoexError> {
229 match page_request {
230 PageRequest::FirstPage => self.fetch_sitenews_page(Pagination::default()),
231 PageRequest::Page(pagination) => self.fetch_sitenews_page(pagination),
232 PageRequest::All { page_limit } => self.sitenews_pages(page_limit).all(),
233 }
234 }
235
236 #[cfg(feature = "news")]
237 pub fn sitenews_pages<'a>(&'a self, page_limit: NonZeroU32) -> SiteNewsPages<'a> {
239 SiteNewsPages {
240 client: self,
241 pagination: PaginationTracker::new(
242 SITENEWS_ENDPOINT,
243 page_limit,
244 RepeatPagePolicy::Error,
245 ),
246 }
247 }
248
249 #[cfg(feature = "news")]
250 pub fn events_query(&self, page_request: PageRequest) -> Result<Vec<Event>, MoexError> {
252 match page_request {
253 PageRequest::FirstPage => self.fetch_events_page(Pagination::default()),
254 PageRequest::Page(pagination) => self.fetch_events_page(pagination),
255 PageRequest::All { page_limit } => self.events_pages(page_limit).all(),
256 }
257 }
258
259 #[cfg(feature = "news")]
260 pub fn events_pages<'a>(&'a self, page_limit: NonZeroU32) -> EventsPages<'a> {
262 EventsPages {
263 client: self,
264 pagination: PaginationTracker::new(
265 EVENTS_ENDPOINT,
266 page_limit,
267 RepeatPagePolicy::Error,
268 ),
269 }
270 }
271
272 pub fn secstats_query(
274 &self,
275 engine: &EngineName,
276 market: &MarketName,
277 page_request: PageRequest,
278 ) -> Result<Vec<SecStat>, MoexError> {
279 match page_request {
280 PageRequest::FirstPage => {
281 self.fetch_secstats_page(engine, market, Pagination::default())
282 }
283 PageRequest::Page(pagination) => self.fetch_secstats_page(engine, market, pagination),
284 PageRequest::All { page_limit } => {
285 self.secstats_pages(engine, market, page_limit).all()
286 }
287 }
288 }
289
290 pub fn secstats_pages<'a>(
292 &'a self,
293 engine: &'a EngineName,
294 market: &'a MarketName,
295 page_limit: NonZeroU32,
296 ) -> SecStatsPages<'a> {
297 SecStatsPages {
298 client: self,
299 engine,
300 market,
301 pagination: PaginationTracker::new(
302 secstats_endpoint(engine, market),
303 page_limit,
304 RepeatPagePolicy::Error,
305 ),
306 }
307 }
308
309 pub fn engines(&self) -> Result<Vec<Engine>, MoexError> {
311 let payload = self.get_payload(
312 ENGINES_ENDPOINT,
313 &[
314 (ISS_META_PARAM, metadata_value(self.metadata)),
315 (ISS_ONLY_PARAM, "engines"),
316 (ENGINES_COLUMNS_PARAM, ENGINES_COLUMNS),
317 ],
318 )?;
319 decode_engines_json_payload(&payload)
320 }
321
322 pub fn markets(&self, engine: &EngineName) -> Result<Vec<Market>, MoexError> {
324 let endpoint = markets_endpoint(engine);
325 let payload = self.get_payload(
326 endpoint.as_str(),
327 &[
328 (ISS_META_PARAM, metadata_value(self.metadata)),
329 (ISS_ONLY_PARAM, "markets"),
330 (MARKETS_COLUMNS_PARAM, MARKETS_COLUMNS),
331 ],
332 )?;
333 decode_markets_json_with_endpoint(&payload, endpoint.as_str())
334 }
335
336 pub fn boards(
338 &self,
339 engine: &EngineName,
340 market: &MarketName,
341 ) -> Result<Vec<Board>, MoexError> {
342 let endpoint = boards_endpoint(engine, market);
343 let payload = self.get_payload(
344 endpoint.as_str(),
345 &[
346 (ISS_META_PARAM, metadata_value(self.metadata)),
347 (ISS_ONLY_PARAM, "boards"),
348 (BOARDS_COLUMNS_PARAM, BOARDS_COLUMNS),
349 ],
350 )?;
351 decode_boards_json_with_endpoint(&payload, endpoint.as_str())
352 }
353
354 pub fn security_boards(&self, security: &SecId) -> Result<Vec<SecurityBoard>, MoexError> {
356 let endpoint = security_boards_endpoint(security);
357 let payload = self.get_payload(
358 endpoint.as_str(),
359 &[
360 (ISS_META_PARAM, metadata_value(self.metadata)),
361 (ISS_ONLY_PARAM, "boards"),
362 (BOARDS_COLUMNS_PARAM, SECURITY_BOARDS_COLUMNS),
363 ],
364 )?;
365 decode_security_boards_json_with_endpoint(&payload, endpoint.as_str())
366 }
367
368 pub fn security_info(&self, security: &SecId) -> Result<Option<Security>, MoexError> {
372 let endpoint = security_endpoint(security);
373 let payload = self.get_payload(
374 endpoint.as_str(),
375 &[
376 (ISS_META_PARAM, metadata_value(self.metadata)),
377 (ISS_ONLY_PARAM, "securities"),
378 (SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS),
379 ],
380 )?;
381 let securities = decode_securities_json_with_endpoint(&payload, endpoint.as_str())?;
382 optional_single_security(endpoint.as_str(), securities)
383 }
384
385 #[cfg(feature = "history")]
386 pub fn history_dates(
390 &self,
391 engine: &EngineName,
392 market: &MarketName,
393 board: &BoardId,
394 security: &SecId,
395 ) -> Result<Option<HistoryDates>, MoexError> {
396 let endpoint = history_dates_endpoint(engine, market, board, security);
397 let payload = self.get_payload(
398 endpoint.as_str(),
399 &[
400 (ISS_META_PARAM, metadata_value(self.metadata)),
401 (ISS_ONLY_PARAM, "dates"),
402 ],
403 )?;
404 let dates = decode_history_dates_json_with_endpoint(&payload, endpoint.as_str())?;
405 optional_single_history_dates(endpoint.as_str(), dates)
406 }
407
408 #[cfg(feature = "history")]
409 pub fn history_query(
411 &self,
412 engine: &EngineName,
413 market: &MarketName,
414 board: &BoardId,
415 security: &SecId,
416 page_request: PageRequest,
417 ) -> Result<Vec<HistoryRecord>, MoexError> {
418 match page_request {
419 PageRequest::FirstPage => {
420 self.fetch_history_page(engine, market, board, security, Pagination::default())
421 }
422 PageRequest::Page(pagination) => {
423 self.fetch_history_page(engine, market, board, security, pagination)
424 }
425 PageRequest::All { page_limit } => self
426 .history_pages(engine, market, board, security, page_limit)
427 .all(),
428 }
429 }
430
431 #[cfg(feature = "history")]
432 pub fn history_pages<'a>(
434 &'a self,
435 engine: &'a EngineName,
436 market: &'a MarketName,
437 board: &'a BoardId,
438 security: &'a SecId,
439 page_limit: NonZeroU32,
440 ) -> HistoryPages<'a> {
441 HistoryPages {
442 client: self,
443 engine,
444 market,
445 board,
446 security,
447 pagination: PaginationTracker::new(
448 history_endpoint(engine, market, board, security),
449 page_limit,
450 RepeatPagePolicy::Error,
451 ),
452 }
453 }
454
455 pub fn board_snapshots(
457 &self,
458 engine: &EngineName,
459 market: &MarketName,
460 board: &BoardId,
461 ) -> Result<Vec<SecuritySnapshot>, MoexError> {
462 let endpoint = securities_endpoint(engine, market, board);
463 let payload = self.get_payload(
464 endpoint.as_str(),
465 &[
466 (ISS_META_PARAM, metadata_value(self.metadata)),
467 (ISS_ONLY_PARAM, "securities,marketdata"),
468 (SECURITIES_COLUMNS_PARAM, SECURITIES_SNAPSHOT_COLUMNS),
469 (MARKETDATA_COLUMNS_PARAM, MARKETDATA_LAST_COLUMNS),
470 ],
471 )?;
472 decode_board_security_snapshots_json_with_endpoint(&payload, endpoint.as_str())
473 }
474
475 pub fn board_security_snapshots(
477 &self,
478 board: &SecurityBoard,
479 ) -> Result<Vec<SecuritySnapshot>, MoexError> {
480 self.board_snapshots(board.engine(), board.market(), board.boardid())
481 }
482
483 pub fn engine<E>(&self, engine: E) -> Result<OwnedEngineScope<'_>, ParseEngineNameError>
485 where
486 E: TryInto<EngineName>,
487 E::Error: Into<ParseEngineNameError>,
488 {
489 let engine = engine.try_into().map_err(Into::into)?;
490 Ok(OwnedEngineScope {
491 client: self,
492 engine,
493 })
494 }
495
496 pub fn stock(&self) -> Result<OwnedEngineScope<'_>, ParseEngineNameError> {
498 self.engine("stock")
499 }
500
501 pub fn index<I>(&self, indexid: I) -> Result<OwnedIndexScope<'_>, ParseIndexError>
503 where
504 I: TryInto<IndexId>,
505 I::Error: Into<ParseIndexError>,
506 {
507 let indexid = indexid.try_into().map_err(Into::into)?;
508 Ok(OwnedIndexScope {
509 client: self,
510 indexid,
511 })
512 }
513
514 pub fn security<S>(
516 &self,
517 security: S,
518 ) -> Result<OwnedSecurityResourceScope<'_>, ParseSecIdError>
519 where
520 S: TryInto<SecId>,
521 S::Error: Into<ParseSecIdError>,
522 {
523 let security = security.try_into().map_err(Into::into)?;
524 Ok(OwnedSecurityResourceScope {
525 client: self,
526 security,
527 })
528 }
529
530 pub fn global_securities_query(
532 &self,
533 page_request: PageRequest,
534 ) -> Result<Vec<Security>, MoexError> {
535 match page_request {
536 PageRequest::FirstPage => self.fetch_global_securities_page(Pagination::default()),
537 PageRequest::Page(pagination) => self.fetch_global_securities_page(pagination),
538 PageRequest::All { page_limit } => self.global_securities_pages(page_limit).all(),
539 }
540 }
541
542 pub fn global_securities_pages<'a>(
544 &'a self,
545 page_limit: NonZeroU32,
546 ) -> GlobalSecuritiesPages<'a> {
547 GlobalSecuritiesPages {
548 client: self,
549 pagination: PaginationTracker::new(
550 GLOBAL_SECURITIES_ENDPOINT,
551 page_limit,
552 RepeatPagePolicy::Error,
553 ),
554 }
555 }
556
557 pub fn market_security_info(
561 &self,
562 engine: &EngineName,
563 market: &MarketName,
564 security: &SecId,
565 ) -> Result<Option<Security>, MoexError> {
566 let endpoint = market_security_endpoint(engine, market, security);
567 let payload = self.get_payload(
568 endpoint.as_str(),
569 &[
570 (ISS_META_PARAM, metadata_value(self.metadata)),
571 (ISS_ONLY_PARAM, "securities"),
572 (SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS),
573 ],
574 )?;
575 let securities = decode_securities_json_with_endpoint(&payload, endpoint.as_str())?;
576 optional_single_security(endpoint.as_str(), securities)
577 }
578
579 pub fn market_securities_query(
581 &self,
582 engine: &EngineName,
583 market: &MarketName,
584 page_request: PageRequest,
585 ) -> Result<Vec<Security>, MoexError> {
586 match page_request {
587 PageRequest::FirstPage => {
588 self.fetch_market_securities_page(engine, market, Pagination::default())
589 }
590 PageRequest::Page(pagination) => {
591 self.fetch_market_securities_page(engine, market, pagination)
592 }
593 PageRequest::All { page_limit } => self
594 .market_securities_pages(engine, market, page_limit)
595 .all(),
596 }
597 }
598
599 pub fn market_securities_pages<'a>(
601 &'a self,
602 engine: &'a EngineName,
603 market: &'a MarketName,
604 page_limit: NonZeroU32,
605 ) -> MarketSecuritiesPages<'a> {
606 MarketSecuritiesPages {
607 client: self,
608 engine,
609 market,
610 pagination: PaginationTracker::new(
611 market_securities_endpoint(engine, market),
612 page_limit,
613 RepeatPagePolicy::Error,
614 ),
615 }
616 }
617
618 pub fn market_orderbook(
620 &self,
621 engine: &EngineName,
622 market: &MarketName,
623 ) -> Result<Vec<OrderbookLevel>, MoexError> {
624 let endpoint = market_orderbook_endpoint(engine, market);
625 let payload = self.get_payload(
626 endpoint.as_str(),
627 &[
628 (ISS_META_PARAM, metadata_value(self.metadata)),
629 (ISS_ONLY_PARAM, "orderbook"),
630 (ORDERBOOK_COLUMNS_PARAM, ORDERBOOK_COLUMNS),
631 ],
632 )?;
633 decode_orderbook_json_with_endpoint(&payload, endpoint.as_str())
634 }
635
636 pub fn candle_borders(
638 &self,
639 engine: &EngineName,
640 market: &MarketName,
641 security: &SecId,
642 ) -> Result<Vec<CandleBorder>, MoexError> {
643 let endpoint = candleborders_endpoint(engine, market, security);
644 let payload = self.get_payload(
645 endpoint.as_str(),
646 &[(ISS_META_PARAM, metadata_value(self.metadata))],
647 )?;
648 decode_candle_borders_json_with_endpoint(&payload, endpoint.as_str())
649 }
650
651 pub fn market_trades_query(
653 &self,
654 engine: &EngineName,
655 market: &MarketName,
656 page_request: PageRequest,
657 ) -> Result<Vec<Trade>, MoexError> {
658 match page_request {
659 PageRequest::FirstPage => {
660 self.fetch_market_trades_page(engine, market, Pagination::default())
661 }
662 PageRequest::Page(pagination) => {
663 self.fetch_market_trades_page(engine, market, pagination)
664 }
665 PageRequest::All { page_limit } => {
666 self.market_trades_pages(engine, market, page_limit).all()
667 }
668 }
669 }
670
671 pub fn market_trades_pages<'a>(
673 &'a self,
674 engine: &'a EngineName,
675 market: &'a MarketName,
676 page_limit: NonZeroU32,
677 ) -> MarketTradesPages<'a> {
678 MarketTradesPages {
679 client: self,
680 engine,
681 market,
682 pagination: PaginationTracker::new(
683 market_trades_endpoint(engine, market),
684 page_limit,
685 RepeatPagePolicy::Error,
686 ),
687 }
688 }
689
690 pub fn securities_query(
696 &self,
697 engine: &EngineName,
698 market: &MarketName,
699 board: &BoardId,
700 page_request: PageRequest,
701 ) -> Result<Vec<Security>, MoexError> {
702 match page_request {
703 PageRequest::FirstPage => {
704 self.fetch_securities_page(engine, market, board, Pagination::default())
705 }
706 PageRequest::Page(pagination) => {
707 self.fetch_securities_page(engine, market, board, pagination)
708 }
709 PageRequest::All { page_limit } => self
710 .securities_pages(engine, market, board, page_limit)
711 .all(),
712 }
713 }
714
715 pub fn securities_pages<'a>(
717 &'a self,
718 engine: &'a EngineName,
719 market: &'a MarketName,
720 board: &'a BoardId,
721 page_limit: NonZeroU32,
722 ) -> SecuritiesPages<'a> {
723 SecuritiesPages {
724 client: self,
725 engine,
726 market,
727 board,
728 pagination: PaginationTracker::new(
729 securities_endpoint(engine, market, board),
730 page_limit,
731 RepeatPagePolicy::Error,
732 ),
733 }
734 }
735
736 pub fn orderbook(
738 &self,
739 engine: &EngineName,
740 market: &MarketName,
741 board: &BoardId,
742 security: &SecId,
743 ) -> Result<Vec<OrderbookLevel>, MoexError> {
744 let endpoint = orderbook_endpoint(engine, market, board, security);
745 let payload = self.get_payload(
746 endpoint.as_str(),
747 &[
748 (ISS_META_PARAM, metadata_value(self.metadata)),
749 (ISS_ONLY_PARAM, "orderbook"),
750 (ORDERBOOK_COLUMNS_PARAM, ORDERBOOK_COLUMNS),
751 ],
752 )?;
753 decode_orderbook_json_with_endpoint(&payload, endpoint.as_str())
754 }
755
756 pub fn candles_query(
758 &self,
759 engine: &EngineName,
760 market: &MarketName,
761 board: &BoardId,
762 security: &SecId,
763 query: CandleQuery,
764 page_request: PageRequest,
765 ) -> Result<Vec<Candle>, MoexError> {
766 match page_request {
767 PageRequest::FirstPage => self.fetch_candles_page(
768 engine,
769 market,
770 board,
771 security,
772 query,
773 Pagination::default(),
774 ),
775 PageRequest::Page(pagination) => {
776 self.fetch_candles_page(engine, market, board, security, query, pagination)
777 }
778 PageRequest::All { page_limit } => self
779 .candles_pages(engine, market, board, security, query, page_limit)
780 .all(),
781 }
782 }
783
784 pub fn candles_pages<'a>(
786 &'a self,
787 engine: &'a EngineName,
788 market: &'a MarketName,
789 board: &'a BoardId,
790 security: &'a SecId,
791 query: CandleQuery,
792 page_limit: NonZeroU32,
793 ) -> CandlesPages<'a> {
794 CandlesPages {
795 client: self,
796 engine,
797 market,
798 board,
799 security,
800 query,
801 pagination: PaginationTracker::new(
802 candles_endpoint(engine, market, board, security),
803 page_limit,
804 RepeatPagePolicy::Error,
805 ),
806 }
807 }
808
809 pub fn trades_query(
811 &self,
812 engine: &EngineName,
813 market: &MarketName,
814 board: &BoardId,
815 security: &SecId,
816 page_request: PageRequest,
817 ) -> Result<Vec<Trade>, MoexError> {
818 match page_request {
819 PageRequest::FirstPage => {
820 self.fetch_trades_page(engine, market, board, security, Pagination::default())
821 }
822 PageRequest::Page(pagination) => {
823 self.fetch_trades_page(engine, market, board, security, pagination)
824 }
825 PageRequest::All { page_limit } => self
826 .trades_pages(engine, market, board, security, page_limit)
827 .all(),
828 }
829 }
830
831 pub fn trades_pages<'a>(
833 &'a self,
834 engine: &'a EngineName,
835 market: &'a MarketName,
836 board: &'a BoardId,
837 security: &'a SecId,
838 page_limit: NonZeroU32,
839 ) -> TradesPages<'a> {
840 TradesPages {
841 client: self,
842 engine,
843 market,
844 board,
845 security,
846 pagination: PaginationTracker::new(
847 trades_endpoint(engine, market, board, security),
848 page_limit,
849 RepeatPagePolicy::Error,
850 ),
851 }
852 }
853
854 fn fetch_securities_page(
855 &self,
856 engine: &EngineName,
857 market: &MarketName,
858 board: &BoardId,
859 pagination: Pagination,
860 ) -> Result<Vec<Security>, MoexError> {
861 let endpoint = securities_endpoint(engine, market, board);
862 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
863 {
864 let mut query = endpoint_url.query_pairs_mut();
865 query
866 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
867 .append_pair(ISS_ONLY_PARAM, "securities")
868 .append_pair(SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS);
869 }
870 append_pagination_to_url(&mut endpoint_url, pagination);
871
872 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
873 decode_securities_json_with_endpoint(&payload, endpoint.as_str())
874 }
875
876 fn fetch_global_securities_page(
877 &self,
878 pagination: Pagination,
879 ) -> Result<Vec<Security>, MoexError> {
880 let endpoint = GLOBAL_SECURITIES_ENDPOINT;
881 let mut endpoint_url = self.endpoint_url(endpoint)?;
882 {
883 let mut query = endpoint_url.query_pairs_mut();
884 query
885 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
886 .append_pair(ISS_ONLY_PARAM, "securities")
887 .append_pair(SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS);
888 }
889 append_pagination_to_url(&mut endpoint_url, pagination);
890
891 let payload = self.fetch_payload(endpoint, endpoint_url)?;
892 decode_securities_json_with_endpoint(&payload, endpoint)
893 }
894
895 #[cfg(feature = "news")]
896 fn fetch_sitenews_page(&self, pagination: Pagination) -> Result<Vec<SiteNews>, MoexError> {
897 let endpoint = SITENEWS_ENDPOINT;
898 let mut endpoint_url = self.endpoint_url(endpoint)?;
899 {
900 let mut query = endpoint_url.query_pairs_mut();
901 query
902 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
903 .append_pair(ISS_ONLY_PARAM, "sitenews")
904 .append_pair(SITENEWS_COLUMNS_PARAM, SITENEWS_COLUMNS);
905 }
906 append_pagination_to_url(&mut endpoint_url, pagination);
907
908 let payload = self.fetch_payload(endpoint, endpoint_url)?;
909 decode_sitenews_json_with_endpoint(&payload, endpoint)
910 }
911
912 #[cfg(feature = "news")]
913 fn fetch_events_page(&self, pagination: Pagination) -> Result<Vec<Event>, MoexError> {
914 let endpoint = EVENTS_ENDPOINT;
915 let mut endpoint_url = self.endpoint_url(endpoint)?;
916 {
917 let mut query = endpoint_url.query_pairs_mut();
918 query
919 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
920 .append_pair(ISS_ONLY_PARAM, "events")
921 .append_pair(EVENTS_COLUMNS_PARAM, EVENTS_COLUMNS);
922 }
923 append_pagination_to_url(&mut endpoint_url, pagination);
924
925 let payload = self.fetch_payload(endpoint, endpoint_url)?;
926 decode_events_json_with_endpoint(&payload, endpoint)
927 }
928
929 fn fetch_market_securities_page(
930 &self,
931 engine: &EngineName,
932 market: &MarketName,
933 pagination: Pagination,
934 ) -> Result<Vec<Security>, MoexError> {
935 let endpoint = market_securities_endpoint(engine, market);
936 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
937 {
938 let mut query = endpoint_url.query_pairs_mut();
939 query
940 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
941 .append_pair(ISS_ONLY_PARAM, "securities")
942 .append_pair(SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS);
943 }
944 append_pagination_to_url(&mut endpoint_url, pagination);
945
946 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
947 decode_securities_json_with_endpoint(&payload, endpoint.as_str())
948 }
949
950 fn fetch_market_trades_page(
951 &self,
952 engine: &EngineName,
953 market: &MarketName,
954 pagination: Pagination,
955 ) -> Result<Vec<Trade>, MoexError> {
956 let endpoint = market_trades_endpoint(engine, market);
957 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
958 {
959 let mut query = endpoint_url.query_pairs_mut();
960 query
961 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
962 .append_pair(ISS_ONLY_PARAM, "trades")
963 .append_pair(TRADES_COLUMNS_PARAM, TRADES_COLUMNS);
964 }
965 append_pagination_to_url(&mut endpoint_url, pagination);
966
967 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
968 decode_trades_json_with_endpoint(&payload, endpoint.as_str())
969 }
970
971 fn fetch_secstats_page(
972 &self,
973 engine: &EngineName,
974 market: &MarketName,
975 pagination: Pagination,
976 ) -> Result<Vec<SecStat>, MoexError> {
977 let endpoint = secstats_endpoint(engine, market);
978 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
979 {
980 let mut query = endpoint_url.query_pairs_mut();
981 query
982 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
983 .append_pair(ISS_ONLY_PARAM, "secstats")
984 .append_pair(SECSTATS_COLUMNS_PARAM, SECSTATS_COLUMNS);
985 }
986 append_pagination_to_url(&mut endpoint_url, pagination);
987
988 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
989 decode_secstats_json_with_endpoint(&payload, endpoint.as_str())
990 }
991
992 fn fetch_index_analytics_page(
993 &self,
994 indexid: &IndexId,
995 pagination: Pagination,
996 ) -> Result<Vec<IndexAnalytics>, MoexError> {
997 let endpoint = index_analytics_endpoint(indexid);
998 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
999 {
1000 let mut query = endpoint_url.query_pairs_mut();
1001 query
1002 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
1003 .append_pair(ISS_ONLY_PARAM, "analytics")
1004 .append_pair(ANALYTICS_COLUMNS_PARAM, ANALYTICS_COLUMNS);
1005 }
1006 append_pagination_to_url(&mut endpoint_url, pagination);
1007
1008 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
1009 decode_index_analytics_json_with_endpoint(&payload, endpoint.as_str())
1010 }
1011
1012 fn fetch_candles_page(
1013 &self,
1014 engine: &EngineName,
1015 market: &MarketName,
1016 board: &BoardId,
1017 security: &SecId,
1018 query: CandleQuery,
1019 pagination: Pagination,
1020 ) -> Result<Vec<Candle>, MoexError> {
1021 let endpoint = candles_endpoint(engine, market, board, security);
1022 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
1023 {
1024 let mut query_pairs = endpoint_url.query_pairs_mut();
1025 query_pairs
1026 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
1027 .append_pair(ISS_ONLY_PARAM, "candles")
1028 .append_pair(CANDLES_COLUMNS_PARAM, CANDLES_COLUMNS);
1029 }
1030 append_candle_query_to_url(&mut endpoint_url, query);
1031 append_pagination_to_url(&mut endpoint_url, pagination);
1032
1033 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
1034 decode_candles_json_with_endpoint(&payload, endpoint.as_str())
1035 }
1036
1037 fn fetch_trades_page(
1038 &self,
1039 engine: &EngineName,
1040 market: &MarketName,
1041 board: &BoardId,
1042 security: &SecId,
1043 pagination: Pagination,
1044 ) -> Result<Vec<Trade>, MoexError> {
1045 let endpoint = trades_endpoint(engine, market, board, security);
1046 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
1047 {
1048 let mut query = endpoint_url.query_pairs_mut();
1049 query
1050 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
1051 .append_pair(ISS_ONLY_PARAM, "trades")
1052 .append_pair(TRADES_COLUMNS_PARAM, TRADES_COLUMNS);
1053 }
1054 append_pagination_to_url(&mut endpoint_url, pagination);
1055
1056 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
1057 decode_trades_json_with_endpoint(&payload, endpoint.as_str())
1058 }
1059
1060 #[cfg(feature = "history")]
1061 fn fetch_history_page(
1062 &self,
1063 engine: &EngineName,
1064 market: &MarketName,
1065 board: &BoardId,
1066 security: &SecId,
1067 pagination: Pagination,
1068 ) -> Result<Vec<HistoryRecord>, MoexError> {
1069 let endpoint = history_endpoint(engine, market, board, security);
1070 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
1071 {
1072 let mut query = endpoint_url.query_pairs_mut();
1073 query
1074 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
1075 .append_pair(ISS_ONLY_PARAM, "history")
1076 .append_pair(HISTORY_COLUMNS_PARAM, HISTORY_COLUMNS);
1077 }
1078 append_pagination_to_url(&mut endpoint_url, pagination);
1079
1080 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url)?;
1081 decode_history_json_with_endpoint(&payload, endpoint.as_str())
1082 }
1083
1084 #[cfg(test)]
1085 pub(super) fn collect_paginated<T, K, F, G>(
1086 endpoint: &str,
1087 page_limit: NonZeroU32,
1088 repeat_page_policy: RepeatPagePolicy,
1089 mut fetch_page: F,
1090 first_key_of: G,
1091 ) -> Result<Vec<T>, MoexError>
1092 where
1093 F: FnMut(Pagination) -> Result<Vec<T>, MoexError>,
1094 G: Fn(&T) -> K,
1095 K: Eq,
1096 {
1097 let mut pagination = PaginationTracker::new(endpoint, page_limit, repeat_page_policy);
1098 let mut items = Vec::new();
1099
1100 while let Some(paging) = pagination.next_page_request() {
1101 let page = fetch_page(paging)?;
1102 let first_key_on_page = page.first().map(&first_key_of);
1103 match pagination.advance(page.len(), first_key_on_page)? {
1104 PaginationAdvance::YieldPage => items.extend(page),
1105 PaginationAdvance::EndOfPages => break,
1106 }
1107 }
1108
1109 Ok(items)
1110 }
1111
1112 fn endpoint_url(&self, endpoint: &str) -> Result<Url, MoexError> {
1113 self.base_url
1114 .join(endpoint)
1115 .map_err(|source| MoexError::EndpointUrl {
1116 endpoint: endpoint.to_owned().into_boxed_str(),
1117 reason: source.to_string(),
1118 })
1119 }
1120
1121 fn get_payload(
1122 &self,
1123 endpoint: &str,
1124 query_params: &[(&'static str, &'static str)],
1125 ) -> Result<String, MoexError> {
1126 let mut endpoint_url = self.endpoint_url(endpoint)?;
1127 {
1128 let mut url_query = endpoint_url.query_pairs_mut();
1129 for (key, value) in query_params {
1130 url_query.append_pair(key, value);
1131 }
1132 }
1133 self.fetch_payload(endpoint, endpoint_url)
1134 }
1135
1136 fn fetch_payload(&self, endpoint: &str, endpoint_url: Url) -> Result<String, MoexError> {
1137 self.wait_for_rate_limit();
1138 let response =
1139 self.client
1140 .get(endpoint_url)
1141 .send()
1142 .map_err(|source| MoexError::Request {
1143 endpoint: endpoint.to_owned().into_boxed_str(),
1144 source,
1145 })?;
1146 let status = response.status();
1147
1148 let content_type = response
1149 .headers()
1150 .get(reqwest::header::CONTENT_TYPE)
1151 .and_then(|value| value.to_str().ok())
1152 .map(|value| value.to_owned().into_boxed_str());
1153
1154 let payload = response.text().map_err(|source| MoexError::ReadBody {
1155 endpoint: endpoint.to_owned().into_boxed_str(),
1156 source,
1157 })?;
1158
1159 if !status.is_success() {
1160 return Err(MoexError::HttpStatus {
1161 endpoint: endpoint.to_owned().into_boxed_str(),
1162 status,
1163 content_type,
1164 body_prefix: truncate_prefix(&payload, NON_JSON_BODY_PREFIX_CHARS),
1165 });
1166 }
1167
1168 if !looks_like_json_payload(content_type.as_deref(), &payload) {
1169 return Err(MoexError::NonJsonPayload {
1170 endpoint: endpoint.to_owned().into_boxed_str(),
1171 content_type,
1172 body_prefix: truncate_prefix(&payload, NON_JSON_BODY_PREFIX_CHARS),
1173 });
1174 }
1175
1176 Ok(payload)
1177 }
1178
1179 fn wait_for_rate_limit(&self) {
1180 let Some(limiter) = &self.rate_limiter else {
1181 return;
1182 };
1183 let delay = reserve_rate_limit_delay(limiter);
1184 if !delay.is_zero() {
1185 std::thread::sleep(delay);
1186 }
1187 }
1188}
1189
1190#[cfg(feature = "blocking")]
1192pub struct BlockingMoexClientBuilder {
1193 base_url: Option<Url>,
1194 metadata: bool,
1195 client: Option<Client>,
1196 http_client: ClientBuilder,
1197 rate_limit: Option<RateLimit>,
1198}
1199
1200#[cfg(feature = "blocking")]
1201impl BlockingMoexClientBuilder {
1202 pub fn metadata(mut self, metadata: bool) -> Self {
1204 self.metadata = metadata;
1205 self
1206 }
1207
1208 pub fn base_url(mut self, base_url: Url) -> Self {
1210 self.base_url = Some(base_url);
1211 self
1212 }
1213
1214 pub fn client(mut self, client: Client) -> Self {
1216 self.client = Some(client);
1217 self
1218 }
1219
1220 pub fn timeout(mut self, timeout: Duration) -> Self {
1222 self.http_client = self.http_client.timeout(timeout);
1223 self
1224 }
1225
1226 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
1228 self.http_client = self.http_client.connect_timeout(timeout);
1229 self
1230 }
1231
1232 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
1234 self.http_client = self.http_client.user_agent(user_agent.into());
1235 self
1236 }
1237
1238 pub fn user_agent_from_crate(self) -> Self {
1240 self.user_agent(format!(
1241 "{}/{}",
1242 env!("CARGO_PKG_NAME"),
1243 env!("CARGO_PKG_VERSION")
1244 ))
1245 }
1246
1247 pub fn default_headers(mut self, headers: HeaderMap) -> Self {
1249 self.http_client = self.http_client.default_headers(headers);
1250 self
1251 }
1252
1253 pub fn proxy(mut self, proxy: reqwest::Proxy) -> Self {
1257 self.http_client = self.http_client.proxy(proxy);
1258 self
1259 }
1260
1261 pub fn no_proxy(mut self) -> Self {
1263 self.http_client = self.http_client.no_proxy();
1264 self
1265 }
1266
1267 pub fn rate_limit(mut self, rate_limit: RateLimit) -> Self {
1271 self.rate_limit = Some(rate_limit);
1272 self
1273 }
1274
1275 pub fn build(self) -> Result<BlockingMoexClient, MoexError> {
1277 let Self {
1278 base_url,
1279 metadata,
1280 client,
1281 http_client,
1282 rate_limit,
1283 } = self;
1284 let base_url = resolve_base_url_or_default(base_url)?;
1285 let client = resolve_blocking_http_client(client, http_client)?;
1286 Ok(BlockingMoexClient::with_base_url_and_rate_limit(
1287 client, base_url, metadata, rate_limit,
1288 ))
1289 }
1290}
1291
1292#[cfg(feature = "async")]
1297pub struct AsyncMoexClient {
1298 base_url: Url,
1299 metadata: bool,
1300 client: reqwest::Client,
1301 rate_limit: Option<AsyncRateLimitState>,
1302}
1303
1304#[cfg(feature = "async")]
1305impl AsyncMoexClient {
1306 pub fn builder() -> AsyncMoexClientBuilder {
1308 AsyncMoexClientBuilder {
1309 base_url: None,
1310 metadata: false,
1311 client: None,
1312 http_client: reqwest::Client::builder(),
1313 rate_limit: None,
1314 rate_limit_sleep: None,
1315 }
1316 }
1317
1318 pub fn new() -> Result<Self, MoexError> {
1320 Self::builder().build()
1321 }
1322
1323 pub fn new_with_metadata() -> Result<Self, MoexError> {
1325 Self::builder().metadata(true).build()
1326 }
1327
1328 pub fn with_client(client: reqwest::Client) -> Result<Self, MoexError> {
1330 Self::builder().client(client).build()
1331 }
1332
1333 pub fn with_client_with_metadata(client: reqwest::Client) -> Result<Self, MoexError> {
1335 Self::builder().metadata(true).client(client).build()
1336 }
1337
1338 pub fn with_base_url(client: reqwest::Client, base_url: Url) -> Self {
1340 Self::with_base_url_and_rate_limit(client, base_url, false, None)
1341 }
1342
1343 pub fn with_base_url_with_metadata(client: reqwest::Client, base_url: Url) -> Self {
1345 Self::with_base_url_and_rate_limit(client, base_url, true, None)
1346 }
1347
1348 pub fn rate_limit(&self) -> Option<RateLimit> {
1350 self.rate_limit
1351 .as_ref()
1352 .map(|rate_limit| lock_rate_limiter(&rate_limit.limiter).limit())
1353 }
1354
1355 fn with_base_url_and_rate_limit(
1356 client: reqwest::Client,
1357 base_url: Url,
1358 metadata: bool,
1359 rate_limit: Option<AsyncRateLimitState>,
1360 ) -> Self {
1361 Self {
1362 base_url,
1363 metadata,
1364 client,
1365 rate_limit,
1366 }
1367 }
1368
1369 pub fn raw(&self) -> AsyncRawIssRequestBuilder<'_> {
1371 AsyncRawIssRequestBuilder {
1372 client: self,
1373 path: None,
1374 query: Vec::new(),
1375 }
1376 }
1377
1378 pub fn raw_endpoint(&self, endpoint: IssEndpoint<'_>) -> AsyncRawIssRequestBuilder<'_> {
1382 let path = endpoint.path();
1383 let request = self.raw().path(path);
1384 match endpoint.default_table() {
1385 Some(table) => request.only(table),
1386 None => request,
1387 }
1388 }
1389
1390 pub async fn indexes(&self) -> Result<Vec<Index>, MoexError> {
1392 let payload = self
1393 .get_payload(
1394 INDEXES_ENDPOINT,
1395 &[
1396 (ISS_META_PARAM, metadata_value(self.metadata)),
1397 (ISS_ONLY_PARAM, "indices"),
1398 (INDICES_COLUMNS_PARAM, INDICES_COLUMNS),
1399 ],
1400 )
1401 .await?;
1402 decode_indexes_json_payload(&payload)
1403 }
1404
1405 pub async fn index_analytics_query(
1407 &self,
1408 indexid: &IndexId,
1409 page_request: PageRequest,
1410 ) -> Result<Vec<IndexAnalytics>, MoexError> {
1411 match page_request {
1412 PageRequest::FirstPage => {
1413 self.fetch_index_analytics_page(indexid, Pagination::default())
1414 .await
1415 }
1416 PageRequest::Page(pagination) => {
1417 self.fetch_index_analytics_page(indexid, pagination).await
1418 }
1419 PageRequest::All { page_limit } => {
1420 self.index_analytics_pages(indexid, page_limit).all().await
1421 }
1422 }
1423 }
1424
1425 pub fn index_analytics_pages<'a>(
1427 &'a self,
1428 indexid: &'a IndexId,
1429 page_limit: NonZeroU32,
1430 ) -> AsyncIndexAnalyticsPages<'a> {
1431 AsyncIndexAnalyticsPages {
1432 client: self,
1433 indexid,
1434 pagination: PaginationTracker::new(
1435 index_analytics_endpoint(indexid),
1436 page_limit,
1437 RepeatPagePolicy::Error,
1438 ),
1439 }
1440 }
1441
1442 pub async fn turnovers(&self) -> Result<Vec<Turnover>, MoexError> {
1444 let payload = self
1445 .get_payload(
1446 TURNOVERS_ENDPOINT,
1447 &[
1448 (ISS_META_PARAM, metadata_value(self.metadata)),
1449 (ISS_ONLY_PARAM, "turnovers"),
1450 (TURNOVERS_COLUMNS_PARAM, TURNOVERS_COLUMNS),
1451 ],
1452 )
1453 .await?;
1454 decode_turnovers_json_with_endpoint(&payload, TURNOVERS_ENDPOINT)
1455 }
1456
1457 pub async fn engine_turnovers(&self, engine: &EngineName) -> Result<Vec<Turnover>, MoexError> {
1459 let endpoint = engine_turnovers_endpoint(engine);
1460 let payload = self
1461 .get_payload(
1462 endpoint.as_str(),
1463 &[
1464 (ISS_META_PARAM, metadata_value(self.metadata)),
1465 (ISS_ONLY_PARAM, "turnovers"),
1466 (TURNOVERS_COLUMNS_PARAM, TURNOVERS_COLUMNS),
1467 ],
1468 )
1469 .await?;
1470 decode_turnovers_json_with_endpoint(&payload, endpoint.as_str())
1471 }
1472
1473 #[cfg(feature = "news")]
1474 pub async fn sitenews_query(
1476 &self,
1477 page_request: PageRequest,
1478 ) -> Result<Vec<SiteNews>, MoexError> {
1479 match page_request {
1480 PageRequest::FirstPage => self.fetch_sitenews_page(Pagination::default()).await,
1481 PageRequest::Page(pagination) => self.fetch_sitenews_page(pagination).await,
1482 PageRequest::All { page_limit } => self.sitenews_pages(page_limit).all().await,
1483 }
1484 }
1485
1486 #[cfg(feature = "news")]
1487 pub fn sitenews_pages<'a>(&'a self, page_limit: NonZeroU32) -> AsyncSiteNewsPages<'a> {
1489 AsyncSiteNewsPages {
1490 client: self,
1491 pagination: PaginationTracker::new(
1492 SITENEWS_ENDPOINT,
1493 page_limit,
1494 RepeatPagePolicy::Error,
1495 ),
1496 }
1497 }
1498
1499 #[cfg(feature = "news")]
1500 pub async fn events_query(&self, page_request: PageRequest) -> Result<Vec<Event>, MoexError> {
1502 match page_request {
1503 PageRequest::FirstPage => self.fetch_events_page(Pagination::default()).await,
1504 PageRequest::Page(pagination) => self.fetch_events_page(pagination).await,
1505 PageRequest::All { page_limit } => self.events_pages(page_limit).all().await,
1506 }
1507 }
1508
1509 #[cfg(feature = "news")]
1510 pub fn events_pages<'a>(&'a self, page_limit: NonZeroU32) -> AsyncEventsPages<'a> {
1512 AsyncEventsPages {
1513 client: self,
1514 pagination: PaginationTracker::new(
1515 EVENTS_ENDPOINT,
1516 page_limit,
1517 RepeatPagePolicy::Error,
1518 ),
1519 }
1520 }
1521
1522 pub async fn secstats_query(
1524 &self,
1525 engine: &EngineName,
1526 market: &MarketName,
1527 page_request: PageRequest,
1528 ) -> Result<Vec<SecStat>, MoexError> {
1529 match page_request {
1530 PageRequest::FirstPage => {
1531 self.fetch_secstats_page(engine, market, Pagination::default())
1532 .await
1533 }
1534 PageRequest::Page(pagination) => {
1535 self.fetch_secstats_page(engine, market, pagination).await
1536 }
1537 PageRequest::All { page_limit } => {
1538 self.secstats_pages(engine, market, page_limit).all().await
1539 }
1540 }
1541 }
1542
1543 pub fn secstats_pages<'a>(
1545 &'a self,
1546 engine: &'a EngineName,
1547 market: &'a MarketName,
1548 page_limit: NonZeroU32,
1549 ) -> AsyncSecStatsPages<'a> {
1550 AsyncSecStatsPages {
1551 client: self,
1552 engine,
1553 market,
1554 pagination: PaginationTracker::new(
1555 secstats_endpoint(engine, market),
1556 page_limit,
1557 RepeatPagePolicy::Error,
1558 ),
1559 }
1560 }
1561
1562 pub async fn engines(&self) -> Result<Vec<Engine>, MoexError> {
1564 let payload = self
1565 .get_payload(
1566 ENGINES_ENDPOINT,
1567 &[
1568 (ISS_META_PARAM, metadata_value(self.metadata)),
1569 (ISS_ONLY_PARAM, "engines"),
1570 (ENGINES_COLUMNS_PARAM, ENGINES_COLUMNS),
1571 ],
1572 )
1573 .await?;
1574 decode_engines_json_payload(&payload)
1575 }
1576
1577 pub async fn markets(&self, engine: &EngineName) -> Result<Vec<Market>, MoexError> {
1579 let endpoint = markets_endpoint(engine);
1580 let payload = self
1581 .get_payload(
1582 endpoint.as_str(),
1583 &[
1584 (ISS_META_PARAM, metadata_value(self.metadata)),
1585 (ISS_ONLY_PARAM, "markets"),
1586 (MARKETS_COLUMNS_PARAM, MARKETS_COLUMNS),
1587 ],
1588 )
1589 .await?;
1590 decode_markets_json_with_endpoint(&payload, endpoint.as_str())
1591 }
1592
1593 pub async fn boards(
1595 &self,
1596 engine: &EngineName,
1597 market: &MarketName,
1598 ) -> Result<Vec<Board>, MoexError> {
1599 let endpoint = boards_endpoint(engine, market);
1600 let payload = self
1601 .get_payload(
1602 endpoint.as_str(),
1603 &[
1604 (ISS_META_PARAM, metadata_value(self.metadata)),
1605 (ISS_ONLY_PARAM, "boards"),
1606 (BOARDS_COLUMNS_PARAM, BOARDS_COLUMNS),
1607 ],
1608 )
1609 .await?;
1610 decode_boards_json_with_endpoint(&payload, endpoint.as_str())
1611 }
1612
1613 pub async fn security_boards(&self, security: &SecId) -> Result<Vec<SecurityBoard>, MoexError> {
1615 let endpoint = security_boards_endpoint(security);
1616 let payload = self
1617 .get_payload(
1618 endpoint.as_str(),
1619 &[
1620 (ISS_META_PARAM, metadata_value(self.metadata)),
1621 (ISS_ONLY_PARAM, "boards"),
1622 (BOARDS_COLUMNS_PARAM, SECURITY_BOARDS_COLUMNS),
1623 ],
1624 )
1625 .await?;
1626 decode_security_boards_json_with_endpoint(&payload, endpoint.as_str())
1627 }
1628
1629 pub async fn security_info(&self, security: &SecId) -> Result<Option<Security>, MoexError> {
1633 let endpoint = security_endpoint(security);
1634 let payload = self
1635 .get_payload(
1636 endpoint.as_str(),
1637 &[
1638 (ISS_META_PARAM, metadata_value(self.metadata)),
1639 (ISS_ONLY_PARAM, "securities"),
1640 (SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS),
1641 ],
1642 )
1643 .await?;
1644 let securities = decode_securities_json_with_endpoint(&payload, endpoint.as_str())?;
1645 optional_single_security(endpoint.as_str(), securities)
1646 }
1647
1648 #[cfg(feature = "history")]
1649 pub async fn history_dates(
1653 &self,
1654 engine: &EngineName,
1655 market: &MarketName,
1656 board: &BoardId,
1657 security: &SecId,
1658 ) -> Result<Option<HistoryDates>, MoexError> {
1659 let endpoint = history_dates_endpoint(engine, market, board, security);
1660 let payload = self
1661 .get_payload(
1662 endpoint.as_str(),
1663 &[
1664 (ISS_META_PARAM, metadata_value(self.metadata)),
1665 (ISS_ONLY_PARAM, "dates"),
1666 ],
1667 )
1668 .await?;
1669 let dates = decode_history_dates_json_with_endpoint(&payload, endpoint.as_str())?;
1670 optional_single_history_dates(endpoint.as_str(), dates)
1671 }
1672
1673 #[cfg(feature = "history")]
1674 pub async fn history_query(
1676 &self,
1677 engine: &EngineName,
1678 market: &MarketName,
1679 board: &BoardId,
1680 security: &SecId,
1681 page_request: PageRequest,
1682 ) -> Result<Vec<HistoryRecord>, MoexError> {
1683 match page_request {
1684 PageRequest::FirstPage => {
1685 self.fetch_history_page(engine, market, board, security, Pagination::default())
1686 .await
1687 }
1688 PageRequest::Page(pagination) => {
1689 self.fetch_history_page(engine, market, board, security, pagination)
1690 .await
1691 }
1692 PageRequest::All { page_limit } => {
1693 self.history_pages(engine, market, board, security, page_limit)
1694 .all()
1695 .await
1696 }
1697 }
1698 }
1699
1700 #[cfg(feature = "history")]
1701 pub fn history_pages<'a>(
1703 &'a self,
1704 engine: &'a EngineName,
1705 market: &'a MarketName,
1706 board: &'a BoardId,
1707 security: &'a SecId,
1708 page_limit: NonZeroU32,
1709 ) -> AsyncHistoryPages<'a> {
1710 AsyncHistoryPages {
1711 client: self,
1712 engine,
1713 market,
1714 board,
1715 security,
1716 pagination: PaginationTracker::new(
1717 history_endpoint(engine, market, board, security),
1718 page_limit,
1719 RepeatPagePolicy::Error,
1720 ),
1721 }
1722 }
1723
1724 pub async fn board_snapshots(
1726 &self,
1727 engine: &EngineName,
1728 market: &MarketName,
1729 board: &BoardId,
1730 ) -> Result<Vec<SecuritySnapshot>, MoexError> {
1731 let endpoint = securities_endpoint(engine, market, board);
1732 let payload = self
1733 .get_payload(
1734 endpoint.as_str(),
1735 &[
1736 (ISS_META_PARAM, metadata_value(self.metadata)),
1737 (ISS_ONLY_PARAM, "securities,marketdata"),
1738 (SECURITIES_COLUMNS_PARAM, SECURITIES_SNAPSHOT_COLUMNS),
1739 (MARKETDATA_COLUMNS_PARAM, MARKETDATA_LAST_COLUMNS),
1740 ],
1741 )
1742 .await?;
1743 decode_board_security_snapshots_json_with_endpoint(&payload, endpoint.as_str())
1744 }
1745
1746 pub async fn board_security_snapshots(
1748 &self,
1749 board: &SecurityBoard,
1750 ) -> Result<Vec<SecuritySnapshot>, MoexError> {
1751 self.board_snapshots(board.engine(), board.market(), board.boardid())
1752 .await
1753 }
1754
1755 pub async fn global_securities_query(
1757 &self,
1758 page_request: PageRequest,
1759 ) -> Result<Vec<Security>, MoexError> {
1760 match page_request {
1761 PageRequest::FirstPage => {
1762 self.fetch_global_securities_page(Pagination::default())
1763 .await
1764 }
1765 PageRequest::Page(pagination) => self.fetch_global_securities_page(pagination).await,
1766 PageRequest::All { page_limit } => self.global_securities_pages(page_limit).all().await,
1767 }
1768 }
1769
1770 pub fn global_securities_pages<'a>(
1772 &'a self,
1773 page_limit: NonZeroU32,
1774 ) -> AsyncGlobalSecuritiesPages<'a> {
1775 AsyncGlobalSecuritiesPages {
1776 client: self,
1777 pagination: PaginationTracker::new(
1778 GLOBAL_SECURITIES_ENDPOINT,
1779 page_limit,
1780 RepeatPagePolicy::Error,
1781 ),
1782 }
1783 }
1784
1785 pub async fn market_security_info(
1789 &self,
1790 engine: &EngineName,
1791 market: &MarketName,
1792 security: &SecId,
1793 ) -> Result<Option<Security>, MoexError> {
1794 let endpoint = market_security_endpoint(engine, market, security);
1795 let payload = self
1796 .get_payload(
1797 endpoint.as_str(),
1798 &[
1799 (ISS_META_PARAM, metadata_value(self.metadata)),
1800 (ISS_ONLY_PARAM, "securities"),
1801 (SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS),
1802 ],
1803 )
1804 .await?;
1805 let securities = decode_securities_json_with_endpoint(&payload, endpoint.as_str())?;
1806 optional_single_security(endpoint.as_str(), securities)
1807 }
1808
1809 pub async fn market_securities_query(
1811 &self,
1812 engine: &EngineName,
1813 market: &MarketName,
1814 page_request: PageRequest,
1815 ) -> Result<Vec<Security>, MoexError> {
1816 match page_request {
1817 PageRequest::FirstPage => {
1818 self.fetch_market_securities_page(engine, market, Pagination::default())
1819 .await
1820 }
1821 PageRequest::Page(pagination) => {
1822 self.fetch_market_securities_page(engine, market, pagination)
1823 .await
1824 }
1825 PageRequest::All { page_limit } => {
1826 self.market_securities_pages(engine, market, page_limit)
1827 .all()
1828 .await
1829 }
1830 }
1831 }
1832
1833 pub fn market_securities_pages<'a>(
1835 &'a self,
1836 engine: &'a EngineName,
1837 market: &'a MarketName,
1838 page_limit: NonZeroU32,
1839 ) -> AsyncMarketSecuritiesPages<'a> {
1840 AsyncMarketSecuritiesPages {
1841 client: self,
1842 engine,
1843 market,
1844 pagination: PaginationTracker::new(
1845 market_securities_endpoint(engine, market),
1846 page_limit,
1847 RepeatPagePolicy::Error,
1848 ),
1849 }
1850 }
1851
1852 pub async fn market_orderbook(
1854 &self,
1855 engine: &EngineName,
1856 market: &MarketName,
1857 ) -> Result<Vec<OrderbookLevel>, MoexError> {
1858 let endpoint = market_orderbook_endpoint(engine, market);
1859 let payload = self
1860 .get_payload(
1861 endpoint.as_str(),
1862 &[
1863 (ISS_META_PARAM, metadata_value(self.metadata)),
1864 (ISS_ONLY_PARAM, "orderbook"),
1865 (ORDERBOOK_COLUMNS_PARAM, ORDERBOOK_COLUMNS),
1866 ],
1867 )
1868 .await?;
1869 decode_orderbook_json_with_endpoint(&payload, endpoint.as_str())
1870 }
1871
1872 pub async fn candle_borders(
1874 &self,
1875 engine: &EngineName,
1876 market: &MarketName,
1877 security: &SecId,
1878 ) -> Result<Vec<CandleBorder>, MoexError> {
1879 let endpoint = candleborders_endpoint(engine, market, security);
1880 let payload = self
1881 .get_payload(
1882 endpoint.as_str(),
1883 &[(ISS_META_PARAM, metadata_value(self.metadata))],
1884 )
1885 .await?;
1886 decode_candle_borders_json_with_endpoint(&payload, endpoint.as_str())
1887 }
1888
1889 pub async fn market_trades_query(
1891 &self,
1892 engine: &EngineName,
1893 market: &MarketName,
1894 page_request: PageRequest,
1895 ) -> Result<Vec<Trade>, MoexError> {
1896 match page_request {
1897 PageRequest::FirstPage => {
1898 self.fetch_market_trades_page(engine, market, Pagination::default())
1899 .await
1900 }
1901 PageRequest::Page(pagination) => {
1902 self.fetch_market_trades_page(engine, market, pagination)
1903 .await
1904 }
1905 PageRequest::All { page_limit } => {
1906 self.market_trades_pages(engine, market, page_limit)
1907 .all()
1908 .await
1909 }
1910 }
1911 }
1912
1913 pub fn market_trades_pages<'a>(
1915 &'a self,
1916 engine: &'a EngineName,
1917 market: &'a MarketName,
1918 page_limit: NonZeroU32,
1919 ) -> AsyncMarketTradesPages<'a> {
1920 AsyncMarketTradesPages {
1921 client: self,
1922 engine,
1923 market,
1924 pagination: PaginationTracker::new(
1925 market_trades_endpoint(engine, market),
1926 page_limit,
1927 RepeatPagePolicy::Error,
1928 ),
1929 }
1930 }
1931
1932 pub async fn securities_query(
1934 &self,
1935 engine: &EngineName,
1936 market: &MarketName,
1937 board: &BoardId,
1938 page_request: PageRequest,
1939 ) -> Result<Vec<Security>, MoexError> {
1940 match page_request {
1941 PageRequest::FirstPage => {
1942 self.fetch_securities_page(engine, market, board, Pagination::default())
1943 .await
1944 }
1945 PageRequest::Page(pagination) => {
1946 self.fetch_securities_page(engine, market, board, pagination)
1947 .await
1948 }
1949 PageRequest::All { page_limit } => {
1950 self.securities_pages(engine, market, board, page_limit)
1951 .all()
1952 .await
1953 }
1954 }
1955 }
1956
1957 pub fn securities_pages<'a>(
1959 &'a self,
1960 engine: &'a EngineName,
1961 market: &'a MarketName,
1962 board: &'a BoardId,
1963 page_limit: NonZeroU32,
1964 ) -> AsyncSecuritiesPages<'a> {
1965 AsyncSecuritiesPages {
1966 client: self,
1967 engine,
1968 market,
1969 board,
1970 pagination: PaginationTracker::new(
1971 securities_endpoint(engine, market, board),
1972 page_limit,
1973 RepeatPagePolicy::Error,
1974 ),
1975 }
1976 }
1977
1978 pub async fn orderbook(
1980 &self,
1981 engine: &EngineName,
1982 market: &MarketName,
1983 board: &BoardId,
1984 security: &SecId,
1985 ) -> Result<Vec<OrderbookLevel>, MoexError> {
1986 let endpoint = orderbook_endpoint(engine, market, board, security);
1987 let payload = self
1988 .get_payload(
1989 endpoint.as_str(),
1990 &[
1991 (ISS_META_PARAM, metadata_value(self.metadata)),
1992 (ISS_ONLY_PARAM, "orderbook"),
1993 (ORDERBOOK_COLUMNS_PARAM, ORDERBOOK_COLUMNS),
1994 ],
1995 )
1996 .await?;
1997 decode_orderbook_json_with_endpoint(&payload, endpoint.as_str())
1998 }
1999
2000 pub async fn candles_query(
2002 &self,
2003 engine: &EngineName,
2004 market: &MarketName,
2005 board: &BoardId,
2006 security: &SecId,
2007 query: CandleQuery,
2008 page_request: PageRequest,
2009 ) -> Result<Vec<Candle>, MoexError> {
2010 match page_request {
2011 PageRequest::FirstPage => {
2012 self.fetch_candles_page(
2013 engine,
2014 market,
2015 board,
2016 security,
2017 query,
2018 Pagination::default(),
2019 )
2020 .await
2021 }
2022 PageRequest::Page(pagination) => {
2023 self.fetch_candles_page(engine, market, board, security, query, pagination)
2024 .await
2025 }
2026 PageRequest::All { page_limit } => {
2027 self.candles_pages(engine, market, board, security, query, page_limit)
2028 .all()
2029 .await
2030 }
2031 }
2032 }
2033
2034 pub fn candles_pages<'a>(
2036 &'a self,
2037 engine: &'a EngineName,
2038 market: &'a MarketName,
2039 board: &'a BoardId,
2040 security: &'a SecId,
2041 query: CandleQuery,
2042 page_limit: NonZeroU32,
2043 ) -> AsyncCandlesPages<'a> {
2044 AsyncCandlesPages {
2045 client: self,
2046 engine,
2047 market,
2048 board,
2049 security,
2050 query,
2051 pagination: PaginationTracker::new(
2052 candles_endpoint(engine, market, board, security),
2053 page_limit,
2054 RepeatPagePolicy::Error,
2055 ),
2056 }
2057 }
2058
2059 pub async fn trades_query(
2061 &self,
2062 engine: &EngineName,
2063 market: &MarketName,
2064 board: &BoardId,
2065 security: &SecId,
2066 page_request: PageRequest,
2067 ) -> Result<Vec<Trade>, MoexError> {
2068 match page_request {
2069 PageRequest::FirstPage => {
2070 self.fetch_trades_page(engine, market, board, security, Pagination::default())
2071 .await
2072 }
2073 PageRequest::Page(pagination) => {
2074 self.fetch_trades_page(engine, market, board, security, pagination)
2075 .await
2076 }
2077 PageRequest::All { page_limit } => {
2078 self.trades_pages(engine, market, board, security, page_limit)
2079 .all()
2080 .await
2081 }
2082 }
2083 }
2084
2085 pub fn trades_pages<'a>(
2087 &'a self,
2088 engine: &'a EngineName,
2089 market: &'a MarketName,
2090 board: &'a BoardId,
2091 security: &'a SecId,
2092 page_limit: NonZeroU32,
2093 ) -> AsyncTradesPages<'a> {
2094 AsyncTradesPages {
2095 client: self,
2096 engine,
2097 market,
2098 board,
2099 security,
2100 pagination: PaginationTracker::new(
2101 trades_endpoint(engine, market, board, security),
2102 page_limit,
2103 RepeatPagePolicy::Error,
2104 ),
2105 }
2106 }
2107
2108 pub fn engine<E>(&self, engine: E) -> Result<AsyncOwnedEngineScope<'_>, ParseEngineNameError>
2110 where
2111 E: TryInto<EngineName>,
2112 E::Error: Into<ParseEngineNameError>,
2113 {
2114 let engine = engine.try_into().map_err(Into::into)?;
2115 Ok(AsyncOwnedEngineScope {
2116 client: self,
2117 engine,
2118 })
2119 }
2120
2121 pub fn stock(&self) -> Result<AsyncOwnedEngineScope<'_>, ParseEngineNameError> {
2123 self.engine("stock")
2124 }
2125
2126 pub fn index<I>(&self, indexid: I) -> Result<AsyncOwnedIndexScope<'_>, ParseIndexError>
2128 where
2129 I: TryInto<IndexId>,
2130 I::Error: Into<ParseIndexError>,
2131 {
2132 let indexid = indexid.try_into().map_err(Into::into)?;
2133 Ok(AsyncOwnedIndexScope {
2134 client: self,
2135 indexid,
2136 })
2137 }
2138
2139 pub fn security<S>(
2141 &self,
2142 security: S,
2143 ) -> Result<AsyncOwnedSecurityResourceScope<'_>, ParseSecIdError>
2144 where
2145 S: TryInto<SecId>,
2146 S::Error: Into<ParseSecIdError>,
2147 {
2148 let security = security.try_into().map_err(Into::into)?;
2149 Ok(AsyncOwnedSecurityResourceScope {
2150 client: self,
2151 security,
2152 })
2153 }
2154
2155 async fn fetch_index_analytics_page(
2156 &self,
2157 indexid: &IndexId,
2158 pagination: Pagination,
2159 ) -> Result<Vec<IndexAnalytics>, MoexError> {
2160 let endpoint = index_analytics_endpoint(indexid);
2161 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2162 {
2163 let mut query = endpoint_url.query_pairs_mut();
2164 query
2165 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2166 .append_pair(ISS_ONLY_PARAM, "analytics")
2167 .append_pair(ANALYTICS_COLUMNS_PARAM, ANALYTICS_COLUMNS);
2168 }
2169 append_pagination_to_url(&mut endpoint_url, pagination);
2170
2171 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2172 decode_index_analytics_json_with_endpoint(&payload, endpoint.as_str())
2173 }
2174
2175 async fn fetch_securities_page(
2176 &self,
2177 engine: &EngineName,
2178 market: &MarketName,
2179 board: &BoardId,
2180 pagination: Pagination,
2181 ) -> Result<Vec<Security>, MoexError> {
2182 let endpoint = securities_endpoint(engine, market, board);
2183 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2184 {
2185 let mut query = endpoint_url.query_pairs_mut();
2186 query
2187 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2188 .append_pair(ISS_ONLY_PARAM, "securities")
2189 .append_pair(SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS);
2190 }
2191 append_pagination_to_url(&mut endpoint_url, pagination);
2192
2193 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2194 decode_securities_json_with_endpoint(&payload, endpoint.as_str())
2195 }
2196
2197 async fn fetch_global_securities_page(
2198 &self,
2199 pagination: Pagination,
2200 ) -> Result<Vec<Security>, MoexError> {
2201 let endpoint = GLOBAL_SECURITIES_ENDPOINT;
2202 let mut endpoint_url = self.endpoint_url(endpoint)?;
2203 {
2204 let mut query = endpoint_url.query_pairs_mut();
2205 query
2206 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2207 .append_pair(ISS_ONLY_PARAM, "securities")
2208 .append_pair(SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS);
2209 }
2210 append_pagination_to_url(&mut endpoint_url, pagination);
2211
2212 let payload = self.fetch_payload(endpoint, endpoint_url).await?;
2213 decode_securities_json_with_endpoint(&payload, endpoint)
2214 }
2215
2216 #[cfg(feature = "news")]
2217 async fn fetch_sitenews_page(
2218 &self,
2219 pagination: Pagination,
2220 ) -> Result<Vec<SiteNews>, MoexError> {
2221 let endpoint = SITENEWS_ENDPOINT;
2222 let mut endpoint_url = self.endpoint_url(endpoint)?;
2223 {
2224 let mut query = endpoint_url.query_pairs_mut();
2225 query
2226 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2227 .append_pair(ISS_ONLY_PARAM, "sitenews")
2228 .append_pair(SITENEWS_COLUMNS_PARAM, SITENEWS_COLUMNS);
2229 }
2230 append_pagination_to_url(&mut endpoint_url, pagination);
2231
2232 let payload = self.fetch_payload(endpoint, endpoint_url).await?;
2233 decode_sitenews_json_with_endpoint(&payload, endpoint)
2234 }
2235
2236 #[cfg(feature = "news")]
2237 async fn fetch_events_page(&self, pagination: Pagination) -> Result<Vec<Event>, MoexError> {
2238 let endpoint = EVENTS_ENDPOINT;
2239 let mut endpoint_url = self.endpoint_url(endpoint)?;
2240 {
2241 let mut query = endpoint_url.query_pairs_mut();
2242 query
2243 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2244 .append_pair(ISS_ONLY_PARAM, "events")
2245 .append_pair(EVENTS_COLUMNS_PARAM, EVENTS_COLUMNS);
2246 }
2247 append_pagination_to_url(&mut endpoint_url, pagination);
2248
2249 let payload = self.fetch_payload(endpoint, endpoint_url).await?;
2250 decode_events_json_with_endpoint(&payload, endpoint)
2251 }
2252
2253 async fn fetch_market_securities_page(
2254 &self,
2255 engine: &EngineName,
2256 market: &MarketName,
2257 pagination: Pagination,
2258 ) -> Result<Vec<Security>, MoexError> {
2259 let endpoint = market_securities_endpoint(engine, market);
2260 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2261 {
2262 let mut query = endpoint_url.query_pairs_mut();
2263 query
2264 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2265 .append_pair(ISS_ONLY_PARAM, "securities")
2266 .append_pair(SECURITIES_COLUMNS_PARAM, SECURITIES_COLUMNS);
2267 }
2268 append_pagination_to_url(&mut endpoint_url, pagination);
2269
2270 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2271 decode_securities_json_with_endpoint(&payload, endpoint.as_str())
2272 }
2273
2274 async fn fetch_market_trades_page(
2275 &self,
2276 engine: &EngineName,
2277 market: &MarketName,
2278 pagination: Pagination,
2279 ) -> Result<Vec<Trade>, MoexError> {
2280 let endpoint = market_trades_endpoint(engine, market);
2281 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2282 {
2283 let mut query = endpoint_url.query_pairs_mut();
2284 query
2285 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2286 .append_pair(ISS_ONLY_PARAM, "trades")
2287 .append_pair(TRADES_COLUMNS_PARAM, TRADES_COLUMNS);
2288 }
2289 append_pagination_to_url(&mut endpoint_url, pagination);
2290
2291 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2292 decode_trades_json_with_endpoint(&payload, endpoint.as_str())
2293 }
2294
2295 async fn fetch_secstats_page(
2296 &self,
2297 engine: &EngineName,
2298 market: &MarketName,
2299 pagination: Pagination,
2300 ) -> Result<Vec<SecStat>, MoexError> {
2301 let endpoint = secstats_endpoint(engine, market);
2302 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2303 {
2304 let mut query = endpoint_url.query_pairs_mut();
2305 query
2306 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2307 .append_pair(ISS_ONLY_PARAM, "secstats")
2308 .append_pair(SECSTATS_COLUMNS_PARAM, SECSTATS_COLUMNS);
2309 }
2310 append_pagination_to_url(&mut endpoint_url, pagination);
2311
2312 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2313 decode_secstats_json_with_endpoint(&payload, endpoint.as_str())
2314 }
2315
2316 async fn fetch_candles_page(
2317 &self,
2318 engine: &EngineName,
2319 market: &MarketName,
2320 board: &BoardId,
2321 security: &SecId,
2322 query: CandleQuery,
2323 pagination: Pagination,
2324 ) -> Result<Vec<Candle>, MoexError> {
2325 let endpoint = candles_endpoint(engine, market, board, security);
2326 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2327 {
2328 let mut query_pairs = endpoint_url.query_pairs_mut();
2329 query_pairs
2330 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2331 .append_pair(ISS_ONLY_PARAM, "candles")
2332 .append_pair(CANDLES_COLUMNS_PARAM, CANDLES_COLUMNS);
2333 }
2334 append_candle_query_to_url(&mut endpoint_url, query);
2335 append_pagination_to_url(&mut endpoint_url, pagination);
2336
2337 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2338 decode_candles_json_with_endpoint(&payload, endpoint.as_str())
2339 }
2340
2341 async fn fetch_trades_page(
2342 &self,
2343 engine: &EngineName,
2344 market: &MarketName,
2345 board: &BoardId,
2346 security: &SecId,
2347 pagination: Pagination,
2348 ) -> Result<Vec<Trade>, MoexError> {
2349 let endpoint = trades_endpoint(engine, market, board, security);
2350 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2351 {
2352 let mut query = endpoint_url.query_pairs_mut();
2353 query
2354 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2355 .append_pair(ISS_ONLY_PARAM, "trades")
2356 .append_pair(TRADES_COLUMNS_PARAM, TRADES_COLUMNS);
2357 }
2358 append_pagination_to_url(&mut endpoint_url, pagination);
2359
2360 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2361 decode_trades_json_with_endpoint(&payload, endpoint.as_str())
2362 }
2363
2364 #[cfg(feature = "history")]
2365 async fn fetch_history_page(
2366 &self,
2367 engine: &EngineName,
2368 market: &MarketName,
2369 board: &BoardId,
2370 security: &SecId,
2371 pagination: Pagination,
2372 ) -> Result<Vec<HistoryRecord>, MoexError> {
2373 let endpoint = history_endpoint(engine, market, board, security);
2374 let mut endpoint_url = self.endpoint_url(endpoint.as_str())?;
2375 {
2376 let mut query = endpoint_url.query_pairs_mut();
2377 query
2378 .append_pair(ISS_META_PARAM, metadata_value(self.metadata))
2379 .append_pair(ISS_ONLY_PARAM, "history")
2380 .append_pair(HISTORY_COLUMNS_PARAM, HISTORY_COLUMNS);
2381 }
2382 append_pagination_to_url(&mut endpoint_url, pagination);
2383
2384 let payload = self.fetch_payload(endpoint.as_str(), endpoint_url).await?;
2385 decode_history_json_with_endpoint(&payload, endpoint.as_str())
2386 }
2387
2388 fn endpoint_url(&self, endpoint: &str) -> Result<Url, MoexError> {
2389 self.base_url
2390 .join(endpoint)
2391 .map_err(|source| MoexError::EndpointUrl {
2392 endpoint: endpoint.to_owned().into_boxed_str(),
2393 reason: source.to_string(),
2394 })
2395 }
2396
2397 async fn get_payload(
2398 &self,
2399 endpoint: &str,
2400 query_params: &[(&'static str, &'static str)],
2401 ) -> Result<String, MoexError> {
2402 let mut endpoint_url = self.endpoint_url(endpoint)?;
2403 {
2404 let mut url_query = endpoint_url.query_pairs_mut();
2405 for (key, value) in query_params {
2406 url_query.append_pair(key, value);
2407 }
2408 }
2409 self.fetch_payload(endpoint, endpoint_url).await
2410 }
2411
2412 async fn fetch_payload(&self, endpoint: &str, endpoint_url: Url) -> Result<String, MoexError> {
2413 self.wait_for_rate_limit().await;
2414 let response = self
2415 .client
2416 .get(endpoint_url)
2417 .send()
2418 .await
2419 .map_err(|source| MoexError::Request {
2420 endpoint: endpoint.to_owned().into_boxed_str(),
2421 source,
2422 })?;
2423 let status = response.status();
2424
2425 let content_type = response
2426 .headers()
2427 .get(reqwest::header::CONTENT_TYPE)
2428 .and_then(|value| value.to_str().ok())
2429 .map(|value| value.to_owned().into_boxed_str());
2430
2431 let payload = response
2432 .text()
2433 .await
2434 .map_err(|source| MoexError::ReadBody {
2435 endpoint: endpoint.to_owned().into_boxed_str(),
2436 source,
2437 })?;
2438
2439 if !status.is_success() {
2440 return Err(MoexError::HttpStatus {
2441 endpoint: endpoint.to_owned().into_boxed_str(),
2442 status,
2443 content_type,
2444 body_prefix: truncate_prefix(&payload, NON_JSON_BODY_PREFIX_CHARS),
2445 });
2446 }
2447
2448 if !looks_like_json_payload(content_type.as_deref(), &payload) {
2449 return Err(MoexError::NonJsonPayload {
2450 endpoint: endpoint.to_owned().into_boxed_str(),
2451 content_type,
2452 body_prefix: truncate_prefix(&payload, NON_JSON_BODY_PREFIX_CHARS),
2453 });
2454 }
2455
2456 Ok(payload)
2457 }
2458
2459 async fn wait_for_rate_limit(&self) {
2460 let Some(rate_limit) = &self.rate_limit else {
2461 return;
2462 };
2463 let delay = reserve_rate_limit_delay(&rate_limit.limiter);
2464 if !delay.is_zero() {
2465 (rate_limit.sleep)(delay).await;
2466 }
2467 }
2468}
2469
2470#[cfg(feature = "async")]
2472pub struct AsyncMoexClientBuilder {
2473 base_url: Option<Url>,
2474 metadata: bool,
2475 client: Option<reqwest::Client>,
2476 http_client: reqwest::ClientBuilder,
2477 rate_limit: Option<RateLimit>,
2478 rate_limit_sleep: Option<AsyncRateLimitSleep>,
2479}
2480
2481#[cfg(feature = "async")]
2482impl AsyncMoexClientBuilder {
2483 pub fn metadata(mut self, metadata: bool) -> Self {
2485 self.metadata = metadata;
2486 self
2487 }
2488
2489 pub fn base_url(mut self, base_url: Url) -> Self {
2491 self.base_url = Some(base_url);
2492 self
2493 }
2494
2495 pub fn client(mut self, client: reqwest::Client) -> Self {
2497 self.client = Some(client);
2498 self
2499 }
2500
2501 pub fn timeout(mut self, timeout: Duration) -> Self {
2503 self.http_client = self.http_client.timeout(timeout);
2504 self
2505 }
2506
2507 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
2509 self.http_client = self.http_client.connect_timeout(timeout);
2510 self
2511 }
2512
2513 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
2515 self.http_client = self.http_client.user_agent(user_agent.into());
2516 self
2517 }
2518
2519 pub fn user_agent_from_crate(self) -> Self {
2521 self.user_agent(format!(
2522 "{}/{}",
2523 env!("CARGO_PKG_NAME"),
2524 env!("CARGO_PKG_VERSION")
2525 ))
2526 }
2527
2528 pub fn default_headers(mut self, headers: HeaderMap) -> Self {
2530 self.http_client = self.http_client.default_headers(headers);
2531 self
2532 }
2533
2534 pub fn proxy(mut self, proxy: reqwest::Proxy) -> Self {
2538 self.http_client = self.http_client.proxy(proxy);
2539 self
2540 }
2541
2542 pub fn no_proxy(mut self) -> Self {
2544 self.http_client = self.http_client.no_proxy();
2545 self
2546 }
2547
2548 pub fn rate_limit(mut self, rate_limit: RateLimit) -> Self {
2553 self.rate_limit = Some(rate_limit);
2554 self
2555 }
2556
2557 pub fn rate_limit_sleep<F, Fut>(mut self, sleep: F) -> Self
2561 where
2562 F: Fn(Duration) -> Fut + Send + Sync + 'static,
2563 Fut: std::future::Future<Output = ()> + 'static,
2564 {
2565 self.rate_limit_sleep = Some(std::sync::Arc::new(move |delay| Box::pin(sleep(delay))));
2566 self
2567 }
2568
2569 pub fn build(self) -> Result<AsyncMoexClient, MoexError> {
2571 let Self {
2572 base_url,
2573 metadata,
2574 client,
2575 http_client,
2576 rate_limit,
2577 rate_limit_sleep,
2578 } = self;
2579 let base_url = resolve_base_url_or_default(base_url)?;
2580 let client = resolve_async_http_client(client, http_client)?;
2581 let rate_limit = resolve_async_rate_limit_state(rate_limit, rate_limit_sleep)?;
2582 Ok(AsyncMoexClient::with_base_url_and_rate_limit(
2583 client, base_url, metadata, rate_limit,
2584 ))
2585 }
2586}
2587
2588#[cfg(feature = "async")]
2589struct AsyncRateLimitState {
2590 limiter: Mutex<RateLimiter>,
2591 sleep: AsyncRateLimitSleep,
2592}
2593
2594#[cfg(feature = "blocking")]
2599pub struct RawIssRequestBuilder<'a> {
2600 client: &'a BlockingMoexClient,
2601 path: Option<Box<str>>,
2602 query: Vec<(Box<str>, Box<str>)>,
2603}
2604
2605#[cfg(feature = "blocking")]
2606impl<'a> RawIssRequestBuilder<'a> {
2607 pub fn path(mut self, path: impl Into<String>) -> Self {
2614 self.path = Some(path.into().into_boxed_str());
2615 self
2616 }
2617
2618 pub fn param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
2620 self.query
2621 .push((key.into().into_boxed_str(), value.into().into_boxed_str()));
2622 self
2623 }
2624
2625 pub fn only(self, tables: impl Into<String>) -> Self {
2627 self.param(ISS_ONLY_PARAM, tables)
2628 }
2629
2630 pub fn columns(self, table: impl Into<String>, columns: impl Into<String>) -> Self {
2632 let mut key = table.into();
2633 key.push_str(".columns");
2634 self.param(key, columns)
2635 }
2636
2637 pub fn metadata(self, metadata: IssToggle) -> Self {
2639 self.param(ISS_META_PARAM, metadata.as_query_value())
2640 }
2641
2642 pub fn data(self, data: IssToggle) -> Self {
2644 self.param(ISS_DATA_PARAM, data.as_query_value())
2645 }
2646
2647 pub fn json(self, json: impl Into<String>) -> Self {
2649 self.param(ISS_JSON_PARAM, json)
2650 }
2651
2652 pub fn version(self, version: IssToggle) -> Self {
2654 self.param(ISS_VERSION_PARAM, version.as_query_value())
2655 }
2656
2657 pub fn options(mut self, options: IssRequestOptions) -> Self {
2659 apply_iss_request_options(&mut self.query, options);
2660 self
2661 }
2662
2663 pub fn send_response(self) -> Result<RawIssResponse, MoexError> {
2667 let (_, response) = self.execute_response()?;
2668 Ok(response)
2669 }
2670
2671 pub fn send_payload(self) -> Result<String, MoexError> {
2673 let (_, payload) = self.execute()?;
2674 Ok(payload)
2675 }
2676
2677 pub fn send_json<T>(self) -> Result<T, MoexError>
2679 where
2680 T: serde::de::DeserializeOwned,
2681 {
2682 let (endpoint, payload) = self.execute()?;
2683 serde_json::from_str(&payload).map_err(|source| MoexError::Decode { endpoint, source })
2684 }
2685
2686 pub fn send_table<T>(self, table: impl Into<String>) -> Result<Vec<T>, MoexError>
2688 where
2689 T: serde::de::DeserializeOwned,
2690 {
2691 let table = table.into();
2692 let (endpoint, payload) = self.execute()?;
2693 decode_raw_table_rows_json_with_endpoint(&payload, endpoint.as_ref(), table.as_str())
2694 }
2695
2696 fn execute(self) -> Result<(Box<str>, String), MoexError> {
2697 let (endpoint, endpoint_url) = self.build_request()?;
2698 let payload = self.client.fetch_payload(&endpoint, endpoint_url)?;
2699 Ok((endpoint, payload))
2700 }
2701
2702 fn execute_response(self) -> Result<(Box<str>, RawIssResponse), MoexError> {
2703 let (endpoint, endpoint_url) = self.build_request()?;
2704 self.client.wait_for_rate_limit();
2705 let response = self
2706 .client
2707 .client
2708 .get(endpoint_url)
2709 .send()
2710 .map_err(|source| MoexError::Request {
2711 endpoint: endpoint.clone(),
2712 source,
2713 })?;
2714 let status = response.status();
2715 let headers = response.headers().clone();
2716 let body = response.text().map_err(|source| MoexError::ReadBody {
2717 endpoint: endpoint.clone(),
2718 source,
2719 })?;
2720 Ok((endpoint, RawIssResponse::new(status, headers, body)))
2721 }
2722
2723 fn build_request(&self) -> Result<(Box<str>, Url), MoexError> {
2724 let endpoint = normalize_raw_endpoint_path(self.path.as_deref())?;
2725 let mut endpoint_url = self.client.endpoint_url(&endpoint)?;
2726 let has_meta = self
2727 .query
2728 .iter()
2729 .any(|(key, _)| key.as_ref() == ISS_META_PARAM);
2730 {
2731 let mut url_query = endpoint_url.query_pairs_mut();
2732 if !has_meta {
2733 url_query.append_pair(ISS_META_PARAM, metadata_value(self.client.metadata));
2734 }
2735 for (key, value) in &self.query {
2736 url_query.append_pair(key, value);
2737 }
2738 }
2739 Ok((endpoint, endpoint_url))
2740 }
2741}
2742
2743#[cfg(feature = "async")]
2745pub struct AsyncRawIssRequestBuilder<'a> {
2746 client: &'a AsyncMoexClient,
2747 path: Option<Box<str>>,
2748 query: Vec<(Box<str>, Box<str>)>,
2749}
2750
2751#[cfg(feature = "async")]
2752impl<'a> AsyncRawIssRequestBuilder<'a> {
2753 pub fn path(mut self, path: impl Into<String>) -> Self {
2760 self.path = Some(path.into().into_boxed_str());
2761 self
2762 }
2763
2764 pub fn param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
2766 self.query
2767 .push((key.into().into_boxed_str(), value.into().into_boxed_str()));
2768 self
2769 }
2770
2771 pub fn only(self, tables: impl Into<String>) -> Self {
2773 self.param(ISS_ONLY_PARAM, tables)
2774 }
2775
2776 pub fn columns(self, table: impl Into<String>, columns: impl Into<String>) -> Self {
2778 let mut key = table.into();
2779 key.push_str(".columns");
2780 self.param(key, columns)
2781 }
2782
2783 pub fn metadata(self, metadata: IssToggle) -> Self {
2785 self.param(ISS_META_PARAM, metadata.as_query_value())
2786 }
2787
2788 pub fn data(self, data: IssToggle) -> Self {
2790 self.param(ISS_DATA_PARAM, data.as_query_value())
2791 }
2792
2793 pub fn json(self, json: impl Into<String>) -> Self {
2795 self.param(ISS_JSON_PARAM, json)
2796 }
2797
2798 pub fn version(self, version: IssToggle) -> Self {
2800 self.param(ISS_VERSION_PARAM, version.as_query_value())
2801 }
2802
2803 pub fn options(mut self, options: IssRequestOptions) -> Self {
2805 apply_iss_request_options(&mut self.query, options);
2806 self
2807 }
2808
2809 pub async fn send_response(self) -> Result<RawIssResponse, MoexError> {
2813 let (_, response) = self.execute_response().await?;
2814 Ok(response)
2815 }
2816
2817 pub async fn send_payload(self) -> Result<String, MoexError> {
2819 let (_, payload) = self.execute().await?;
2820 Ok(payload)
2821 }
2822
2823 pub async fn send_json<T>(self) -> Result<T, MoexError>
2825 where
2826 T: serde::de::DeserializeOwned,
2827 {
2828 let (endpoint, payload) = self.execute().await?;
2829 serde_json::from_str(&payload).map_err(|source| MoexError::Decode { endpoint, source })
2830 }
2831
2832 pub async fn send_table<T>(self, table: impl Into<String>) -> Result<Vec<T>, MoexError>
2834 where
2835 T: serde::de::DeserializeOwned,
2836 {
2837 let table = table.into();
2838 let (endpoint, payload) = self.execute().await?;
2839 decode_raw_table_rows_json_with_endpoint(&payload, endpoint.as_ref(), table.as_str())
2840 }
2841
2842 async fn execute(self) -> Result<(Box<str>, String), MoexError> {
2843 let (endpoint, endpoint_url) = self.build_request()?;
2844 let payload = self.client.fetch_payload(&endpoint, endpoint_url).await?;
2845 Ok((endpoint, payload))
2846 }
2847
2848 async fn execute_response(self) -> Result<(Box<str>, RawIssResponse), MoexError> {
2849 let (endpoint, endpoint_url) = self.build_request()?;
2850 self.client.wait_for_rate_limit().await;
2851 let response = self
2852 .client
2853 .client
2854 .get(endpoint_url)
2855 .send()
2856 .await
2857 .map_err(|source| MoexError::Request {
2858 endpoint: endpoint.clone(),
2859 source,
2860 })?;
2861 let status = response.status();
2862 let headers = response.headers().clone();
2863 let body = response
2864 .text()
2865 .await
2866 .map_err(|source| MoexError::ReadBody {
2867 endpoint: endpoint.clone(),
2868 source,
2869 })?;
2870 Ok((endpoint, RawIssResponse::new(status, headers, body)))
2871 }
2872
2873 fn build_request(&self) -> Result<(Box<str>, Url), MoexError> {
2874 let endpoint = normalize_raw_endpoint_path(self.path.as_deref())?;
2875 let mut endpoint_url = self.client.endpoint_url(&endpoint)?;
2876 let has_meta = self
2877 .query
2878 .iter()
2879 .any(|(key, _)| key.as_ref() == ISS_META_PARAM);
2880 {
2881 let mut url_query = endpoint_url.query_pairs_mut();
2882 if !has_meta {
2883 url_query.append_pair(ISS_META_PARAM, metadata_value(self.client.metadata));
2884 }
2885 for (key, value) in &self.query {
2886 url_query.append_pair(key, value);
2887 }
2888 }
2889 Ok((endpoint, endpoint_url))
2890 }
2891}
2892
2893#[cfg(feature = "async")]
2895pub struct AsyncIndexAnalyticsPages<'a> {
2896 client: &'a AsyncMoexClient,
2897 indexid: &'a IndexId,
2898 pagination: PaginationTracker<(chrono::NaiveDate, SecId)>,
2899}
2900
2901#[cfg(feature = "async")]
2902impl<'a> AsyncIndexAnalyticsPages<'a> {
2903 pub async fn next_page(&mut self) -> Result<Option<Vec<IndexAnalytics>>, MoexError> {
2905 next_page_async(
2906 &mut self.pagination,
2907 |pagination| {
2908 self.client
2909 .fetch_index_analytics_page(self.indexid, pagination)
2910 },
2911 |item| (item.trade_session_date(), item.secid().clone()),
2912 )
2913 .await
2914 }
2915
2916 pub async fn try_collect(mut self) -> Result<Vec<IndexAnalytics>, MoexError> {
2918 {
2919 let mut items = Vec::new();
2920 while let Some(page) = self.next_page().await? {
2921 items.extend(page);
2922 }
2923 Ok(items)
2924 }
2925 }
2926
2927 pub async fn all(self) -> Result<Vec<IndexAnalytics>, MoexError> {
2929 self.try_collect().await
2930 }
2931}
2932
2933#[cfg(feature = "async")]
2935pub struct AsyncSecuritiesPages<'a> {
2936 client: &'a AsyncMoexClient,
2937 engine: &'a EngineName,
2938 market: &'a MarketName,
2939 board: &'a BoardId,
2940 pagination: PaginationTracker<SecId>,
2941}
2942
2943#[cfg(feature = "async")]
2944impl<'a> AsyncSecuritiesPages<'a> {
2945 pub async fn next_page(&mut self) -> Result<Option<Vec<Security>>, MoexError> {
2947 next_page_async(
2948 &mut self.pagination,
2949 |pagination| {
2950 self.client
2951 .fetch_securities_page(self.engine, self.market, self.board, pagination)
2952 },
2953 |item| item.secid().clone(),
2954 )
2955 .await
2956 }
2957
2958 pub async fn try_collect(mut self) -> Result<Vec<Security>, MoexError> {
2960 {
2961 let mut items = Vec::new();
2962 while let Some(page) = self.next_page().await? {
2963 items.extend(page);
2964 }
2965 Ok(items)
2966 }
2967 }
2968
2969 pub async fn all(self) -> Result<Vec<Security>, MoexError> {
2971 self.try_collect().await
2972 }
2973}
2974
2975#[cfg(feature = "async")]
2977pub struct AsyncGlobalSecuritiesPages<'a> {
2978 client: &'a AsyncMoexClient,
2979 pagination: PaginationTracker<SecId>,
2980}
2981
2982#[cfg(feature = "async")]
2983impl<'a> AsyncGlobalSecuritiesPages<'a> {
2984 pub async fn next_page(&mut self) -> Result<Option<Vec<Security>>, MoexError> {
2986 next_page_async(
2987 &mut self.pagination,
2988 |pagination| self.client.fetch_global_securities_page(pagination),
2989 |item| item.secid().clone(),
2990 )
2991 .await
2992 }
2993
2994 pub async fn try_collect(mut self) -> Result<Vec<Security>, MoexError> {
2996 {
2997 let mut items = Vec::new();
2998 while let Some(page) = self.next_page().await? {
2999 items.extend(page);
3000 }
3001 Ok(items)
3002 }
3003 }
3004
3005 pub async fn all(self) -> Result<Vec<Security>, MoexError> {
3007 self.try_collect().await
3008 }
3009}
3010
3011#[cfg(all(feature = "async", feature = "news"))]
3013pub struct AsyncSiteNewsPages<'a> {
3014 client: &'a AsyncMoexClient,
3015 pagination: PaginationTracker<u64>,
3016}
3017
3018#[cfg(all(feature = "async", feature = "news"))]
3019impl<'a> AsyncSiteNewsPages<'a> {
3020 pub async fn next_page(&mut self) -> Result<Option<Vec<SiteNews>>, MoexError> {
3022 next_page_async(
3023 &mut self.pagination,
3024 |pagination| self.client.fetch_sitenews_page(pagination),
3025 SiteNews::id,
3026 )
3027 .await
3028 }
3029
3030 pub async fn try_collect(mut self) -> Result<Vec<SiteNews>, MoexError> {
3032 {
3033 let mut items = Vec::new();
3034 while let Some(page) = self.next_page().await? {
3035 items.extend(page);
3036 }
3037 Ok(items)
3038 }
3039 }
3040
3041 pub async fn all(self) -> Result<Vec<SiteNews>, MoexError> {
3043 self.try_collect().await
3044 }
3045}
3046
3047#[cfg(all(feature = "async", feature = "news"))]
3049pub struct AsyncEventsPages<'a> {
3050 client: &'a AsyncMoexClient,
3051 pagination: PaginationTracker<u64>,
3052}
3053
3054#[cfg(all(feature = "async", feature = "news"))]
3055impl<'a> AsyncEventsPages<'a> {
3056 pub async fn next_page(&mut self) -> Result<Option<Vec<Event>>, MoexError> {
3058 next_page_async(
3059 &mut self.pagination,
3060 |pagination| self.client.fetch_events_page(pagination),
3061 Event::id,
3062 )
3063 .await
3064 }
3065
3066 pub async fn try_collect(mut self) -> Result<Vec<Event>, MoexError> {
3068 {
3069 let mut items = Vec::new();
3070 while let Some(page) = self.next_page().await? {
3071 items.extend(page);
3072 }
3073 Ok(items)
3074 }
3075 }
3076
3077 pub async fn all(self) -> Result<Vec<Event>, MoexError> {
3079 self.try_collect().await
3080 }
3081}
3082
3083#[cfg(feature = "async")]
3085pub struct AsyncMarketSecuritiesPages<'a> {
3086 client: &'a AsyncMoexClient,
3087 engine: &'a EngineName,
3088 market: &'a MarketName,
3089 pagination: PaginationTracker<SecId>,
3090}
3091
3092#[cfg(feature = "async")]
3093impl<'a> AsyncMarketSecuritiesPages<'a> {
3094 pub async fn next_page(&mut self) -> Result<Option<Vec<Security>>, MoexError> {
3096 next_page_async(
3097 &mut self.pagination,
3098 |pagination| {
3099 self.client
3100 .fetch_market_securities_page(self.engine, self.market, pagination)
3101 },
3102 |item| item.secid().clone(),
3103 )
3104 .await
3105 }
3106
3107 pub async fn try_collect(mut self) -> Result<Vec<Security>, MoexError> {
3109 {
3110 let mut items = Vec::new();
3111 while let Some(page) = self.next_page().await? {
3112 items.extend(page);
3113 }
3114 Ok(items)
3115 }
3116 }
3117
3118 pub async fn all(self) -> Result<Vec<Security>, MoexError> {
3120 self.try_collect().await
3121 }
3122}
3123
3124#[cfg(feature = "async")]
3126pub struct AsyncMarketTradesPages<'a> {
3127 client: &'a AsyncMoexClient,
3128 engine: &'a EngineName,
3129 market: &'a MarketName,
3130 pagination: PaginationTracker<u64>,
3131}
3132
3133#[cfg(feature = "async")]
3134impl<'a> AsyncMarketTradesPages<'a> {
3135 pub async fn next_page(&mut self) -> Result<Option<Vec<Trade>>, MoexError> {
3137 next_page_async(
3138 &mut self.pagination,
3139 |pagination| {
3140 self.client
3141 .fetch_market_trades_page(self.engine, self.market, pagination)
3142 },
3143 Trade::tradeno,
3144 )
3145 .await
3146 }
3147
3148 pub async fn try_collect(mut self) -> Result<Vec<Trade>, MoexError> {
3150 {
3151 let mut items = Vec::new();
3152 while let Some(page) = self.next_page().await? {
3153 items.extend(page);
3154 }
3155 Ok(items)
3156 }
3157 }
3158
3159 pub async fn all(self) -> Result<Vec<Trade>, MoexError> {
3161 self.try_collect().await
3162 }
3163}
3164
3165#[cfg(feature = "async")]
3167pub struct AsyncTradesPages<'a> {
3168 client: &'a AsyncMoexClient,
3169 engine: &'a EngineName,
3170 market: &'a MarketName,
3171 board: &'a BoardId,
3172 security: &'a SecId,
3173 pagination: PaginationTracker<u64>,
3174}
3175
3176#[cfg(feature = "async")]
3177impl<'a> AsyncTradesPages<'a> {
3178 pub async fn next_page(&mut self) -> Result<Option<Vec<Trade>>, MoexError> {
3180 next_page_async(
3181 &mut self.pagination,
3182 |pagination| {
3183 self.client.fetch_trades_page(
3184 self.engine,
3185 self.market,
3186 self.board,
3187 self.security,
3188 pagination,
3189 )
3190 },
3191 Trade::tradeno,
3192 )
3193 .await
3194 }
3195
3196 pub async fn try_collect(mut self) -> Result<Vec<Trade>, MoexError> {
3198 {
3199 let mut items = Vec::new();
3200 while let Some(page) = self.next_page().await? {
3201 items.extend(page);
3202 }
3203 Ok(items)
3204 }
3205 }
3206
3207 pub async fn all(self) -> Result<Vec<Trade>, MoexError> {
3209 self.try_collect().await
3210 }
3211}
3212
3213#[cfg(all(feature = "async", feature = "history"))]
3215pub struct AsyncHistoryPages<'a> {
3216 client: &'a AsyncMoexClient,
3217 engine: &'a EngineName,
3218 market: &'a MarketName,
3219 board: &'a BoardId,
3220 security: &'a SecId,
3221 pagination: PaginationTracker<chrono::NaiveDate>,
3222}
3223
3224#[cfg(all(feature = "async", feature = "history"))]
3225impl<'a> AsyncHistoryPages<'a> {
3226 pub async fn next_page(&mut self) -> Result<Option<Vec<HistoryRecord>>, MoexError> {
3228 next_page_async(
3229 &mut self.pagination,
3230 |pagination| {
3231 self.client.fetch_history_page(
3232 self.engine,
3233 self.market,
3234 self.board,
3235 self.security,
3236 pagination,
3237 )
3238 },
3239 HistoryRecord::tradedate,
3240 )
3241 .await
3242 }
3243
3244 pub async fn try_collect(mut self) -> Result<Vec<HistoryRecord>, MoexError> {
3246 {
3247 let mut items = Vec::new();
3248 while let Some(page) = self.next_page().await? {
3249 items.extend(page);
3250 }
3251 Ok(items)
3252 }
3253 }
3254
3255 pub async fn all(self) -> Result<Vec<HistoryRecord>, MoexError> {
3257 self.try_collect().await
3258 }
3259}
3260
3261#[cfg(feature = "async")]
3263pub struct AsyncSecStatsPages<'a> {
3264 client: &'a AsyncMoexClient,
3265 engine: &'a EngineName,
3266 market: &'a MarketName,
3267 pagination: PaginationTracker<(SecId, BoardId)>,
3268}
3269
3270#[cfg(feature = "async")]
3271impl<'a> AsyncSecStatsPages<'a> {
3272 pub async fn next_page(&mut self) -> Result<Option<Vec<SecStat>>, MoexError> {
3274 next_page_async(
3275 &mut self.pagination,
3276 |pagination| {
3277 self.client
3278 .fetch_secstats_page(self.engine, self.market, pagination)
3279 },
3280 |item| (item.secid().clone(), item.boardid().clone()),
3281 )
3282 .await
3283 }
3284
3285 pub async fn try_collect(mut self) -> Result<Vec<SecStat>, MoexError> {
3287 {
3288 let mut items = Vec::new();
3289 while let Some(page) = self.next_page().await? {
3290 items.extend(page);
3291 }
3292 Ok(items)
3293 }
3294 }
3295
3296 pub async fn all(self) -> Result<Vec<SecStat>, MoexError> {
3298 self.try_collect().await
3299 }
3300}
3301
3302#[cfg(feature = "async")]
3304pub struct AsyncCandlesPages<'a> {
3305 client: &'a AsyncMoexClient,
3306 engine: &'a EngineName,
3307 market: &'a MarketName,
3308 board: &'a BoardId,
3309 security: &'a SecId,
3310 query: CandleQuery,
3311 pagination: PaginationTracker<chrono::NaiveDateTime>,
3312}
3313
3314#[cfg(feature = "async")]
3315impl<'a> AsyncCandlesPages<'a> {
3316 pub async fn next_page(&mut self) -> Result<Option<Vec<Candle>>, MoexError> {
3318 next_page_async(
3319 &mut self.pagination,
3320 |pagination| {
3321 self.client.fetch_candles_page(
3322 self.engine,
3323 self.market,
3324 self.board,
3325 self.security,
3326 self.query,
3327 pagination,
3328 )
3329 },
3330 Candle::begin,
3331 )
3332 .await
3333 }
3334
3335 pub async fn try_collect(mut self) -> Result<Vec<Candle>, MoexError> {
3337 {
3338 let mut items = Vec::new();
3339 while let Some(page) = self.next_page().await? {
3340 items.extend(page);
3341 }
3342 Ok(items)
3343 }
3344 }
3345
3346 pub async fn all(self) -> Result<Vec<Candle>, MoexError> {
3348 self.try_collect().await
3349 }
3350}
3351
3352#[cfg(feature = "blocking")]
3354pub struct IndexAnalyticsPages<'a> {
3355 client: &'a BlockingMoexClient,
3356 indexid: &'a IndexId,
3357 pagination: PaginationTracker<(chrono::NaiveDate, SecId)>,
3358}
3359
3360#[cfg(feature = "blocking")]
3361impl<'a> IndexAnalyticsPages<'a> {
3362 pub fn next_page(&mut self) -> Result<Option<Vec<IndexAnalytics>>, MoexError> {
3364 next_page_blocking(
3365 &mut self.pagination,
3366 |pagination| {
3367 self.client
3368 .fetch_index_analytics_page(self.indexid, pagination)
3369 },
3370 |item| (item.trade_session_date(), item.secid().clone()),
3371 )
3372 }
3373
3374 pub fn try_collect(mut self) -> Result<Vec<IndexAnalytics>, MoexError> {
3376 collect_pages_blocking(|| self.next_page())
3377 }
3378
3379 pub fn all(self) -> Result<Vec<IndexAnalytics>, MoexError> {
3381 self.try_collect()
3382 }
3383}
3384
3385#[cfg(feature = "blocking")]
3387pub struct SecuritiesPages<'a> {
3388 client: &'a BlockingMoexClient,
3389 engine: &'a EngineName,
3390 market: &'a MarketName,
3391 board: &'a BoardId,
3392 pagination: PaginationTracker<SecId>,
3393}
3394
3395#[cfg(feature = "blocking")]
3396impl<'a> SecuritiesPages<'a> {
3397 pub fn next_page(&mut self) -> Result<Option<Vec<Security>>, MoexError> {
3399 next_page_blocking(
3400 &mut self.pagination,
3401 |pagination| {
3402 self.client
3403 .fetch_securities_page(self.engine, self.market, self.board, pagination)
3404 },
3405 |item| item.secid().clone(),
3406 )
3407 }
3408
3409 pub fn try_collect(mut self) -> Result<Vec<Security>, MoexError> {
3411 collect_pages_blocking(|| self.next_page())
3412 }
3413
3414 pub fn all(self) -> Result<Vec<Security>, MoexError> {
3416 self.try_collect()
3417 }
3418}
3419
3420#[cfg(feature = "blocking")]
3422pub struct GlobalSecuritiesPages<'a> {
3423 client: &'a BlockingMoexClient,
3424 pagination: PaginationTracker<SecId>,
3425}
3426
3427#[cfg(feature = "blocking")]
3428impl<'a> GlobalSecuritiesPages<'a> {
3429 pub fn next_page(&mut self) -> Result<Option<Vec<Security>>, MoexError> {
3431 next_page_blocking(
3432 &mut self.pagination,
3433 |pagination| self.client.fetch_global_securities_page(pagination),
3434 |item| item.secid().clone(),
3435 )
3436 }
3437
3438 pub fn try_collect(mut self) -> Result<Vec<Security>, MoexError> {
3440 collect_pages_blocking(|| self.next_page())
3441 }
3442
3443 pub fn all(self) -> Result<Vec<Security>, MoexError> {
3445 self.try_collect()
3446 }
3447}
3448
3449#[cfg(all(feature = "blocking", feature = "news"))]
3451pub struct SiteNewsPages<'a> {
3452 client: &'a BlockingMoexClient,
3453 pagination: PaginationTracker<u64>,
3454}
3455
3456#[cfg(all(feature = "blocking", feature = "news"))]
3457impl<'a> SiteNewsPages<'a> {
3458 pub fn next_page(&mut self) -> Result<Option<Vec<SiteNews>>, MoexError> {
3460 next_page_blocking(
3461 &mut self.pagination,
3462 |pagination| self.client.fetch_sitenews_page(pagination),
3463 SiteNews::id,
3464 )
3465 }
3466
3467 pub fn try_collect(mut self) -> Result<Vec<SiteNews>, MoexError> {
3469 collect_pages_blocking(|| self.next_page())
3470 }
3471
3472 pub fn all(self) -> Result<Vec<SiteNews>, MoexError> {
3474 self.try_collect()
3475 }
3476}
3477
3478#[cfg(all(feature = "blocking", feature = "news"))]
3480pub struct EventsPages<'a> {
3481 client: &'a BlockingMoexClient,
3482 pagination: PaginationTracker<u64>,
3483}
3484
3485#[cfg(all(feature = "blocking", feature = "news"))]
3486impl<'a> EventsPages<'a> {
3487 pub fn next_page(&mut self) -> Result<Option<Vec<Event>>, MoexError> {
3489 next_page_blocking(
3490 &mut self.pagination,
3491 |pagination| self.client.fetch_events_page(pagination),
3492 Event::id,
3493 )
3494 }
3495
3496 pub fn try_collect(mut self) -> Result<Vec<Event>, MoexError> {
3498 collect_pages_blocking(|| self.next_page())
3499 }
3500
3501 pub fn all(self) -> Result<Vec<Event>, MoexError> {
3503 self.try_collect()
3504 }
3505}
3506
3507#[cfg(feature = "blocking")]
3509pub struct MarketSecuritiesPages<'a> {
3510 client: &'a BlockingMoexClient,
3511 engine: &'a EngineName,
3512 market: &'a MarketName,
3513 pagination: PaginationTracker<SecId>,
3514}
3515
3516#[cfg(feature = "blocking")]
3517impl<'a> MarketSecuritiesPages<'a> {
3518 pub fn next_page(&mut self) -> Result<Option<Vec<Security>>, MoexError> {
3520 next_page_blocking(
3521 &mut self.pagination,
3522 |pagination| {
3523 self.client
3524 .fetch_market_securities_page(self.engine, self.market, pagination)
3525 },
3526 |item| item.secid().clone(),
3527 )
3528 }
3529
3530 pub fn try_collect(mut self) -> Result<Vec<Security>, MoexError> {
3532 collect_pages_blocking(|| self.next_page())
3533 }
3534
3535 pub fn all(self) -> Result<Vec<Security>, MoexError> {
3537 self.try_collect()
3538 }
3539}
3540
3541#[cfg(feature = "blocking")]
3543pub struct MarketTradesPages<'a> {
3544 client: &'a BlockingMoexClient,
3545 engine: &'a EngineName,
3546 market: &'a MarketName,
3547 pagination: PaginationTracker<u64>,
3548}
3549
3550#[cfg(feature = "blocking")]
3551impl<'a> MarketTradesPages<'a> {
3552 pub fn next_page(&mut self) -> Result<Option<Vec<Trade>>, MoexError> {
3554 next_page_blocking(
3555 &mut self.pagination,
3556 |pagination| {
3557 self.client
3558 .fetch_market_trades_page(self.engine, self.market, pagination)
3559 },
3560 Trade::tradeno,
3561 )
3562 }
3563
3564 pub fn try_collect(mut self) -> Result<Vec<Trade>, MoexError> {
3566 collect_pages_blocking(|| self.next_page())
3567 }
3568
3569 pub fn all(self) -> Result<Vec<Trade>, MoexError> {
3571 self.try_collect()
3572 }
3573}
3574
3575#[cfg(feature = "blocking")]
3577pub struct TradesPages<'a> {
3578 client: &'a BlockingMoexClient,
3579 engine: &'a EngineName,
3580 market: &'a MarketName,
3581 board: &'a BoardId,
3582 security: &'a SecId,
3583 pagination: PaginationTracker<u64>,
3584}
3585
3586#[cfg(feature = "blocking")]
3587impl<'a> TradesPages<'a> {
3588 pub fn next_page(&mut self) -> Result<Option<Vec<Trade>>, MoexError> {
3590 next_page_blocking(
3591 &mut self.pagination,
3592 |pagination| {
3593 self.client.fetch_trades_page(
3594 self.engine,
3595 self.market,
3596 self.board,
3597 self.security,
3598 pagination,
3599 )
3600 },
3601 Trade::tradeno,
3602 )
3603 }
3604
3605 pub fn try_collect(mut self) -> Result<Vec<Trade>, MoexError> {
3607 collect_pages_blocking(|| self.next_page())
3608 }
3609
3610 pub fn all(self) -> Result<Vec<Trade>, MoexError> {
3612 self.try_collect()
3613 }
3614}
3615
3616#[cfg(all(feature = "blocking", feature = "history"))]
3618pub struct HistoryPages<'a> {
3619 client: &'a BlockingMoexClient,
3620 engine: &'a EngineName,
3621 market: &'a MarketName,
3622 board: &'a BoardId,
3623 security: &'a SecId,
3624 pagination: PaginationTracker<chrono::NaiveDate>,
3625}
3626
3627#[cfg(all(feature = "blocking", feature = "history"))]
3628impl<'a> HistoryPages<'a> {
3629 pub fn next_page(&mut self) -> Result<Option<Vec<HistoryRecord>>, MoexError> {
3631 next_page_blocking(
3632 &mut self.pagination,
3633 |pagination| {
3634 self.client.fetch_history_page(
3635 self.engine,
3636 self.market,
3637 self.board,
3638 self.security,
3639 pagination,
3640 )
3641 },
3642 HistoryRecord::tradedate,
3643 )
3644 }
3645
3646 pub fn try_collect(mut self) -> Result<Vec<HistoryRecord>, MoexError> {
3648 collect_pages_blocking(|| self.next_page())
3649 }
3650
3651 pub fn all(self) -> Result<Vec<HistoryRecord>, MoexError> {
3653 self.try_collect()
3654 }
3655}
3656
3657#[cfg(feature = "blocking")]
3659pub struct SecStatsPages<'a> {
3660 client: &'a BlockingMoexClient,
3661 engine: &'a EngineName,
3662 market: &'a MarketName,
3663 pagination: PaginationTracker<(SecId, BoardId)>,
3664}
3665
3666#[cfg(feature = "blocking")]
3667impl<'a> SecStatsPages<'a> {
3668 pub fn next_page(&mut self) -> Result<Option<Vec<SecStat>>, MoexError> {
3670 next_page_blocking(
3671 &mut self.pagination,
3672 |pagination| {
3673 self.client
3674 .fetch_secstats_page(self.engine, self.market, pagination)
3675 },
3676 |item| (item.secid().clone(), item.boardid().clone()),
3677 )
3678 }
3679
3680 pub fn try_collect(mut self) -> Result<Vec<SecStat>, MoexError> {
3682 collect_pages_blocking(|| self.next_page())
3683 }
3684
3685 pub fn all(self) -> Result<Vec<SecStat>, MoexError> {
3687 self.try_collect()
3688 }
3689}
3690
3691#[cfg(feature = "blocking")]
3693pub struct CandlesPages<'a> {
3694 client: &'a BlockingMoexClient,
3695 engine: &'a EngineName,
3696 market: &'a MarketName,
3697 board: &'a BoardId,
3698 security: &'a SecId,
3699 query: CandleQuery,
3700 pagination: PaginationTracker<chrono::NaiveDateTime>,
3701}
3702
3703#[cfg(feature = "blocking")]
3704impl<'a> CandlesPages<'a> {
3705 pub fn next_page(&mut self) -> Result<Option<Vec<Candle>>, MoexError> {
3707 next_page_blocking(
3708 &mut self.pagination,
3709 |pagination| {
3710 self.client.fetch_candles_page(
3711 self.engine,
3712 self.market,
3713 self.board,
3714 self.security,
3715 self.query,
3716 pagination,
3717 )
3718 },
3719 Candle::begin,
3720 )
3721 }
3722
3723 pub fn try_collect(mut self) -> Result<Vec<Candle>, MoexError> {
3725 collect_pages_blocking(|| self.next_page())
3726 }
3727
3728 pub fn all(self) -> Result<Vec<Candle>, MoexError> {
3730 self.try_collect()
3731 }
3732}
3733
3734#[derive(Clone)]
3735#[cfg(feature = "async")]
3739pub struct AsyncOwnedIndexScope<'a> {
3740 client: &'a AsyncMoexClient,
3741 indexid: IndexId,
3742}
3743
3744#[cfg(feature = "async")]
3745impl<'a> AsyncOwnedIndexScope<'a> {
3746 pub fn indexid(&self) -> &IndexId {
3748 &self.indexid
3749 }
3750
3751 pub async fn analytics(
3753 &self,
3754 page_request: PageRequest,
3755 ) -> Result<Vec<IndexAnalytics>, MoexError> {
3756 self.client
3757 .index_analytics_query(&self.indexid, page_request)
3758 .await
3759 }
3760
3761 pub fn analytics_pages(&self, page_limit: NonZeroU32) -> AsyncIndexAnalyticsPages<'_> {
3763 self.client.index_analytics_pages(&self.indexid, page_limit)
3764 }
3765}
3766
3767#[derive(Clone)]
3768#[cfg(feature = "async")]
3770pub struct AsyncOwnedEngineScope<'a> {
3771 client: &'a AsyncMoexClient,
3772 engine: EngineName,
3773}
3774
3775#[cfg(feature = "async")]
3776impl<'a> AsyncOwnedEngineScope<'a> {
3777 pub fn engine(&self) -> &EngineName {
3779 &self.engine
3780 }
3781
3782 pub async fn markets(&self) -> Result<Vec<Market>, MoexError> {
3784 self.client.markets(&self.engine).await
3785 }
3786
3787 pub async fn turnovers(&self) -> Result<Vec<Turnover>, MoexError> {
3789 self.client.engine_turnovers(&self.engine).await
3790 }
3791
3792 pub fn market<M>(self, market: M) -> Result<AsyncOwnedMarketScope<'a>, ParseMarketNameError>
3794 where
3795 M: TryInto<MarketName>,
3796 M::Error: Into<ParseMarketNameError>,
3797 {
3798 let market = market.try_into().map_err(Into::into)?;
3799 Ok(AsyncOwnedMarketScope {
3800 client: self.client,
3801 engine: self.engine,
3802 market,
3803 })
3804 }
3805
3806 pub fn shares(self) -> Result<AsyncOwnedMarketScope<'a>, ParseMarketNameError> {
3808 self.market("shares")
3809 }
3810}
3811
3812#[derive(Clone)]
3813#[cfg(feature = "async")]
3815pub struct AsyncOwnedMarketScope<'a> {
3816 client: &'a AsyncMoexClient,
3817 engine: EngineName,
3818 market: MarketName,
3819}
3820
3821#[cfg(feature = "async")]
3822impl<'a> AsyncOwnedMarketScope<'a> {
3823 pub fn engine(&self) -> &EngineName {
3825 &self.engine
3826 }
3827
3828 pub fn market(&self) -> &MarketName {
3830 &self.market
3831 }
3832
3833 pub async fn boards(&self) -> Result<Vec<Board>, MoexError> {
3835 self.client.boards(&self.engine, &self.market).await
3836 }
3837
3838 pub async fn securities(&self, page_request: PageRequest) -> Result<Vec<Security>, MoexError> {
3840 self.client
3841 .market_securities_query(&self.engine, &self.market, page_request)
3842 .await
3843 }
3844
3845 pub fn securities_pages(&self, page_limit: NonZeroU32) -> AsyncMarketSecuritiesPages<'_> {
3847 self.client
3848 .market_securities_pages(&self.engine, &self.market, page_limit)
3849 }
3850
3851 pub async fn orderbook(&self) -> Result<Vec<OrderbookLevel>, MoexError> {
3853 self.client
3854 .market_orderbook(&self.engine, &self.market)
3855 .await
3856 }
3857
3858 pub async fn trades(&self, page_request: PageRequest) -> Result<Vec<Trade>, MoexError> {
3860 self.client
3861 .market_trades_query(&self.engine, &self.market, page_request)
3862 .await
3863 }
3864
3865 pub fn trades_pages(&self, page_limit: NonZeroU32) -> AsyncMarketTradesPages<'_> {
3867 self.client
3868 .market_trades_pages(&self.engine, &self.market, page_limit)
3869 }
3870
3871 pub async fn secstats(&self, page_request: PageRequest) -> Result<Vec<SecStat>, MoexError> {
3873 self.client
3874 .secstats_query(&self.engine, &self.market, page_request)
3875 .await
3876 }
3877
3878 pub fn secstats_pages(&self, page_limit: NonZeroU32) -> AsyncSecStatsPages<'_> {
3880 self.client
3881 .secstats_pages(&self.engine, &self.market, page_limit)
3882 }
3883
3884 pub async fn candle_borders(&self, security: &SecId) -> Result<Vec<CandleBorder>, MoexError> {
3886 self.client
3887 .candle_borders(&self.engine, &self.market, security)
3888 .await
3889 }
3890
3891 pub fn security<S>(
3893 self,
3894 security: S,
3895 ) -> Result<AsyncOwnedMarketSecurityScope<'a>, ParseSecIdError>
3896 where
3897 S: TryInto<SecId>,
3898 S::Error: Into<ParseSecIdError>,
3899 {
3900 let security = security.try_into().map_err(Into::into)?;
3901 Ok(AsyncOwnedMarketSecurityScope {
3902 client: self.client,
3903 engine: self.engine,
3904 market: self.market,
3905 security,
3906 })
3907 }
3908
3909 pub fn board<B>(self, board: B) -> Result<AsyncOwnedBoardScope<'a>, ParseBoardIdError>
3911 where
3912 B: TryInto<BoardId>,
3913 B::Error: Into<ParseBoardIdError>,
3914 {
3915 let board = board.try_into().map_err(Into::into)?;
3916 Ok(AsyncOwnedBoardScope {
3917 client: self.client,
3918 engine: self.engine,
3919 market: self.market,
3920 board,
3921 })
3922 }
3923}
3924
3925#[derive(Clone)]
3926#[cfg(feature = "async")]
3928pub struct AsyncOwnedMarketSecurityScope<'a> {
3929 client: &'a AsyncMoexClient,
3930 engine: EngineName,
3931 market: MarketName,
3932 security: SecId,
3933}
3934
3935#[cfg(feature = "async")]
3936impl<'a> AsyncOwnedMarketSecurityScope<'a> {
3937 pub fn engine(&self) -> &EngineName {
3939 &self.engine
3940 }
3941
3942 pub fn market(&self) -> &MarketName {
3944 &self.market
3945 }
3946
3947 pub fn security(&self) -> &SecId {
3949 &self.security
3950 }
3951
3952 pub async fn info(&self) -> Result<Option<Security>, MoexError> {
3954 self.client
3955 .market_security_info(&self.engine, &self.market, &self.security)
3956 .await
3957 }
3958
3959 pub async fn candle_borders(&self) -> Result<Vec<CandleBorder>, MoexError> {
3961 self.client
3962 .candle_borders(&self.engine, &self.market, &self.security)
3963 .await
3964 }
3965}
3966
3967#[derive(Clone)]
3968#[cfg(feature = "async")]
3970pub struct AsyncOwnedBoardScope<'a> {
3971 client: &'a AsyncMoexClient,
3972 engine: EngineName,
3973 market: MarketName,
3974 board: BoardId,
3975}
3976
3977#[cfg(feature = "async")]
3978impl<'a> AsyncOwnedBoardScope<'a> {
3979 pub fn engine(&self) -> &EngineName {
3981 &self.engine
3982 }
3983
3984 pub fn market(&self) -> &MarketName {
3986 &self.market
3987 }
3988
3989 pub fn board(&self) -> &BoardId {
3991 &self.board
3992 }
3993
3994 pub async fn securities(&self, page_request: PageRequest) -> Result<Vec<Security>, MoexError> {
3996 self.client
3997 .securities_query(&self.engine, &self.market, &self.board, page_request)
3998 .await
3999 }
4000
4001 pub fn securities_pages(&self, page_limit: NonZeroU32) -> AsyncSecuritiesPages<'_> {
4003 self.client
4004 .securities_pages(&self.engine, &self.market, &self.board, page_limit)
4005 }
4006
4007 pub async fn snapshots(&self) -> Result<Vec<SecuritySnapshot>, MoexError> {
4009 self.client
4010 .board_snapshots(&self.engine, &self.market, &self.board)
4011 .await
4012 }
4013
4014 pub fn security<S>(self, security: S) -> Result<AsyncOwnedSecurityScope<'a>, ParseSecIdError>
4016 where
4017 S: TryInto<SecId>,
4018 S::Error: Into<ParseSecIdError>,
4019 {
4020 let security = security.try_into().map_err(Into::into)?;
4021 Ok(AsyncOwnedSecurityScope {
4022 client: self.client,
4023 engine: self.engine,
4024 market: self.market,
4025 board: self.board,
4026 security,
4027 })
4028 }
4029}
4030
4031#[derive(Clone)]
4032#[cfg(feature = "async")]
4034pub struct AsyncOwnedSecurityResourceScope<'a> {
4035 client: &'a AsyncMoexClient,
4036 security: SecId,
4037}
4038
4039#[cfg(feature = "async")]
4040impl<'a> AsyncOwnedSecurityResourceScope<'a> {
4041 pub fn secid(&self) -> &SecId {
4043 &self.security
4044 }
4045
4046 pub async fn info(&self) -> Result<Option<Security>, MoexError> {
4048 self.client.security_info(&self.security).await
4049 }
4050
4051 pub async fn boards(&self) -> Result<Vec<SecurityBoard>, MoexError> {
4053 self.client.security_boards(&self.security).await
4054 }
4055}
4056
4057#[derive(Clone)]
4058#[cfg(feature = "async")]
4060pub struct AsyncOwnedSecurityScope<'a> {
4061 client: &'a AsyncMoexClient,
4062 engine: EngineName,
4063 market: MarketName,
4064 board: BoardId,
4065 security: SecId,
4066}
4067
4068#[cfg(feature = "async")]
4069impl<'a> AsyncOwnedSecurityScope<'a> {
4070 pub fn security(&self) -> &SecId {
4072 &self.security
4073 }
4074
4075 pub async fn orderbook(&self) -> Result<Vec<OrderbookLevel>, MoexError> {
4077 self.client
4078 .orderbook(&self.engine, &self.market, &self.board, &self.security)
4079 .await
4080 }
4081
4082 #[cfg(feature = "history")]
4083 pub async fn history_dates(&self) -> Result<Option<HistoryDates>, MoexError> {
4085 self.client
4086 .history_dates(&self.engine, &self.market, &self.board, &self.security)
4087 .await
4088 }
4089
4090 #[cfg(feature = "history")]
4091 pub async fn history(
4093 &self,
4094 page_request: PageRequest,
4095 ) -> Result<Vec<HistoryRecord>, MoexError> {
4096 self.client
4097 .history_query(
4098 &self.engine,
4099 &self.market,
4100 &self.board,
4101 &self.security,
4102 page_request,
4103 )
4104 .await
4105 }
4106
4107 #[cfg(feature = "history")]
4108 pub fn history_pages(&self, page_limit: NonZeroU32) -> AsyncHistoryPages<'_> {
4110 self.client.history_pages(
4111 &self.engine,
4112 &self.market,
4113 &self.board,
4114 &self.security,
4115 page_limit,
4116 )
4117 }
4118
4119 pub async fn trades(&self, page_request: PageRequest) -> Result<Vec<Trade>, MoexError> {
4121 self.client
4122 .trades_query(
4123 &self.engine,
4124 &self.market,
4125 &self.board,
4126 &self.security,
4127 page_request,
4128 )
4129 .await
4130 }
4131
4132 pub fn trades_pages(&self, page_limit: NonZeroU32) -> AsyncTradesPages<'_> {
4134 self.client.trades_pages(
4135 &self.engine,
4136 &self.market,
4137 &self.board,
4138 &self.security,
4139 page_limit,
4140 )
4141 }
4142
4143 pub async fn candles(
4145 &self,
4146 query: CandleQuery,
4147 page_request: PageRequest,
4148 ) -> Result<Vec<Candle>, MoexError> {
4149 self.client
4150 .candles_query(
4151 &self.engine,
4152 &self.market,
4153 &self.board,
4154 &self.security,
4155 query,
4156 page_request,
4157 )
4158 .await
4159 }
4160
4161 pub fn candles_pages(
4163 &self,
4164 query: CandleQuery,
4165 page_limit: NonZeroU32,
4166 ) -> AsyncCandlesPages<'_> {
4167 self.client.candles_pages(
4168 &self.engine,
4169 &self.market,
4170 &self.board,
4171 &self.security,
4172 query,
4173 page_limit,
4174 )
4175 }
4176}
4177
4178#[derive(Clone)]
4179#[cfg(feature = "blocking")]
4183pub struct OwnedIndexScope<'a> {
4184 client: &'a BlockingMoexClient,
4185 indexid: IndexId,
4186}
4187
4188#[cfg(feature = "blocking")]
4189impl<'a> OwnedIndexScope<'a> {
4190 pub fn indexid(&self) -> &IndexId {
4192 &self.indexid
4193 }
4194
4195 pub fn analytics(&self, page_request: PageRequest) -> Result<Vec<IndexAnalytics>, MoexError> {
4197 self.client
4198 .index_analytics_query(&self.indexid, page_request)
4199 }
4200
4201 pub fn analytics_pages(&self, page_limit: NonZeroU32) -> IndexAnalyticsPages<'_> {
4203 self.client.index_analytics_pages(&self.indexid, page_limit)
4204 }
4205}
4206
4207#[derive(Clone)]
4208#[cfg(feature = "blocking")]
4210pub struct OwnedEngineScope<'a> {
4211 client: &'a BlockingMoexClient,
4212 engine: EngineName,
4213}
4214
4215#[cfg(feature = "blocking")]
4216impl<'a> OwnedEngineScope<'a> {
4217 pub fn engine(&self) -> &EngineName {
4219 &self.engine
4220 }
4221
4222 pub fn markets(&self) -> Result<Vec<Market>, MoexError> {
4224 self.client.markets(&self.engine)
4225 }
4226
4227 pub fn turnovers(&self) -> Result<Vec<Turnover>, MoexError> {
4229 self.client.engine_turnovers(&self.engine)
4230 }
4231
4232 pub fn market<M>(self, market: M) -> Result<OwnedMarketScope<'a>, ParseMarketNameError>
4234 where
4235 M: TryInto<MarketName>,
4236 M::Error: Into<ParseMarketNameError>,
4237 {
4238 let market = market.try_into().map_err(Into::into)?;
4239 Ok(OwnedMarketScope {
4240 client: self.client,
4241 engine: self.engine,
4242 market,
4243 })
4244 }
4245
4246 pub fn shares(self) -> Result<OwnedMarketScope<'a>, ParseMarketNameError> {
4248 self.market("shares")
4249 }
4250}
4251
4252#[derive(Clone)]
4253#[cfg(feature = "blocking")]
4255pub struct OwnedMarketScope<'a> {
4256 client: &'a BlockingMoexClient,
4257 engine: EngineName,
4258 market: MarketName,
4259}
4260
4261#[cfg(feature = "blocking")]
4262impl<'a> OwnedMarketScope<'a> {
4263 pub fn engine(&self) -> &EngineName {
4265 &self.engine
4266 }
4267
4268 pub fn market(&self) -> &MarketName {
4270 &self.market
4271 }
4272
4273 pub fn boards(&self) -> Result<Vec<Board>, MoexError> {
4275 self.client.boards(&self.engine, &self.market)
4276 }
4277
4278 pub fn securities(&self, page_request: PageRequest) -> Result<Vec<Security>, MoexError> {
4280 self.client
4281 .market_securities_query(&self.engine, &self.market, page_request)
4282 }
4283
4284 pub fn securities_pages(&self, page_limit: NonZeroU32) -> MarketSecuritiesPages<'_> {
4286 self.client
4287 .market_securities_pages(&self.engine, &self.market, page_limit)
4288 }
4289
4290 pub fn orderbook(&self) -> Result<Vec<OrderbookLevel>, MoexError> {
4292 self.client.market_orderbook(&self.engine, &self.market)
4293 }
4294
4295 pub fn trades(&self, page_request: PageRequest) -> Result<Vec<Trade>, MoexError> {
4297 self.client
4298 .market_trades_query(&self.engine, &self.market, page_request)
4299 }
4300
4301 pub fn trades_pages(&self, page_limit: NonZeroU32) -> MarketTradesPages<'_> {
4303 self.client
4304 .market_trades_pages(&self.engine, &self.market, page_limit)
4305 }
4306
4307 pub fn secstats(&self, page_request: PageRequest) -> Result<Vec<SecStat>, MoexError> {
4309 self.client
4310 .secstats_query(&self.engine, &self.market, page_request)
4311 }
4312
4313 pub fn secstats_pages(&self, page_limit: NonZeroU32) -> SecStatsPages<'_> {
4315 self.client
4316 .secstats_pages(&self.engine, &self.market, page_limit)
4317 }
4318
4319 pub fn candle_borders(&self, security: &SecId) -> Result<Vec<CandleBorder>, MoexError> {
4321 self.client
4322 .candle_borders(&self.engine, &self.market, security)
4323 }
4324
4325 pub fn security<S>(self, security: S) -> Result<OwnedMarketSecurityScope<'a>, ParseSecIdError>
4327 where
4328 S: TryInto<SecId>,
4329 S::Error: Into<ParseSecIdError>,
4330 {
4331 let security = security.try_into().map_err(Into::into)?;
4332 Ok(OwnedMarketSecurityScope {
4333 client: self.client,
4334 engine: self.engine,
4335 market: self.market,
4336 security,
4337 })
4338 }
4339
4340 pub fn board<B>(self, board: B) -> Result<OwnedBoardScope<'a>, ParseBoardIdError>
4342 where
4343 B: TryInto<BoardId>,
4344 B::Error: Into<ParseBoardIdError>,
4345 {
4346 let board = board.try_into().map_err(Into::into)?;
4347 Ok(OwnedBoardScope {
4348 client: self.client,
4349 engine: self.engine,
4350 market: self.market,
4351 board,
4352 })
4353 }
4354}
4355
4356#[derive(Clone)]
4357#[cfg(feature = "blocking")]
4359pub struct OwnedMarketSecurityScope<'a> {
4360 client: &'a BlockingMoexClient,
4361 engine: EngineName,
4362 market: MarketName,
4363 security: SecId,
4364}
4365
4366#[cfg(feature = "blocking")]
4367impl<'a> OwnedMarketSecurityScope<'a> {
4368 pub fn engine(&self) -> &EngineName {
4370 &self.engine
4371 }
4372
4373 pub fn market(&self) -> &MarketName {
4375 &self.market
4376 }
4377
4378 pub fn security(&self) -> &SecId {
4380 &self.security
4381 }
4382
4383 pub fn info(&self) -> Result<Option<Security>, MoexError> {
4385 self.client
4386 .market_security_info(&self.engine, &self.market, &self.security)
4387 }
4388
4389 pub fn candle_borders(&self) -> Result<Vec<CandleBorder>, MoexError> {
4391 self.client
4392 .candle_borders(&self.engine, &self.market, &self.security)
4393 }
4394}
4395
4396#[derive(Clone)]
4397#[cfg(feature = "blocking")]
4399pub struct OwnedBoardScope<'a> {
4400 client: &'a BlockingMoexClient,
4401 engine: EngineName,
4402 market: MarketName,
4403 board: BoardId,
4404}
4405
4406#[cfg(feature = "blocking")]
4407impl<'a> OwnedBoardScope<'a> {
4408 pub fn engine(&self) -> &EngineName {
4410 &self.engine
4411 }
4412
4413 pub fn market(&self) -> &MarketName {
4415 &self.market
4416 }
4417
4418 pub fn board(&self) -> &BoardId {
4420 &self.board
4421 }
4422
4423 pub fn securities(&self, page_request: PageRequest) -> Result<Vec<Security>, MoexError> {
4425 self.client
4426 .securities_query(&self.engine, &self.market, &self.board, page_request)
4427 }
4428
4429 pub fn securities_pages(&self, page_limit: NonZeroU32) -> SecuritiesPages<'_> {
4431 self.client
4432 .securities_pages(&self.engine, &self.market, &self.board, page_limit)
4433 }
4434
4435 pub fn snapshots(&self) -> Result<Vec<SecuritySnapshot>, MoexError> {
4437 self.client
4438 .board_snapshots(&self.engine, &self.market, &self.board)
4439 }
4440
4441 pub fn security<S>(self, security: S) -> Result<OwnedSecurityScope<'a>, ParseSecIdError>
4443 where
4444 S: TryInto<SecId>,
4445 S::Error: Into<ParseSecIdError>,
4446 {
4447 let security = security.try_into().map_err(Into::into)?;
4448 Ok(OwnedSecurityScope {
4449 client: self.client,
4450 engine: self.engine,
4451 market: self.market,
4452 board: self.board,
4453 security,
4454 })
4455 }
4456}
4457
4458#[derive(Clone)]
4459#[cfg(feature = "blocking")]
4461pub struct OwnedSecurityResourceScope<'a> {
4462 client: &'a BlockingMoexClient,
4463 security: SecId,
4464}
4465
4466#[cfg(feature = "blocking")]
4467impl<'a> OwnedSecurityResourceScope<'a> {
4468 pub fn secid(&self) -> &SecId {
4470 &self.security
4471 }
4472
4473 pub fn info(&self) -> Result<Option<Security>, MoexError> {
4475 self.client.security_info(&self.security)
4476 }
4477
4478 pub fn boards(&self) -> Result<Vec<SecurityBoard>, MoexError> {
4480 self.client.security_boards(&self.security)
4481 }
4482}
4483
4484#[derive(Clone)]
4485#[cfg(feature = "blocking")]
4487pub struct OwnedSecurityScope<'a> {
4488 client: &'a BlockingMoexClient,
4489 engine: EngineName,
4490 market: MarketName,
4491 board: BoardId,
4492 security: SecId,
4493}
4494
4495#[cfg(feature = "blocking")]
4496impl<'a> OwnedSecurityScope<'a> {
4497 pub fn security(&self) -> &SecId {
4499 &self.security
4500 }
4501
4502 pub fn orderbook(&self) -> Result<Vec<OrderbookLevel>, MoexError> {
4504 self.client
4505 .orderbook(&self.engine, &self.market, &self.board, &self.security)
4506 }
4507
4508 #[cfg(feature = "history")]
4509 pub fn history_dates(&self) -> Result<Option<HistoryDates>, MoexError> {
4511 self.client
4512 .history_dates(&self.engine, &self.market, &self.board, &self.security)
4513 }
4514
4515 #[cfg(feature = "history")]
4516 pub fn history(&self, page_request: PageRequest) -> Result<Vec<HistoryRecord>, MoexError> {
4518 self.client.history_query(
4519 &self.engine,
4520 &self.market,
4521 &self.board,
4522 &self.security,
4523 page_request,
4524 )
4525 }
4526
4527 #[cfg(feature = "history")]
4528 pub fn history_pages(&self, page_limit: NonZeroU32) -> HistoryPages<'_> {
4530 self.client.history_pages(
4531 &self.engine,
4532 &self.market,
4533 &self.board,
4534 &self.security,
4535 page_limit,
4536 )
4537 }
4538
4539 pub fn candle_borders(&self) -> Result<Vec<CandleBorder>, MoexError> {
4541 self.client
4542 .candle_borders(&self.engine, &self.market, &self.security)
4543 }
4544
4545 pub fn trades(&self, page_request: PageRequest) -> Result<Vec<Trade>, MoexError> {
4547 self.client.trades_query(
4548 &self.engine,
4549 &self.market,
4550 &self.board,
4551 &self.security,
4552 page_request,
4553 )
4554 }
4555
4556 pub fn trades_pages(&self, page_limit: NonZeroU32) -> TradesPages<'_> {
4558 self.client.trades_pages(
4559 &self.engine,
4560 &self.market,
4561 &self.board,
4562 &self.security,
4563 page_limit,
4564 )
4565 }
4566
4567 pub fn candles(
4569 &self,
4570 query: CandleQuery,
4571 page_request: PageRequest,
4572 ) -> Result<Vec<Candle>, MoexError> {
4573 self.client.candles_query(
4574 &self.engine,
4575 &self.market,
4576 &self.board,
4577 &self.security,
4578 query,
4579 page_request,
4580 )
4581 }
4582
4583 pub fn candles_pages(&self, query: CandleQuery, page_limit: NonZeroU32) -> CandlesPages<'_> {
4585 self.client.candles_pages(
4586 &self.engine,
4587 &self.market,
4588 &self.board,
4589 &self.security,
4590 query,
4591 page_limit,
4592 )
4593 }
4594}
4595
4596struct PaginationTracker<K> {
4597 endpoint: Box<str>,
4598 page_limit: NonZeroU32,
4599 repeat_page_policy: RepeatPagePolicy,
4600 start: u32,
4601 first_key_on_previous_page: Option<K>,
4602 finished: bool,
4603}
4604
4605impl<K> PaginationTracker<K> {
4606 fn new(
4607 endpoint: impl Into<String>,
4608 page_limit: NonZeroU32,
4609 repeat_page_policy: RepeatPagePolicy,
4610 ) -> Self {
4611 Self {
4612 endpoint: endpoint.into().into_boxed_str(),
4613 page_limit,
4614 repeat_page_policy,
4615 start: 0,
4616 first_key_on_previous_page: None,
4617 finished: false,
4618 }
4619 }
4620
4621 fn next_page_request(&self) -> Option<Pagination> {
4622 if self.finished {
4623 return None;
4624 }
4625 Some(Pagination {
4626 start: Some(self.start),
4627 limit: Some(self.page_limit),
4628 })
4629 }
4630}
4631
4632impl<K> PaginationTracker<K>
4633where
4634 K: Eq,
4635{
4636 fn advance(
4637 &mut self,
4638 page_len: usize,
4639 first_key_on_page: Option<K>,
4640 ) -> Result<PaginationAdvance, MoexError> {
4641 let page_limit = self.page_limit.get();
4642
4643 if page_len == 0 {
4644 self.finished = true;
4645 return Ok(PaginationAdvance::EndOfPages);
4646 }
4647
4648 if let (Some(prev), Some(current)) = (&self.first_key_on_previous_page, &first_key_on_page)
4649 && prev == current
4650 {
4651 return match self.repeat_page_policy {
4652 RepeatPagePolicy::Error => Err(MoexError::PaginationStuck {
4653 endpoint: self.endpoint.clone(),
4654 start: self.start,
4655 limit: page_limit,
4656 }),
4657 };
4658 }
4659
4660 self.first_key_on_previous_page = first_key_on_page;
4661
4662 if (page_len as u128) < u128::from(page_limit) {
4663 self.finished = true;
4664 return Ok(PaginationAdvance::YieldPage);
4665 }
4666
4667 self.start =
4668 self.start
4669 .checked_add(page_limit)
4670 .ok_or_else(|| MoexError::PaginationOverflow {
4671 endpoint: self.endpoint.clone(),
4672 start: self.start,
4673 limit: page_limit,
4674 })?;
4675
4676 Ok(PaginationAdvance::YieldPage)
4677 }
4678}
4679
4680#[cfg(any(feature = "blocking", feature = "async"))]
4681fn resolve_base_url_or_default(base_url: Option<Url>) -> Result<Url, MoexError> {
4682 match base_url {
4683 Some(base_url) => Ok(base_url),
4684 None => Url::parse(BASE_URL).map_err(|source| MoexError::InvalidBaseUrl {
4685 base_url: BASE_URL,
4686 reason: source.to_string(),
4687 }),
4688 }
4689}
4690
4691#[cfg(feature = "blocking")]
4692fn resolve_blocking_http_client(
4693 client: Option<Client>,
4694 http_client: ClientBuilder,
4695) -> Result<Client, MoexError> {
4696 match client {
4697 Some(client) => Ok(client),
4698 None => http_client
4699 .build()
4700 .map_err(|source| MoexError::BuildHttpClient { source }),
4701 }
4702}
4703
4704#[cfg(feature = "async")]
4705fn resolve_async_http_client(
4706 client: Option<reqwest::Client>,
4707 http_client: reqwest::ClientBuilder,
4708) -> Result<reqwest::Client, MoexError> {
4709 match client {
4710 Some(client) => Ok(client),
4711 None => http_client
4712 .build()
4713 .map_err(|source| MoexError::BuildHttpClient { source }),
4714 }
4715}
4716
4717#[cfg(feature = "async")]
4718fn resolve_async_rate_limit_state(
4719 rate_limit: Option<RateLimit>,
4720 rate_limit_sleep: Option<AsyncRateLimitSleep>,
4721) -> Result<Option<AsyncRateLimitState>, MoexError> {
4722 match rate_limit {
4723 Some(limit) => {
4724 let sleep = rate_limit_sleep.ok_or(MoexError::MissingAsyncRateLimitSleep)?;
4725 Ok(Some(AsyncRateLimitState {
4726 limiter: Mutex::new(RateLimiter::new(limit)),
4727 sleep,
4728 }))
4729 }
4730 None => Ok(None),
4731 }
4732}
4733
4734#[cfg(any(feature = "blocking", feature = "async"))]
4735fn lock_rate_limiter(limiter: &Mutex<RateLimiter>) -> std::sync::MutexGuard<'_, RateLimiter> {
4736 match limiter.lock() {
4737 Ok(guard) => guard,
4738 Err(poisoned) => poisoned.into_inner(),
4739 }
4740}
4741
4742#[cfg(any(feature = "blocking", feature = "async"))]
4743fn reserve_rate_limit_delay(limiter: &Mutex<RateLimiter>) -> Duration {
4744 let mut limiter = lock_rate_limiter(limiter);
4745 limiter.reserve_delay()
4746}
4747
4748#[cfg(feature = "blocking")]
4749fn next_page_blocking<T, K, F, G>(
4750 pagination: &mut PaginationTracker<K>,
4751 fetch_page: F,
4752 first_key_of: G,
4753) -> Result<Option<Vec<T>>, MoexError>
4754where
4755 K: Eq,
4756 F: FnOnce(Pagination) -> Result<Vec<T>, MoexError>,
4757 G: Fn(&T) -> K,
4758{
4759 let Some(paging) = pagination.next_page_request() else {
4760 return Ok(None);
4761 };
4762 let page = fetch_page(paging)?;
4763 let first_key_on_page = page.first().map(first_key_of);
4764 match pagination.advance(page.len(), first_key_on_page)? {
4765 PaginationAdvance::YieldPage => Ok(Some(page)),
4766 PaginationAdvance::EndOfPages => Ok(None),
4767 }
4768}
4769
4770#[cfg(feature = "blocking")]
4771fn collect_pages_blocking<T, F>(mut next_page: F) -> Result<Vec<T>, MoexError>
4772where
4773 F: FnMut() -> Result<Option<Vec<T>>, MoexError>,
4774{
4775 let mut items = Vec::new();
4776 while let Some(page) = next_page()? {
4777 items.extend(page);
4778 }
4779 Ok(items)
4780}
4781
4782fn apply_iss_request_options(query: &mut Vec<(Box<str>, Box<str>)>, options: IssRequestOptions) {
4783 if let Some(metadata) = options.metadata_value() {
4784 query.push((ISS_META_PARAM.into(), metadata.as_query_value().into()));
4785 }
4786 if let Some(data) = options.data_value() {
4787 query.push((ISS_DATA_PARAM.into(), data.as_query_value().into()));
4788 }
4789 if let Some(version) = options.version_value() {
4790 query.push((ISS_VERSION_PARAM.into(), version.as_query_value().into()));
4791 }
4792 if let Some(json) = options.json_value() {
4793 query.push((ISS_JSON_PARAM.into(), json.into()));
4794 }
4795}
4796
4797pub(super) fn normalize_raw_endpoint_path(path: Option<&str>) -> Result<Box<str>, MoexError> {
4802 let raw = path.ok_or(MoexError::MissingRawPath)?;
4803 let trimmed = raw.trim();
4804 if trimmed.is_empty() {
4805 return Err(MoexError::InvalidRawPath {
4806 path: raw.to_owned().into_boxed_str(),
4807 reason: "path must not be empty".into(),
4808 });
4809 }
4810 if trimmed.contains('?') {
4811 return Err(MoexError::InvalidRawPath {
4812 path: raw.to_owned().into_boxed_str(),
4813 reason: "query string is not allowed in path; use .param(...)".into(),
4814 });
4815 }
4816
4817 let without_slash = trimmed.trim_start_matches('/');
4819 let endpoint = without_slash
4820 .strip_prefix("iss/")
4821 .unwrap_or(without_slash)
4822 .trim();
4823
4824 if endpoint.is_empty() {
4825 return Err(MoexError::InvalidRawPath {
4826 path: raw.to_owned().into_boxed_str(),
4827 reason: "endpoint path is empty after normalization".into(),
4828 });
4829 }
4830
4831 if endpoint.ends_with(".json") {
4832 return Ok(endpoint.to_owned().into_boxed_str());
4833 }
4834
4835 let mut normalized = endpoint.to_owned();
4836 normalized.push_str(".json");
4837 Ok(normalized.into_boxed_str())
4838}
4839
4840pub(super) fn optional_single_security(
4842 endpoint: &str,
4843 mut securities: Vec<Security>,
4844) -> Result<Option<Security>, MoexError> {
4845 if securities.len() > 1 {
4846 return Err(MoexError::UnexpectedSecurityRows {
4847 endpoint: endpoint.to_owned().into_boxed_str(),
4848 row_count: securities.len(),
4849 });
4850 }
4851 Ok(securities.pop())
4852}
4853
4854#[cfg(feature = "history")]
4855pub(super) fn optional_single_history_dates(
4857 endpoint: &str,
4858 mut dates: Vec<HistoryDates>,
4859) -> Result<Option<HistoryDates>, MoexError> {
4860 if dates.len() > 1 {
4861 return Err(MoexError::UnexpectedHistoryDatesRows {
4862 endpoint: endpoint.to_owned().into_boxed_str(),
4863 row_count: dates.len(),
4864 });
4865 }
4866 Ok(dates.pop())
4867}
4868
4869pub(super) fn append_candle_query_to_url(endpoint_url: &mut Url, candle_query: CandleQuery) {
4871 let mut query_pairs = endpoint_url.query_pairs_mut();
4872 if let Some(from) = candle_query.from() {
4873 let from = from.format("%Y-%m-%d %H:%M:%S").to_string();
4874 query_pairs.append_pair(FROM_PARAM, &from);
4875 }
4876 if let Some(till) = candle_query.till() {
4877 let till = till.format("%Y-%m-%d %H:%M:%S").to_string();
4878 query_pairs.append_pair(TILL_PARAM, &till);
4879 }
4880 if let Some(interval) = candle_query.interval() {
4881 query_pairs.append_pair(INTERVAL_PARAM, interval.as_str());
4882 }
4883}
4884
4885pub(super) fn append_pagination_to_url(endpoint_url: &mut Url, pagination: Pagination) {
4887 if pagination.start.is_none() && pagination.limit.is_none() {
4888 return;
4889 }
4890
4891 let mut query = endpoint_url.query_pairs_mut();
4892 if let Some(start) = pagination.start {
4893 let start = start.to_string();
4894 query.append_pair(START_PARAM, &start);
4895 }
4896 if let Some(limit) = pagination.limit {
4897 let limit = limit.get().to_string();
4898 query.append_pair(LIMIT_PARAM, &limit);
4899 }
4900}
4901
4902pub(super) fn looks_like_json_payload(content_type: Option<&str>, payload: &str) -> bool {
4904 if content_type.is_some_and(contains_json_token_ascii_case_insensitive) {
4905 return true;
4906 }
4907
4908 let trimmed = payload.trim_start();
4909 trimmed.starts_with('{') || trimmed.starts_with('[')
4910}
4911
4912fn contains_json_token_ascii_case_insensitive(content_type: &str) -> bool {
4913 content_type
4914 .as_bytes()
4915 .windows(4)
4916 .any(|window| window.eq_ignore_ascii_case(b"json"))
4917}
4918
4919pub(super) fn truncate_prefix(payload: &str, max_chars: usize) -> Box<str> {
4921 payload
4922 .chars()
4923 .take(max_chars)
4924 .collect::<String>()
4925 .into_boxed_str()
4926}
4927#[cfg(feature = "async")]
4928async fn next_page_async<T, K, F, Fut, G>(
4929 pagination: &mut PaginationTracker<K>,
4930 fetch_page: F,
4931 first_key_of: G,
4932) -> Result<Option<Vec<T>>, MoexError>
4933where
4934 K: Eq,
4935 F: FnOnce(Pagination) -> Fut,
4936 Fut: std::future::Future<Output = Result<Vec<T>, MoexError>>,
4937 G: Fn(&T) -> K,
4938{
4939 let Some(paging) = pagination.next_page_request() else {
4940 return Ok(None);
4941 };
4942 let page = fetch_page(paging).await?;
4943 let first_key_on_page = page.first().map(first_key_of);
4944 match pagination.advance(page.len(), first_key_on_page)? {
4945 PaginationAdvance::YieldPage => Ok(Some(page)),
4946 PaginationAdvance::EndOfPages => Ok(None),
4947 }
4948}