fix(graphql): handle past HTTP dates in retry-after header gracefully

Extract parse_retry_after_value(header, now) as a pure function to enable
deterministic testing of Retry-After header parsing. The previous
implementation used let-chains with SystemTime::now() inline, which made
it untestable and would panic on negative durations when the server
clock was behind or the header contained a date in the past.

Changes:
- Extract parse_retry_after_value() taking an explicit `now` parameter
- Handle past HTTP dates by returning 1 second instead of panicking on
  negative Duration (date.duration_since(now) returns Err for past dates)
- Trim whitespace from header values before parsing
- Add test for past HTTP date returning 1 second minimum
- Add test for delta-seconds with surrounding whitespace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-14 10:20:01 -05:00
parent 361757568f
commit 64e73b1cab
2 changed files with 26 additions and 4 deletions

View File

@@ -126,14 +126,21 @@ fn parse_retry_after(response: &reqwest::Response) -> u64 {
None => return 60,
};
parse_retry_after_value(header, SystemTime::now())
}
fn parse_retry_after_value(header: &str, now: SystemTime) -> u64 {
let header = header.trim();
if let Ok(secs) = header.parse::<u64>() {
return secs.max(1);
}
if let Ok(date) = httpdate::parse_http_date(header)
&& let Ok(delta) = date.duration_since(SystemTime::now())
{
return delta.as_secs().max(1);
if let Ok(date) = httpdate::parse_http_date(header) {
return match date.duration_since(now) {
Ok(delta) => delta.as_secs().max(1),
Err(_) => 1,
};
}
60