1use std::fmt;
4use std::time::Duration;
5
6pub const DEFAULT_MAX_LISTING_ENTRIES: usize = 4096;
8pub const MAX_LISTING_RESPONSE_BYTES: usize = 10 * 1024 * 1024;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct LimitsError {
13 pub field: &'static str,
15 pub value: String,
17 pub constraint: String,
19}
20
21impl fmt::Display for LimitsError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(
24 f,
25 "{} must be {}: got {}",
26 self.field, self.constraint, self.value
27 )
28 }
29}
30
31impl std::error::Error for LimitsError {}
32
33#[derive(Debug, Clone)]
34#[must_use]
35pub struct Limits {
36 pub max_connections: usize,
37 pub max_file_streams: usize,
38 pub(crate) max_request_body_bytes: u64,
39 pub header_read_timeout: Duration,
40 pub connection_total_timeout: Duration,
41 pub handler_timeout: Duration,
43 pub body_read_timeout: Duration,
46 pub graceful_shutdown_timeout: Duration,
47 pub max_listing_entries: usize,
49 pub max_listing_response_bytes: usize,
51 pub stream_chunk_size: usize,
53}
54
55impl Default for Limits {
56 fn default() -> Self {
57 Self {
58 max_connections: 64,
59 max_file_streams: 32,
60 max_request_body_bytes: 0,
61 header_read_timeout: Duration::from_secs(10),
62 connection_total_timeout: Duration::from_secs(60),
63 handler_timeout: Duration::from_secs(30),
64 body_read_timeout: Duration::from_secs(30),
65 graceful_shutdown_timeout: Duration::from_secs(10),
66 max_listing_entries: DEFAULT_MAX_LISTING_ENTRIES,
67 max_listing_response_bytes: 1024 * 1024, stream_chunk_size: 8192,
69 }
70 }
71}
72
73impl Limits {
74 pub fn validate(&self) -> Result<(), Vec<LimitsError>> {
79 let mut errors = Vec::new();
80 let max_semaphore_permits = tokio::sync::Semaphore::MAX_PERMITS;
81 if self.max_connections == 0 {
82 errors.push(LimitsError {
83 field: "max_connections",
84 value: "0".into(),
85 constraint: "> 0".into(),
86 });
87 } else if self.max_connections > max_semaphore_permits {
88 errors.push(LimitsError {
89 field: "max_connections",
90 value: self.max_connections.to_string(),
91 constraint: format!("<= {} (Semaphore::MAX_PERMITS)", max_semaphore_permits),
92 });
93 }
94 if self.max_file_streams == 0 {
95 errors.push(LimitsError {
96 field: "max_file_streams",
97 value: "0".into(),
98 constraint: "> 0".into(),
99 });
100 } else if self.max_file_streams > max_semaphore_permits {
101 errors.push(LimitsError {
102 field: "max_file_streams",
103 value: self.max_file_streams.to_string(),
104 constraint: format!("<= {} (Semaphore::MAX_PERMITS)", max_semaphore_permits),
105 });
106 }
107 if self.header_read_timeout.is_zero() {
108 errors.push(LimitsError {
109 field: "header_read_timeout",
110 value: "0s".into(),
111 constraint: "> 0".into(),
112 });
113 }
114 if self.connection_total_timeout.is_zero() {
115 errors.push(LimitsError {
116 field: "connection_total_timeout",
117 value: "0s".into(),
118 constraint: "> 0".into(),
119 });
120 }
121 if self.header_read_timeout > self.connection_total_timeout {
122 errors.push(LimitsError {
123 field: "header_read_timeout",
124 value: format!("{}s", self.header_read_timeout.as_secs()),
125 constraint: "<= connection_total_timeout".into(),
126 });
127 }
128 if self.handler_timeout.is_zero() {
129 errors.push(LimitsError {
130 field: "handler_timeout",
131 value: "0s".into(),
132 constraint: "> 0".into(),
133 });
134 }
135 if self.body_read_timeout.is_zero() {
136 errors.push(LimitsError {
137 field: "body_read_timeout",
138 value: "0s".into(),
139 constraint: "> 0".into(),
140 });
141 }
142 if self.graceful_shutdown_timeout.is_zero() {
143 errors.push(LimitsError {
144 field: "graceful_shutdown_timeout",
145 value: "0s".into(),
146 constraint: "> 0".into(),
147 });
148 }
149 if self.stream_chunk_size < 64 {
150 errors.push(LimitsError {
151 field: "stream_chunk_size",
152 value: self.stream_chunk_size.to_string(),
153 constraint: ">= 64".into(),
154 });
155 }
156 if self.stream_chunk_size > 1024 * 1024 {
157 errors.push(LimitsError {
158 field: "stream_chunk_size",
159 value: self.stream_chunk_size.to_string(),
160 constraint: "<= 1048576 (1 MiB)".into(),
161 });
162 }
163 if self.max_listing_response_bytes == 0 {
164 errors.push(LimitsError {
165 field: "max_listing_response_bytes",
166 value: "0".into(),
167 constraint: "> 0".into(),
168 });
169 } else if self.max_listing_response_bytes > MAX_LISTING_RESPONSE_BYTES {
170 errors.push(LimitsError {
171 field: "max_listing_response_bytes",
172 value: self.max_listing_response_bytes.to_string(),
173 constraint: format!("<= {} (10 MiB)", MAX_LISTING_RESPONSE_BYTES),
174 });
175 }
176 if errors.is_empty() {
177 Ok(())
178 } else {
179 Err(errors)
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn default_limits_are_valid() {
190 let limits = Limits::default();
191 assert!(limits.validate().is_ok());
192 }
193
194 #[test]
195 fn zero_max_connections_is_invalid() {
196 let limits = Limits {
197 max_connections: 0,
198 ..Default::default()
199 };
200 let errs = limits.validate().unwrap_err();
201 assert!(errs.iter().any(|e| e.field == "max_connections"));
202 }
203
204 #[test]
205 fn zero_max_file_streams_is_invalid() {
206 let limits = Limits {
207 max_file_streams: 0,
208 ..Default::default()
209 };
210 let errs = limits.validate().unwrap_err();
211 assert!(errs.iter().any(|e| e.field == "max_file_streams"));
212 }
213
214 #[test]
215 fn zero_header_read_timeout_is_invalid() {
216 let limits = Limits {
217 header_read_timeout: Duration::ZERO,
218 ..Default::default()
219 };
220 let errs = limits.validate().unwrap_err();
221 assert!(errs.iter().any(|e| e.field == "header_read_timeout"));
222 }
223
224 #[test]
225 fn zero_connection_total_timeout_is_invalid() {
226 let limits = Limits {
227 connection_total_timeout: Duration::ZERO,
228 ..Default::default()
229 };
230 let errs = limits.validate().unwrap_err();
231 assert!(errs.iter().any(|e| e.field == "connection_total_timeout"));
232 }
233
234 #[test]
235 fn header_timeout_cannot_exceed_connection_timeout() {
236 let limits = Limits {
237 header_read_timeout: Duration::from_secs(2),
238 connection_total_timeout: Duration::from_secs(1),
239 ..Default::default()
240 };
241 let errs = limits.validate().unwrap_err();
242 assert!(errs.iter().any(|e| e.field == "header_read_timeout"));
243 }
244
245 #[test]
246 fn zero_handler_timeout_is_invalid() {
247 let limits = Limits {
248 handler_timeout: Duration::ZERO,
249 ..Default::default()
250 };
251 let errs = limits.validate().unwrap_err();
252 assert!(errs.iter().any(|e| e.field == "handler_timeout"));
253 }
254
255 #[test]
256 fn zero_body_read_timeout_is_invalid() {
257 let limits = Limits {
258 body_read_timeout: Duration::ZERO,
259 ..Default::default()
260 };
261 let errs = limits.validate().unwrap_err();
262 assert!(errs.iter().any(|e| e.field == "body_read_timeout"));
263 }
264
265 #[test]
266 fn zero_graceful_shutdown_timeout_is_invalid() {
267 let limits = Limits {
268 graceful_shutdown_timeout: Duration::ZERO,
269 ..Default::default()
270 };
271 let errs = limits.validate().unwrap_err();
272 assert!(errs.iter().any(|e| e.field == "graceful_shutdown_timeout"));
273 }
274
275 #[test]
276 fn multiple_errors_reported() {
277 let limits = Limits {
278 max_connections: 0,
279 max_file_streams: 0,
280 handler_timeout: Duration::ZERO,
281 ..Default::default()
282 };
283 let errs = limits.validate().unwrap_err();
284 assert_eq!(errs.len(), 3);
285 }
286
287 #[test]
288 fn non_default_valid_values() {
289 let limits = Limits {
290 max_connections: 1,
291 max_file_streams: 1,
292 header_read_timeout: Duration::from_millis(1),
293 connection_total_timeout: Duration::from_millis(1),
294 handler_timeout: Duration::from_millis(1),
295 body_read_timeout: Duration::from_millis(1),
296 graceful_shutdown_timeout: Duration::from_millis(1),
297 ..Default::default()
298 };
299 assert!(limits.validate().is_ok());
300 }
301
302 #[test]
303 fn limits_error_display() {
304 let err = LimitsError {
305 field: "max_connections",
306 value: "0".into(),
307 constraint: "> 0".into(),
308 };
309 let msg = err.to_string();
310 assert!(msg.contains("max_connections"));
311 assert!(msg.contains("> 0"));
312 assert!(msg.contains("0"));
313 }
314
315 #[test]
316 fn large_concurrency_values_are_valid() {
317 let max = tokio::sync::Semaphore::MAX_PERMITS;
318 let limits = Limits {
319 max_connections: max,
320 max_file_streams: max,
321 ..Default::default()
322 };
323 assert!(limits.validate().is_ok());
324 }
325
326 #[test]
327 fn exceeding_semaphore_max_permits_is_invalid() {
328 let limits = Limits {
329 max_connections: tokio::sync::Semaphore::MAX_PERMITS + 1,
330 ..Default::default()
331 };
332 let errs = limits.validate().unwrap_err();
333 assert!(errs.iter().any(|e| e.field == "max_connections"));
334 }
335
336 #[test]
337 fn usizemax_concurrency_is_invalid() {
338 let limits = Limits {
339 max_connections: usize::MAX,
340 max_file_streams: usize::MAX,
341 ..Default::default()
342 };
343 let errs = limits.validate().unwrap_err();
344 assert!(errs.iter().any(|e| e.field == "max_connections"));
345 assert!(errs.iter().any(|e| e.field == "max_file_streams"));
346 }
347
348 #[test]
349 fn large_duration_values_are_valid() {
350 let limits = Limits {
351 header_read_timeout: Duration::from_secs(u64::MAX),
352 connection_total_timeout: Duration::from_secs(u64::MAX),
353 handler_timeout: Duration::from_secs(u64::MAX),
354 body_read_timeout: Duration::from_secs(u64::MAX),
355 graceful_shutdown_timeout: Duration::from_secs(u64::MAX),
356 ..Default::default()
357 };
358 assert!(limits.validate().is_ok());
359 }
360
361 #[test]
362 fn limits_error_is_send_and_sync() {
363 fn assert_send_sync<T: Send + Sync>() {}
364 assert_send_sync::<LimitsError>();
365 }
366
367 #[test]
368 fn limits_is_clone() {
369 let limits = Limits::default();
370 let cloned = limits.clone();
371 assert_eq!(limits.max_connections, cloned.max_connections);
372 }
373
374 #[test]
375 fn zero_stream_chunk_size_is_invalid() {
376 let limits = Limits {
377 stream_chunk_size: 0,
378 ..Default::default()
379 };
380 let errs = limits.validate().unwrap_err();
381 assert!(errs.iter().any(|e| e.field == "stream_chunk_size"));
382 }
383
384 #[test]
385 fn small_stream_chunk_size_below_minimum_is_invalid() {
386 let limits = Limits {
387 stream_chunk_size: 63,
388 ..Default::default()
389 };
390 let errs = limits.validate().unwrap_err();
391 assert!(errs.iter().any(|e| e.field == "stream_chunk_size"));
392 }
393
394 #[test]
395 fn minimum_stream_chunk_size_is_valid() {
396 let limits = Limits {
397 stream_chunk_size: 64,
398 ..Default::default()
399 };
400 assert!(limits.validate().is_ok());
401 }
402
403 #[test]
404 fn excessive_stream_chunk_size_is_invalid() {
405 let limits = Limits {
406 stream_chunk_size: 1024 * 1024 + 1,
407 ..Default::default()
408 };
409 let errs = limits.validate().unwrap_err();
410 assert!(errs.iter().any(|e| e.field == "stream_chunk_size"));
411 }
412
413 #[test]
414 fn listing_response_limit_is_bounded() {
415 let limits = Limits {
416 max_listing_response_bytes: MAX_LISTING_RESPONSE_BYTES + 1,
417 ..Default::default()
418 };
419 let errs = limits.validate().unwrap_err();
420 assert!(errs.iter().any(|e| e.field == "max_listing_response_bytes"));
421 }
422
423 #[test]
424 fn maximum_stream_chunk_size_is_valid() {
425 let limits = Limits {
426 stream_chunk_size: 1024 * 1024,
427 ..Default::default()
428 };
429 assert!(limits.validate().is_ok());
430 }
431
432 #[test]
433 fn validate_all_fields_simultaneously() {
434 let limits = Limits {
435 max_connections: 0,
436 max_file_streams: 0,
437 header_read_timeout: Duration::ZERO,
438 connection_total_timeout: Duration::ZERO,
439 handler_timeout: Duration::ZERO,
440 body_read_timeout: Duration::ZERO,
441 graceful_shutdown_timeout: Duration::ZERO,
442 stream_chunk_size: 0,
443 ..Default::default()
444 };
445 let errs = limits.validate().unwrap_err();
446 assert_eq!(errs.len(), 8);
447 let fields: Vec<&str> = errs.iter().map(|e| e.field).collect();
448 assert!(fields.contains(&"max_connections"));
449 assert!(fields.contains(&"max_file_streams"));
450 assert!(fields.contains(&"header_read_timeout"));
451 assert!(fields.contains(&"connection_total_timeout"));
452 assert!(fields.contains(&"handler_timeout"));
453 assert!(fields.contains(&"body_read_timeout"));
454 assert!(fields.contains(&"graceful_shutdown_timeout"));
455 assert!(fields.contains(&"stream_chunk_size"));
456 }
457}