1use std::fmt;
27
28use serde::de::{self, Unexpected, Visitor};
29use serde::{Deserializer, Serializer};
30
31fn split_unit(text: &str) -> Result<(u64, &str), String> {
33 let text = text.trim();
34 let digits = text
35 .find(|character: char| !character.is_ascii_digit())
36 .unwrap_or(text.len());
37
38 if digits == 0 {
39 return Err(format!("`{text}` does not start with a number"));
40 }
41
42 let value = text[..digits]
43 .parse::<u64>()
44 .map_err(|_| format!("`{}` is too large", &text[..digits]))?;
45
46 Ok((value, text[digits..].trim()))
47}
48
49pub mod duration {
72 use super::*;
73 use std::time::Duration;
74
75 pub fn parse(text: &str) -> Result<Duration, String> {
81 let text = text.trim();
82
83 if text.is_empty() {
84 return Err("an empty string is not a duration".to_owned());
85 }
86
87 let mut total = Duration::ZERO;
88 let mut rest = text;
89
90 while !rest.is_empty() {
91 let (value, tail) = split_unit(rest)?;
92
93 let boundary = tail
94 .find(|character: char| character.is_ascii_digit())
95 .unwrap_or(tail.len());
96 let (unit, tail) = tail.split_at(boundary);
97
98 let component = match unit.trim() {
99 "ms" => Duration::from_millis(value),
100 "s" => Duration::from_secs(value),
101 "m" => Duration::from_secs(checked(value, 60)?),
106 "h" => Duration::from_secs(checked(value, 60 * 60)?),
107 "d" => Duration::from_secs(checked(value, 24 * 60 * 60)?),
108 "" => {
109 return Err(format!(
110 "`{value}` has no unit; expected one of ms, s, m, h, d"
111 ))
112 }
113 other => {
114 return Err(format!(
115 "unknown duration unit `{other}`; expected one of ms, s, m, h, d"
116 ))
117 }
118 };
119
120 total = total
121 .checked_add(component)
122 .ok_or_else(|| format!("`{text}` is longer than a `Duration` can hold"))?;
123
124 rest = tail;
125 }
126
127 Ok(total)
128 }
129
130 fn checked(value: u64, seconds_per_unit: u64) -> Result<u64, String> {
132 value
133 .checked_mul(seconds_per_unit)
134 .ok_or_else(|| format!("`{value}` overflows a 64-bit second count"))
135 }
136
137 struct DurationVisitor;
138
139 impl Visitor<'_> for DurationVisitor {
140 type Value = Duration;
141
142 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143 formatter.write_str("a duration such as \"30s\" or \"1h30m\", or a number of seconds")
144 }
145
146 fn visit_str<E: de::Error>(self, value: &str) -> Result<Duration, E> {
147 parse(value).map_err(E::custom)
148 }
149
150 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Duration, E> {
151 Ok(Duration::from_secs(value))
152 }
153
154 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Duration, E> {
155 u64::try_from(value)
156 .map(Duration::from_secs)
157 .map_err(|_| E::invalid_value(Unexpected::Signed(value), &self))
158 }
159 }
160
161 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Duration, D::Error> {
167 deserializer.deserialize_any(DurationVisitor)
168 }
169
170 pub fn serialize<S: Serializer>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error> {
176 serializer.serialize_str(&format!("{}ms", value.as_millis()))
177 }
178
179 pub mod option {
181 use super::*;
182
183 struct OptionVisitor;
184
185 impl<'de> Visitor<'de> for OptionVisitor {
186 type Value = Option<Duration>;
187
188 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
189 formatter.write_str("a duration, a number of seconds, or null")
190 }
191
192 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
193 Ok(None)
194 }
195
196 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
197 Ok(None)
198 }
199
200 fn visit_some<D: Deserializer<'de>>(
201 self,
202 deserializer: D,
203 ) -> Result<Self::Value, D::Error> {
204 super::deserialize(deserializer).map(Some)
205 }
206 }
207
208 pub fn deserialize<'de, D: Deserializer<'de>>(
214 deserializer: D,
215 ) -> Result<Option<Duration>, D::Error> {
216 deserializer.deserialize_option(OptionVisitor)
217 }
218
219 pub fn serialize<S: Serializer>(
223 value: &Option<Duration>,
224 serializer: S,
225 ) -> Result<S::Ok, S::Error> {
226 match value {
227 Some(duration) => super::serialize(duration, serializer),
228 None => serializer.serialize_none(),
229 }
230 }
231 }
232}
233
234pub mod bytes {
256 use super::*;
257
258 pub fn parse(text: &str) -> Result<u64, String> {
264 let (value, unit) = split_unit(text)?;
265
266 let multiplier: u64 = match unit.trim().to_ascii_lowercase().as_str() {
267 "" | "b" => 1,
268 "k" | "kib" => 1 << 10,
269 "m" | "mib" => 1 << 20,
270 "g" | "gib" => 1 << 30,
271 "t" | "tib" => 1 << 40,
272 "kb" => 1_000,
273 "mb" => 1_000_000,
274 "gb" => 1_000_000_000,
275 "tb" => 1_000_000_000_000,
276 other => {
277 return Err(format!(
278 "unknown size unit `{other}`; expected one of B, KiB, MiB, GiB, TiB, \
279 KB, MB, GB, TB"
280 ))
281 }
282 };
283
284 value
285 .checked_mul(multiplier)
286 .ok_or_else(|| format!("`{}` overflows a 64-bit byte count", text.trim()))
287 }
288
289 struct BytesVisitor;
290
291 impl Visitor<'_> for BytesVisitor {
292 type Value = u64;
293
294 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
295 formatter.write_str("a size such as \"64MiB\", or a number of bytes")
296 }
297
298 fn visit_str<E: de::Error>(self, value: &str) -> Result<u64, E> {
299 parse(value).map_err(E::custom)
300 }
301
302 fn visit_u64<E: de::Error>(self, value: u64) -> Result<u64, E> {
303 Ok(value)
304 }
305
306 fn visit_i64<E: de::Error>(self, value: i64) -> Result<u64, E> {
307 u64::try_from(value).map_err(|_| E::invalid_value(Unexpected::Signed(value), &self))
308 }
309 }
310
311 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
317 deserializer.deserialize_any(BytesVisitor)
318 }
319
320 pub fn serialize<S: Serializer>(value: &u64, serializer: S) -> Result<S::Ok, S::Error> {
324 serializer.serialize_u64(*value)
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use std::time::Duration;
331
332 use super::{bytes, duration};
333
334 #[test]
335 fn every_duration_unit_parses() {
336 assert_eq!(
337 duration::parse("500ms").unwrap(),
338 Duration::from_millis(500)
339 );
340 assert_eq!(duration::parse("30s").unwrap(), Duration::from_secs(30));
341 assert_eq!(duration::parse("5m").unwrap(), Duration::from_secs(300));
342 assert_eq!(duration::parse("2h").unwrap(), Duration::from_secs(7_200));
343 assert_eq!(duration::parse("1d").unwrap(), Duration::from_secs(86_400));
344 }
345
346 #[test]
347 fn duration_components_add_up() {
348 assert_eq!(
349 duration::parse("1h30m").unwrap(),
350 Duration::from_secs(5_400)
351 );
352 assert_eq!(
353 duration::parse("1m 500ms").unwrap(),
354 Duration::from_millis(60_500)
355 );
356 }
357
358 #[test]
359 fn a_duration_without_a_unit_is_an_error_not_a_guess() {
360 let error = duration::parse("30").unwrap_err();
363
364 assert!(error.contains("no unit"), "{error}");
365 }
366
367 #[test]
368 fn an_unknown_duration_unit_lists_the_valid_ones() {
369 let error = duration::parse("30w").unwrap_err();
370
371 assert!(error.contains("unknown duration unit `w`"), "{error}");
372 assert!(error.contains("ms, s, m, h, d"), "{error}");
373 }
374
375 #[test]
376 fn duration_rejects_empty_and_non_numeric_input() {
377 assert!(duration::parse(" ").is_err());
378 assert!(duration::parse("abc").is_err());
379 assert!(duration::parse("-5s").is_err());
380 }
381
382 #[test]
383 fn binary_and_decimal_size_units_differ() {
384 assert_eq!(bytes::parse("1KiB").unwrap(), 1_024);
385 assert_eq!(bytes::parse("1KB").unwrap(), 1_000);
386 assert_eq!(bytes::parse("64MiB").unwrap(), 64 * 1_024 * 1_024);
387 assert_eq!(bytes::parse("1GB").unwrap(), 1_000_000_000);
388 }
389
390 #[test]
391 fn a_bare_size_unit_is_binary() {
392 assert_eq!(bytes::parse("1M").unwrap(), 1 << 20);
393 assert_eq!(bytes::parse("512").unwrap(), 512);
394 assert_eq!(bytes::parse("512B").unwrap(), 512);
395 }
396
397 #[test]
398 fn size_units_are_case_insensitive() {
399 assert_eq!(
400 bytes::parse("64mib").unwrap(),
401 bytes::parse("64MiB").unwrap()
402 );
403 assert_eq!(bytes::parse("1gb").unwrap(), bytes::parse("1GB").unwrap());
404 }
405
406 #[test]
407 fn an_unknown_size_unit_lists_the_valid_ones() {
408 let error = bytes::parse("5PB").unwrap_err();
409
410 assert!(error.contains("unknown size unit `pb`"), "{error}");
411 }
412
413 #[test]
414 fn a_size_that_overflows_is_reported_rather_than_wrapped() {
415 let error = bytes::parse("100000000000TiB").unwrap_err();
416
417 assert!(error.contains("overflows"), "{error}");
418 }
419
420 #[test]
421 fn both_adapters_still_accept_a_bare_number() {
422 #[derive(serde::Deserialize)]
423 struct Config {
424 #[serde(with = "super::duration")]
425 timeout: Duration,
426 #[serde(with = "super::bytes")]
427 max_body: u64,
428 }
429
430 let config: Config =
431 serde_json::from_str(r#"{"timeout": 30, "max_body": 1048576}"#).unwrap();
432
433 assert_eq!(config.timeout, Duration::from_secs(30));
434 assert_eq!(config.max_body, 1_048_576);
435 }
436
437 #[test]
438 fn the_string_forms_deserialize_through_serde() {
439 #[derive(serde::Deserialize)]
440 struct Config {
441 #[serde(with = "super::duration")]
442 timeout: Duration,
443 #[serde(default, with = "super::duration::option")]
444 grace: Option<Duration>,
445 #[serde(with = "super::bytes")]
446 max_body: u64,
447 }
448
449 let config: Config =
450 serde_json::from_str(r#"{"timeout": "1h30m", "max_body": "64MiB"}"#).unwrap();
451
452 assert_eq!(config.timeout, Duration::from_secs(5_400));
453 assert_eq!(config.grace, None);
454 assert_eq!(config.max_body, 64 * 1_024 * 1_024);
455
456 let config: Config =
457 serde_json::from_str(r#"{"timeout": "1s", "grace": "250ms", "max_body": 1}"#).unwrap();
458
459 assert_eq!(config.grace, Some(Duration::from_millis(250)));
460 }
461
462 #[test]
463 fn a_duration_component_that_overflows_is_an_error_not_a_saturation() {
464 let error = duration::parse("307445734561825861m").unwrap_err();
466
467 assert!(error.contains("overflows"), "{error}");
468 }
469}