nntp_proxy/types/config/
limits.rs1use std::num::{NonZeroU32, NonZeroUsize};
4
5nonzero_newtype! {
6 #[doc(alias = "pool_size")]
21 #[doc(alias = "connection_limit")]
22 pub struct MaxConnections(NonZeroUsize: usize, serialize as serialize_u64);
23}
24
25impl MaxConnections {
26 pub const DEFAULT: Self = Self(NonZeroUsize::new(10).unwrap());
28}
29
30nonzero_newtype! {
31 pub struct MaxErrors(NonZeroU32: u32, serialize as serialize_u32);
52}
53
54impl MaxErrors {
55 pub const DEFAULT: Self = Self(NonZeroU32::new(3).unwrap());
57}
58
59#[repr(transparent)]
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub struct ThreadCount(NonZeroUsize);
66
67impl ThreadCount {
68 pub const DEFAULT: Self = Self(NonZeroUsize::new(1).unwrap());
70
71 #[must_use]
77 pub const fn new(value: usize) -> Option<Self> {
78 if value == 0 {
79 None
83 } else {
84 match NonZeroUsize::new(value) {
85 Some(nz) => Some(Self(nz)),
86 None => None,
87 }
88 }
89 }
90
91 #[must_use]
97 pub fn from_value(value: usize) -> Option<Self> {
98 if value == 0 {
99 Some(Self::num_cpus())
100 } else {
101 NonZeroUsize::new(value).map(Self)
102 }
103 }
104
105 #[must_use]
107 #[inline]
108 pub const fn get(&self) -> usize {
109 self.0.get()
110 }
111
112 fn num_cpus() -> Self {
114 let count = std::thread::available_parallelism()
115 .map(|p| p.get())
116 .unwrap_or(1);
117 Self(NonZeroUsize::new(count).unwrap())
119 }
120}
121
122impl Default for ThreadCount {
123 fn default() -> Self {
125 Self(NonZeroUsize::new(1).unwrap())
126 }
127}
128
129impl std::fmt::Display for ThreadCount {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(f, "{}", self.get())
132 }
133}
134
135impl From<ThreadCount> for usize {
136 fn from(val: ThreadCount) -> Self {
137 val.get()
138 }
139}
140
141impl serde::Serialize for ThreadCount {
142 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143 where
144 S: serde::Serializer,
145 {
146 serializer.serialize_u64(self.get() as _)
147 }
148}
149
150impl<'de> serde::Deserialize<'de> for ThreadCount {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: serde::Deserializer<'de>,
154 {
155 let value = usize::deserialize(deserializer)?;
156 Self::from_value(value).ok_or_else(|| {
157 serde::de::Error::custom("ThreadCount must be a positive integer (0 means auto-detect)")
158 })
159 }
160}
161
162impl std::str::FromStr for ThreadCount {
163 type Err = std::num::ParseIntError;
164
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 let value = s.parse::<usize>()?;
167 Ok(Self::from_value(value).unwrap_or_else(Self::default))
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use proptest::prelude::*;
175
176 proptest! {
181 #[test]
183 fn prop_max_connections_valid_range(value in 1usize..=10000) {
184 let max = MaxConnections::new(value).unwrap();
185 prop_assert_eq!(max.get(), value);
186 }
187
188 #[test]
190 fn prop_max_connections_display(value in 1usize..=10000) {
191 let max = MaxConnections::new(value).unwrap();
192 prop_assert_eq!(max.to_string(), value.to_string());
193 }
194
195 #[test]
197 fn prop_max_connections_serde_json(value in 1usize..=10000) {
198 let max = MaxConnections::new(value).unwrap();
199 let json = serde_json::to_string(&max).unwrap();
200 let parsed: MaxConnections = serde_json::from_str(&json).unwrap();
201 prop_assert_eq!(parsed.get(), value);
202 }
203
204 #[test]
206 fn prop_max_connections_clone(value in 1usize..=10000) {
207 let max = MaxConnections::new(value).unwrap();
208 let cloned = max.clone();
209 prop_assert_eq!(max, cloned);
210 }
211 }
212
213 #[test]
215 fn test_max_connections_zero_rejected() {
216 assert!(MaxConnections::new(0).is_none());
217 }
218
219 #[test]
220 fn test_max_connections_default() {
221 assert_eq!(MaxConnections::DEFAULT.get(), 10);
222 }
223
224 #[test]
225 fn test_max_connections_serde_json_zero_rejected() {
226 assert!(serde_json::from_str::<MaxConnections>("0").is_err());
227 }
228
229 proptest! {
234 #[test]
236 fn prop_max_errors_valid_range(value in 1u32..=1000) {
237 let max = MaxErrors::new(value).unwrap();
238 prop_assert_eq!(max.get(), value);
239 }
240
241 #[test]
243 fn prop_max_errors_display(value in 1u32..=1000) {
244 let max = MaxErrors::new(value).unwrap();
245 prop_assert_eq!(max.to_string(), value.to_string());
246 }
247
248 #[test]
250 fn prop_max_errors_serde_json(value in 1u32..=1000) {
251 let max = MaxErrors::new(value).unwrap();
252 let json = serde_json::to_string(&max).unwrap();
253 let parsed: MaxErrors = serde_json::from_str(&json).unwrap();
254 prop_assert_eq!(parsed.get(), value);
255 }
256
257 #[test]
259 fn prop_max_errors_clone(value in 1u32..=1000) {
260 let max = MaxErrors::new(value).unwrap();
261 let cloned = max.clone();
262 prop_assert_eq!(max, cloned);
263 }
264 }
265
266 #[test]
268 fn test_max_errors_zero_rejected() {
269 assert!(MaxErrors::new(0).is_none());
270 }
271
272 #[test]
273 fn test_max_errors_default() {
274 assert_eq!(MaxErrors::DEFAULT.get(), 3);
275 }
276
277 #[test]
278 fn test_max_errors_serde_json_zero_rejected() {
279 assert!(serde_json::from_str::<MaxErrors>("0").is_err());
280 }
281
282 proptest! {
287 #[test]
289 fn prop_thread_count_valid_range(value in 1usize..=128) {
290 let threads = ThreadCount::new(value).unwrap();
291 prop_assert_eq!(threads.get(), value);
292 }
293
294 #[test]
296 fn prop_thread_count_from_value(value in 1usize..=128) {
297 let threads = ThreadCount::from_value(value).unwrap();
298 prop_assert_eq!(threads.get(), value);
299 }
300
301 #[test]
303 fn prop_thread_count_display(value in 1usize..=128) {
304 let threads = ThreadCount::new(value).unwrap();
305 prop_assert_eq!(threads.to_string(), value.to_string());
306 }
307
308 #[test]
310 fn prop_thread_count_into_usize(value in 1usize..=128) {
311 let threads = ThreadCount::new(value).unwrap();
312 let converted: usize = threads.into();
313 prop_assert_eq!(converted, value);
314 }
315
316 #[test]
318 fn prop_thread_count_from_str(value in 1usize..=128) {
319 let s = value.to_string();
320 let threads: ThreadCount = s.parse().unwrap();
321 prop_assert_eq!(threads.get(), value);
322 }
323
324 #[test]
326 fn prop_thread_count_serde_json(value in 1usize..=128) {
327 let threads = ThreadCount::new(value).unwrap();
328 let json = serde_json::to_string(&threads).unwrap();
329 let parsed: ThreadCount = serde_json::from_str(&json).unwrap();
330 prop_assert_eq!(parsed.get(), value);
331 }
332
333 #[test]
335 fn prop_thread_count_clone(value in 1usize..=128) {
336 let threads = ThreadCount::new(value).unwrap();
337 let cloned = threads.clone();
338 prop_assert_eq!(threads, cloned);
339 }
340 }
341
342 #[test]
344 fn test_thread_count_default() {
345 assert_eq!(ThreadCount::default().get(), 1);
346 }
347
348 #[test]
349 fn test_thread_count_new_zero_rejected() {
350 assert!(ThreadCount::new(0).is_none());
352 }
353
354 #[test]
355 fn test_thread_count_from_value_zero_auto_detects() {
356 let threads = ThreadCount::from_value(0).unwrap();
358 assert!(threads.get() >= 1);
359 }
360
361 #[test]
362 fn test_thread_count_from_str_zero_auto_detects() {
363 let threads: ThreadCount = "0".parse().unwrap();
365 assert!(threads.get() >= 1);
366 }
367
368 #[test]
369 fn test_thread_count_from_str_invalid() {
370 assert!("not_a_number".parse::<ThreadCount>().is_err());
371 }
372
373 #[test]
374 fn test_thread_count_serde_json_zero_auto_detects() {
375 let parsed: ThreadCount = serde_json::from_str("0").unwrap();
377 assert!(parsed.get() >= 1);
378 }
379}