matrix_sdk/config/
request.rs

1// Copyright 2021 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    fmt::{self, Debug},
17    num::NonZeroUsize,
18    time::Duration,
19};
20
21use matrix_sdk_common::debug::DebugStructExt;
22
23use crate::http_client::DEFAULT_REQUEST_TIMEOUT;
24
25/// Configuration for requests the `Client` makes.
26///
27/// This sets how often and for how long a request should be repeated. As well
28/// as how long a successful request is allowed to take.
29///
30/// By default requests are retried indefinitely and use no timeout.
31///
32/// # Examples
33///
34/// ```
35/// use matrix_sdk::config::RequestConfig;
36/// use std::time::Duration;
37///
38/// // This sets makes requests fail after a single send request and sets the timeout to 30s
39/// let request_config = RequestConfig::new()
40///     .disable_retry()
41///     .timeout(Duration::from_secs(30));
42/// ```
43#[derive(Copy, Clone)]
44pub struct RequestConfig {
45    pub(crate) timeout: Option<Duration>,
46    pub(crate) read_timeout: Option<Duration>,
47    pub(crate) retry_limit: Option<usize>,
48    pub(crate) max_retry_time: Option<Duration>,
49    pub(crate) max_concurrent_requests: Option<NonZeroUsize>,
50    pub(crate) force_auth: bool,
51    pub(crate) skip_auth: bool,
52}
53
54#[cfg(not(tarpaulin_include))]
55impl Debug for RequestConfig {
56    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
57        let Self {
58            timeout,
59            read_timeout,
60            retry_limit,
61            max_retry_time: retry_timeout,
62            force_auth,
63            max_concurrent_requests,
64            skip_auth: skip_optional_auth,
65        } = self;
66
67        let mut res = fmt.debug_struct("RequestConfig");
68        res.field("timeout", timeout)
69            .maybe_field("read_timeout", read_timeout)
70            .maybe_field("retry_limit", retry_limit)
71            .maybe_field("max_retry_time", retry_timeout)
72            .maybe_field("max_concurrent_requests", max_concurrent_requests);
73
74        if *force_auth {
75            res.field("force_auth", &true);
76        }
77
78        if *skip_optional_auth {
79            res.field("skip_optional_auth", &true);
80        }
81
82        res.finish()
83    }
84}
85
86impl Default for RequestConfig {
87    fn default() -> Self {
88        Self {
89            timeout: Some(DEFAULT_REQUEST_TIMEOUT),
90            read_timeout: None,
91            retry_limit: Default::default(),
92            max_retry_time: Default::default(),
93            max_concurrent_requests: Default::default(),
94            force_auth: false,
95            skip_auth: false,
96        }
97    }
98}
99
100impl RequestConfig {
101    /// Create a new default `RequestConfig`.
102    #[must_use]
103    pub fn new() -> Self {
104        Default::default()
105    }
106
107    /// Create a new `RequestConfig` with default values, except the retry limit
108    /// which is set to 3.
109    #[must_use]
110    pub fn short_retry() -> Self {
111        Self::default().retry_limit(3)
112    }
113
114    /// This is a convince method to disable the retries of a request. Setting
115    /// the `retry_limit` to `0` has the same effect.
116    #[must_use]
117    pub fn disable_retry(mut self) -> Self {
118        self.retry_limit = Some(0);
119        self
120    }
121
122    /// The number of times a request should be retried. The default is no
123    /// limit.
124    #[must_use]
125    pub fn retry_limit(mut self, retry_limit: usize) -> Self {
126        self.retry_limit = Some(retry_limit);
127        self
128    }
129
130    /// The total limit of request that are pending or run concurrently.
131    /// Any additional request beyond that number will be waiting until another
132    /// concurrent requests finished. Requests are queued fairly.
133    #[must_use]
134    pub fn max_concurrent_requests(mut self, limit: Option<NonZeroUsize>) -> Self {
135        self.max_concurrent_requests = limit;
136        self
137    }
138
139    /// Set the timeout duration for all HTTP requests.
140    #[must_use]
141    pub fn timeout(mut self, timeout: impl Into<Option<Duration>>) -> Self {
142        self.timeout = timeout.into();
143        self
144    }
145
146    /// Set the read timeout duration for all HTTP requests.
147    ///
148    /// The timeout applies to each read operation, and resets after a
149    /// successful read. This is more appropriate for detecting stalled
150    /// connections when the size isn’t known beforehand.
151    ///
152    /// **IMPORTANT**: note this value can only be applied when the HTTP client
153    /// is instantiated, it won't have any effect on a per-request basis.
154    #[must_use]
155    pub fn read_timeout(mut self, timeout: impl Into<Option<Duration>>) -> Self {
156        self.read_timeout = timeout.into();
157        self
158    }
159
160    /// Set a time limit for how long a request should be retried. The default
161    /// is that there isn't a limit, meaning requests are retried forever.
162    ///
163    /// This is a time-based variant of the [`RequestConfig::retry_limit`]
164    /// method.
165    #[must_use]
166    pub fn max_retry_time(mut self, retry_timeout: Duration) -> Self {
167        self.max_retry_time = Some(retry_timeout);
168        self
169    }
170
171    /// Force sending authorization even if the endpoint does not require it.
172    /// Default is only sending authorization if it is required.
173    #[must_use]
174    pub fn force_auth(mut self) -> Self {
175        self.force_auth = true;
176        self
177    }
178
179    /// Skip sending authorization headers even if the endpoint requires it.
180    ///
181    /// Default is to send authorization headers if the endpoint accepts or
182    /// requires it.
183    ///
184    /// This is useful for endpoints that may optionally accept authorization
185    /// but don't require it.
186    ///
187    /// Note: [`RequestConfig::force_auth`] takes precedence. If force auth is
188    /// set, this value will be ignored.
189    #[must_use]
190    pub fn skip_auth(mut self) -> Self {
191        self.skip_auth = true;
192        self
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use std::time::Duration;
199
200    use super::RequestConfig;
201
202    #[test]
203    fn smoketest() {
204        let cfg = RequestConfig::new()
205            .force_auth()
206            .max_retry_time(Duration::from_secs(32))
207            .retry_limit(4)
208            .timeout(Duration::from_secs(600))
209            .read_timeout(Duration::from_secs(10));
210
211        assert!(cfg.force_auth);
212        assert_eq!(cfg.retry_limit, Some(4));
213        assert_eq!(cfg.max_retry_time, Some(Duration::from_secs(32)));
214        assert_eq!(cfg.timeout, Some(Duration::from_secs(600)));
215        assert_eq!(cfg.read_timeout, Some(Duration::from_secs(10)));
216    }
217
218    #[test]
219    fn testing_retry_settings() {
220        let mut cfg = RequestConfig::new();
221        assert_eq!(cfg.retry_limit, None);
222        cfg = cfg.retry_limit(10);
223        assert_eq!(cfg.retry_limit, Some(10));
224        cfg = cfg.disable_retry();
225        assert_eq!(cfg.retry_limit, Some(0));
226
227        let cfg = RequestConfig::short_retry();
228        assert_eq!(cfg.retry_limit, Some(3));
229    }
230}