Skip to main content

agent_first_http/shared/
time.rs

1//! Duration parsing for `--timeout 30s`, `--older-than 30d`, etc.
2//!
3//! Delegates to `humantime` for the wire format and converts to
4//! `std::time::Duration`. Keeps a single error type (`InvalidArgument`) so
5//! every flag that takes a duration produces the same shape on bad input.
6
7use crate::shared::error::{Error, ErrorCode};
8
9/// Parse a human-readable duration (`30s`, `250ms`, `1m`, `30d`) into a
10/// `std::time::Duration`. Empty or unparseable input returns
11/// [`ErrorCode::InvalidArgument`].
12pub fn parse_duration(input: &str) -> Result<std::time::Duration, Error> {
13    if input.is_empty() {
14        return Err(Error::new(
15            ErrorCode::InvalidArgument,
16            "duration: empty input",
17        ));
18    }
19    humantime::parse_duration(input)
20        .map_err(|e| Error::new(ErrorCode::InvalidArgument, format!("duration: {e}")))
21}
22
23/// Convert a `Duration` to milliseconds, saturating on overflow. Used by
24/// `*_ms` fields in the protocol output.
25#[must_use]
26pub fn duration_ms(d: std::time::Duration) -> u64 {
27    u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use std::time::Duration;
34
35    #[test]
36    fn parses_common_units() {
37        assert_eq!(parse_duration("30s").map(|d| d.as_secs()).ok(), Some(30));
38        assert_eq!(
39            parse_duration("250ms").map(|d| d.as_millis()).ok(),
40            Some(250)
41        );
42        assert_eq!(parse_duration("1m").map(|d| d.as_secs()).ok(), Some(60));
43        assert_eq!(
44            parse_duration("2h").map(|d| d.as_secs()).ok(),
45            Some(2 * 3600)
46        );
47        assert_eq!(
48            parse_duration("30d").map(|d| d.as_secs()).ok(),
49            Some(30 * 86400),
50        );
51    }
52
53    #[test]
54    fn empty_input_is_invalid_argument() {
55        let err = parse_duration("").err();
56        assert!(err.is_some());
57        if let Some(e) = err {
58            assert_eq!(e.error_code, ErrorCode::InvalidArgument);
59        }
60    }
61
62    #[test]
63    fn garbage_input_is_invalid_argument() {
64        let err = parse_duration("nonsense").err();
65        assert!(err.is_some());
66        if let Some(e) = err {
67            assert_eq!(e.error_code, ErrorCode::InvalidArgument);
68        }
69    }
70
71    #[test]
72    fn duration_ms_saturates() {
73        assert_eq!(duration_ms(Duration::from_millis(1234)), 1234);
74        assert_eq!(duration_ms(Duration::ZERO), 0);
75    }
76}