invest_api_rust_sdk/
lib.rs

1use paste::paste;
2pub(crate) mod prelude;
3use contracts::instruments_service_client::InstrumentsServiceClient;
4use contracts::market_data_service_client::MarketDataServiceClient;
5use contracts::market_data_stream_service_client::MarketDataStreamServiceClient;
6use contracts::operations_service_client::OperationsServiceClient;
7use contracts::operations_stream_service_client::OperationsStreamServiceClient;
8use contracts::orders_service_client::OrdersServiceClient;
9use contracts::orders_stream_service_client::OrdersStreamServiceClient;
10use contracts::sandbox_service_client::SandboxServiceClient;
11use contracts::signal_service_client::SignalServiceClient;
12use contracts::stop_orders_service_client::StopOrdersServiceClient;
13use contracts::users_service_client::UsersServiceClient;
14pub use prelude::contracts;
15use std::time::Duration;
16use tonic::metadata::{MetadataMap, MetadataValue};
17use tonic::service::interceptor::InterceptedService;
18use tonic::transport::{Channel, ClientTlsConfig, Endpoint};
19use tonic::Request;
20
21/// [Production endpoint](https://russianinvestments.github.io/investAPI/)
22pub const PROD_ENDPOINT: &'static str = "https://invest-public-api.tinkoff.ru:443";
23/// [Sandbox endpoint](https://russianinvestments.github.io/investAPI/)
24pub const SANDBOX_ENDPOINT: &'static str = "https://sandbox-invest-public-api.tinkoff.ru:443";
25const DEFAULT_USER_AGENT: &'static str = "sillent/invest-api-rust-sdk";
26
27/// [ServiceFactory] builder that aggregate parameters for future gRPC-channel and gRPC-metadata
28pub struct ServiceFactoryBuilder {
29    base_url: Option<String>,
30    token: Option<String>,
31    user_agent: Option<String>,
32    headers: Vec<(&'static str, String)>,
33    rate_limit: Option<(u64, Duration)>,
34    timeout: Option<Duration>,
35    connect_timeout: Option<Duration>,
36    tcp_keepalive: Option<Duration>,
37}
38
39impl ServiceFactoryBuilder {
40    /// Create empty [ServiceFactoryBuilder]
41    pub fn new() -> Self {
42        Self {
43            base_url: None,
44            token: None,
45            user_agent: None,
46            headers: vec![],
47            rate_limit: None,
48            timeout: None,
49            connect_timeout: None,
50            tcp_keepalive: None,
51        }
52    }
53
54    /// Set a base URL if needed to change endpoint address
55    pub fn base_url<S: Into<String>>(self, base_url: S) -> Self {
56        Self {
57            base_url: Some(base_url.into()),
58            ..self
59        }
60    }
61
62    /// Set a token for authorization
63    pub fn token<S: Into<String>>(self, token: S) -> Self {
64        Self {
65            token: Some(token.into()),
66            ..self
67        }
68    }
69
70    /// Set User-Agent http2 layer header
71    pub fn user_agent<S: Into<String>>(self, user_agent: S) -> Self {
72        Self {
73            user_agent: Some(user_agent.into()),
74            ..self
75        }
76    }
77
78    /// Set a headers that converted into gRPC metadata
79    pub fn headers(self, headers: Vec<(&'static str, String)>) -> Self {
80        Self { headers, ..self }
81    }
82
83    /// Set `tonic` rate_limit for Endpoint
84    pub fn rate_limit(self, rate_limit: (u64, Duration)) -> Self {
85        Self {
86            rate_limit: Some(rate_limit),
87            ..self
88        }
89    }
90
91    /// Set `tonic` timeout for Endpoint
92    pub fn timeout(self, timeout: Duration) -> Self {
93        Self {
94            timeout: Some(timeout),
95            ..self
96        }
97    }
98
99    /// Set `tonic` connect_timeout for Endpoint
100    pub fn connect_timeout(self, timeout: Duration) -> Self {
101        Self {
102            connect_timeout: Some(timeout),
103            ..self
104        }
105    }
106
107    /// Set `tonic` tcp_keepalive for Endpoint
108    pub fn tcp_keepalive(self, tcp_keepalive: Duration) -> Self {
109        Self {
110            tcp_keepalive: Some(tcp_keepalive),
111            ..self
112        }
113    }
114
115    /// Bulid the [ServiceFactory]
116    pub fn build(self) -> Result<ServiceFactory, Box<dyn std::error::Error>> {
117        let ServiceFactoryBuilder {
118            base_url,
119            token,
120            user_agent,
121            headers,
122            rate_limit,
123            timeout,
124            connect_timeout,
125            tcp_keepalive,
126        } = self;
127        let base_url = match base_url {
128            Some(base_url) => base_url,
129            None => PROD_ENDPOINT.to_owned(),
130        };
131        let tls_config = ClientTlsConfig::new().with_native_roots();
132        let mut metadata: MetadataMap = MetadataMap::new();
133        for header in headers {
134            metadata.insert(header.0, header.1.parse()?);
135        }
136        if let Some(token) = token {
137            let token: MetadataValue<_> = format!("Bearer {}", token).parse()?;
138            metadata.insert("authorization", token);
139        }
140        let user_agent = match user_agent {
141            Some(user_agent) => user_agent,
142            None => DEFAULT_USER_AGENT.to_owned(),
143        };
144        let mut endpoint = Endpoint::from_shared(base_url)?
145            .tls_config(tls_config)?
146            .user_agent(user_agent)?
147            .tcp_keepalive(tcp_keepalive);
148        if let Some(rate_limit) = rate_limit {
149            endpoint = endpoint.rate_limit(rate_limit.0, rate_limit.1);
150        }
151        if let Some(timeout) = timeout {
152            endpoint = endpoint.timeout(timeout);
153        }
154        if let Some(connect_timeout) = connect_timeout {
155            endpoint = endpoint.connect_timeout(connect_timeout);
156        }
157        let channel = endpoint.connect_lazy();
158        Ok(ServiceFactory { channel, metadata })
159    }
160}
161
162/// Factory that create gRPC client services from .proto specs of [invest API](https://russianinvestments.github.io/investAPI/)
163#[derive(Clone)]
164pub struct ServiceFactory {
165    metadata: MetadataMap,
166    channel: Channel,
167}
168
169impl ServiceFactory {
170    /// Create ServiceFactoryBuilder
171    pub fn builder() -> ServiceFactoryBuilder {
172        ServiceFactoryBuilder::new()
173    }
174
175    service_gen!(users_service:UsersServiceClient);
176    service_gen!(orders_service:OrdersServiceClient);
177    service_gen!(orders_stream_service:OrdersStreamServiceClient);
178    service_gen!(stop_orders_service:StopOrdersServiceClient);
179    service_gen!(operations_service:OperationsServiceClient);
180    service_gen!(operations_stream_service:OperationsStreamServiceClient);
181    service_gen!(instruments_service:InstrumentsServiceClient);
182    service_gen!(marketdata_service:MarketDataServiceClient);
183    service_gen!(marketdata_stream_service:MarketDataStreamServiceClient);
184    service_gen!(sandbox_service:SandboxServiceClient);
185    service_gen!(signal_service:SignalServiceClient);
186}
187
188#[macro_export]
189macro_rules! service_gen {
190    ($name:ident:$service:ident) => {
191        /// Create generated .proto services of [invest API](https://russianinvestments.github.io/investAPI/)
192        pub fn $name(
193            &self,
194        ) -> $service<
195            InterceptedService<
196                Channel,
197                impl FnMut(Request<()>) -> Result<Request<()>, tonic::Status>,
198            >,
199        > {
200            let channel = self.channel.clone();
201            let metadata = self.metadata.clone();
202            $service::with_interceptor(channel, move |mut req: Request<()>| {
203                metadata
204                    .iter()
205                    .map(|x| match x {
206                        tonic::metadata::KeyAndValueRef::Ascii(k, v) => {
207                            req.metadata_mut().insert(k, v.into());
208                        }
209                        tonic::metadata::KeyAndValueRef::Binary(k, v) => {
210                            req.metadata_mut().insert_bin(k, v.into());
211                        }
212                    })
213                    .count();
214                Ok(req)
215            })
216        }
217
218        paste! {
219            /// Create generated .proto services of [invest API](https://russianinvestments.github.io/investAPI/) with interceptor
220            pub fn [<$name _with_interceptor>](&self, mut interceptor: impl FnMut(Request<()>)-> Result<Request<()>, tonic::Status>) -> $service<
221            InterceptedService<
222                Channel,
223                impl FnMut(Request<()>) -> Result<Request<()>, tonic::Status>,
224            >,
225        > {
226                let channel = self.channel.clone();
227                let metadata = self.metadata.clone();
228                $service::with_interceptor(channel, move |mut req: Request<()>| {
229                    metadata
230                    .iter()
231                    .map(|x| match x {
232                        tonic::metadata::KeyAndValueRef::Ascii(k, v) => {
233                            req.metadata_mut().insert(k, v.into());
234                        }
235                        tonic::metadata::KeyAndValueRef::Binary(k, v) => {
236                            req.metadata_mut().insert_bin(k, v.into());
237                        }
238                    })
239                    .count();
240                    interceptor(req)
241                })
242            }
243        }
244    };
245}