Skip to main content

brokapi_finam/
limits.rs

1//! Rate limits of the Finam connectors, loaded from the bundled `limits.toml`.
2//!
3//! The only number Finam documents is 200 requests per minute for the whole API. The bundled buckets sit
4//! below it (global 200 per minute, orders 60, cancel 100, market data 120, reference 100, reports
5//! 20). Whether the quota is per user or per secret is not stated by the broker. The file reads it as
6//! PER USER, so create ONE [`BrokerLimiter`](brokapi_runtime::BrokerLimiter) per Finam user and give it to
7//! every connector. This reading is not verified against the live API.
8//!
9//! A request over the limit is refused locally with `Error::RateLimited` and never sent.
10//!
11//! # Examples
12//!
13//! ```
14//! use brokapi_finam::limits::default_limits;
15//! use brokapi_runtime::BrokerLimiter;
16//!
17//! let limits = default_limits();
18//! // Every proto method name has a route. Unknown names spend the "*" default route.
19//! assert!(limits.route.contains_key("PlaceOrder"));
20//! let limiter = BrokerLimiter::new(&limits);
21//! // Hand `limiter.clone()` to every connector of this Finam user.
22//! assert!(limiter.acquire("GetOrders", std::time::Instant::now()).is_ok());
23//! ```
24
25use brokapi_core::{ConfigError, Error};
26use brokapi_runtime::BrokerLimits;
27
28/// Text of the bundled limits file. Written from the broker documentation, see the file header for sources.
29pub const DEFAULT_LIMITS_TOML: &str = include_str!("../limits.toml");
30
31/// Parses the `[finam]` section of a limits file through `BrokerLimits::from_toml_section`.
32///
33/// Use it to load your own limits, for example when the broker raises its quota.
34///
35/// # Errors
36///
37/// [`Error::Config`] when the text is not valid TOML or has no `[finam]` section. A section of
38/// another broker is not accepted.
39///
40/// # Examples
41///
42/// ```
43/// use brokapi_finam::limits::{DEFAULT_LIMITS_TOML, parse_limits};
44///
45/// assert!(parse_limits(DEFAULT_LIMITS_TOML).is_ok());
46/// assert!(parse_limits("[bcs.buckets.global]\nrate = 1").is_err());
47/// ```
48pub fn parse_limits(raw: &str) -> Result<BrokerLimits, Error> {
49    BrokerLimits::from_toml_section(raw, "finam").map_err(|e: ConfigError| Error::Config(e))
50}
51
52/// The bundled limits, ready for [`BrokerLimiter::new`](brokapi_runtime::BrokerLimiter::new).
53///
54/// # Panics
55///
56/// Never in a correct build: the bundled file is checked by a test.
57pub fn default_limits() -> BrokerLimits {
58    parse_limits(DEFAULT_LIMITS_TOML).expect("bundled finam_limits.toml is valid")
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn bundled_limits_parse_and_every_route_points_to_a_bucket() {
67        let l = default_limits();
68        assert!(!l.route.is_empty());
69        for method in l.route.keys() {
70            for b in l.buckets_for(method) {
71                assert!(l.buckets.contains_key(&b), "{method}: unknown bucket {b}");
72            }
73        }
74    }
75
76    #[test]
77    fn other_broker_section_is_not_accepted() {
78        assert!(parse_limits("[bcs.buckets.global]\nrate=1").is_err());
79    }
80}