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