Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions src/daemon_v2/rpc/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1500,19 +1500,10 @@ pub fn handle_pr_review_post(params: Option<&Value>) -> Result<Value, RpcError>
Ok(json!({"ok": true, "pr": pr_number}))
}

/// Parse a human-friendly duration string into seconds.
/// Parse a human-friendly duration string (e.g., "5m", "1h", "30s", "2w") to seconds.
///
/// Accepts a numeric value followed by a single-character suffix:
/// - `s` — seconds (e.g., `"30s"` → 30)
/// - `m` — minutes (e.g., `"5m"` → 300)
/// - `h` — hours (e.g., `"1h"` → 3600)
/// - `d` — days (e.g., `"2d"` → 172800)
///
/// Returns `None` if the input is empty, the numeric part is not a valid `u64`,
/// or the suffix is unrecognized. Leading/trailing whitespace is trimmed.
///
/// Used by the `channel.read` RPC to convert the `since` parameter (a relative
/// duration like `"5m"`) into a seconds offset for filtering messages.
/// Returns `None` if the input is empty, has an unknown suffix, a non-numeric
/// prefix, or if the resulting value would overflow `u64`.
fn parse_duration_secs(s: &str) -> Option<u64> {
let s = s.trim();
if s.is_empty() {
Expand All @@ -1522,9 +1513,10 @@ fn parse_duration_secs(s: &str) -> Option<u64> {
let n: u64 = num.parse().ok()?;
match suffix {
"s" => Some(n),
"m" => Some(n * 60),
"h" => Some(n * 3600),
"d" => Some(n * 86400),
"m" => n.checked_mul(60),
"h" => n.checked_mul(3600),
"d" => n.checked_mul(86400),
"w" => n.checked_mul(604800),
_ => None,
}
}
22 changes: 22 additions & 0 deletions src/daemon_v2/rpc/handlers_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,25 @@ fn zero_value() {
assert_eq!(parse_duration_secs("0s"), Some(0));
assert_eq!(parse_duration_secs("0m"), Some(0));
}

#[test]
fn weeks() {
assert_eq!(parse_duration_secs("1w"), Some(604800));
assert_eq!(parse_duration_secs("2w"), Some(1209600));
}

#[test]
fn overflow_returns_none() {
// Values just above the u64 overflow threshold for each multiplier
assert_eq!(parse_duration_secs("30500568904944w"), None); // 30500568904944 * 604800 > u64::MAX
assert_eq!(parse_duration_secs("213503982334602d"), None); // 213503982334602 * 86400 > u64::MAX
assert_eq!(parse_duration_secs("5124095576030432h"), None); // 5124095576030432 * 3600 > u64::MAX
assert_eq!(parse_duration_secs("307445734561825861m"), None); // 307445734561825861 * 60 > u64::MAX
// Seconds don't multiply, so u64::MAX itself is valid
assert_eq!(parse_duration_secs("18446744073709551615s"), Some(u64::MAX));
// Values just below the threshold still work
assert_eq!(
parse_duration_secs("30500568904943w"),
Some(30500568904943 * 604800)
);
}
Loading