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:
@@ -126,14 +126,21 @@ fn parse_retry_after(response: &reqwest::Response) -> u64 {
|
|||||||
None => return 60,
|
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>() {
|
if let Ok(secs) = header.parse::<u64>() {
|
||||||
return secs.max(1);
|
return secs.max(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(date) = httpdate::parse_http_date(header)
|
if let Ok(date) = httpdate::parse_http_date(header) {
|
||||||
&& let Ok(delta) = date.duration_since(SystemTime::now())
|
return match date.duration_since(now) {
|
||||||
{
|
Ok(delta) => delta.as_secs().max(1),
|
||||||
return delta.as_secs().max(1);
|
Err(_) => 1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
60
|
60
|
||||||
|
|||||||
@@ -244,6 +244,21 @@ async fn test_retry_after_invalid_falls_back_to_60() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_retry_after_http_date_in_past_returns_one_second() {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let past = now - Duration::from_secs(120);
|
||||||
|
let date_str = httpdate::fmt_http_date(past);
|
||||||
|
|
||||||
|
assert_eq!(parse_retry_after_value(&date_str, now), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_retry_after_delta_seconds_trims_whitespace() {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
assert_eq!(parse_retry_after_value(" 120 ", now), 120);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_graphql_network_error() {
|
async fn test_graphql_network_error() {
|
||||||
let client = GraphqlClient::new("http://127.0.0.1:1", "token");
|
let client = GraphqlClient::new("http://127.0.0.1:1", "token");
|
||||||
|
|||||||
Reference in New Issue
Block a user