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}