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