Skip to content

Commit 8cb6a43

Browse files
committed
chore: impl date parsing
1 parent acd5b17 commit 8cb6a43

File tree

2 files changed

+51
-9
lines changed

2 files changed

+51
-9
lines changed

opentelemetry-otlp/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ tokio = { workspace = true, features = ["sync", "rt"], optional = true }
4040

4141
reqwest = { workspace = true, optional = true }
4242
http = { workspace = true, optional = true }
43+
httpdate = { version = "1.0.3", optional = true }
4344
serde = { workspace = true, features = ["derive"], optional = true }
4445
thiserror = { workspace = true }
4546
serde_json = { workspace = true, optional = true }
@@ -88,7 +89,7 @@ tls-webpki-roots = ["tls", "tonic/tls-webpki-roots"]
8889
http-proto = ["prost", "opentelemetry-http", "opentelemetry-proto/gen-tonic-messages", "http", "trace", "metrics"]
8990

9091
# http with retry support.
91-
experimental-http-retry = ["opentelemetry_sdk/experimental_async_runtime", "opentelemetry_sdk/rt-tokio", "tokio"]
92+
experimental-http-retry = ["opentelemetry_sdk/experimental_async_runtime", "opentelemetry_sdk/rt-tokio", "tokio", "httpdate"]
9293

9394
http-json = ["serde_json", "prost", "opentelemetry-http", "opentelemetry-proto/gen-tonic-messages", "opentelemetry-proto/with-serde", "http", "trace", "metrics"]
9495
reqwest-blocking-client = ["reqwest/blocking", "opentelemetry-http/reqwest-blocking"]

opentelemetry-otlp/src/retry_classification.rs

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
//! gRPC RetryInfo metadata.
66
77
use crate::retry::RetryErrorType;
8-
use std::time::Duration;
98

109
#[cfg(feature = "grpc-tonic")]
1110
use tonic;
@@ -14,8 +13,10 @@ use tonic;
1413
use tonic_types::StatusExt;
1514

1615
/// HTTP-specific error classification with Retry-After header support.
16+
#[cfg(feature = "experimental-http-retry")]
1717
pub mod http {
1818
use super::*;
19+
use std::time::Duration;
1920

2021
/// Classifies HTTP errors based on status code and headers.
2122
///
@@ -76,14 +77,17 @@ pub mod http {
7677
}
7778

7879
/// Parses HTTP date format and returns delay in seconds from now.
79-
///
80-
/// This is a simplified parser for the most common HTTP date format.
81-
/// TODO - should we use a library here?
8280
fn parse_http_date_to_delay(date_str: &str) -> Result<u64, ()> {
83-
// For now, return error - would need proper HTTP date parsing
84-
// This could be implemented with chrono or similar
85-
let _ = date_str;
86-
Err(())
81+
use std::time::SystemTime;
82+
83+
// Try parse the date; if we fail, propagate an () error up to the caller.
84+
let target_time = httpdate::parse_http_date(date_str).map_err(|_| ())?;
85+
86+
let now = SystemTime::now();
87+
let delay = target_time
88+
.duration_since(now)
89+
.unwrap_or(std::time::Duration::ZERO);
90+
Ok(delay.as_secs())
8791
}
8892
}
8993

@@ -160,6 +164,7 @@ pub mod grpc {
160164
#[cfg(test)]
161165
mod tests {
162166
use super::*;
167+
use std::time::Duration;
163168

164169
// Tests for HTTP error classification
165170
mod http_tests {
@@ -216,6 +221,42 @@ mod tests {
216221
assert_eq!(classify_http_error(200, None), RetryErrorType::Retryable);
217222
assert_eq!(classify_http_error(300, None), RetryErrorType::Retryable);
218223
}
224+
225+
#[test]
226+
#[cfg(feature = "experimental-http-retry")]
227+
fn test_http_429_with_retry_after_valid_date() {
228+
use std::time::SystemTime;
229+
230+
// Create a time 30 seconds in the future
231+
let future_time = SystemTime::now() + Duration::from_secs(30);
232+
let date_str = httpdate::fmt_http_date(future_time);
233+
let result = classify_http_error(429, Some(&date_str));
234+
match result {
235+
RetryErrorType::Throttled(duration) => {
236+
let secs = duration.as_secs();
237+
assert!(
238+
(29..=30).contains(&secs),
239+
"Expected ~30 seconds, got {}",
240+
secs
241+
);
242+
}
243+
_ => panic!("Expected Throttled, got {:?}", result),
244+
}
245+
}
246+
247+
#[test]
248+
#[cfg(feature = "experimental-http-retry")]
249+
fn test_http_429_with_retry_after_invalid_date() {
250+
let result = classify_http_error(429, Some("Not a valid date"));
251+
assert_eq!(result, RetryErrorType::Retryable); // Falls back to retryable
252+
}
253+
254+
#[test]
255+
#[cfg(feature = "experimental-http-retry")]
256+
fn test_http_429_with_retry_after_malformed_date() {
257+
let result = classify_http_error(429, Some("Sun, 99 Nov 9999 99:99:99 GMT"));
258+
assert_eq!(result, RetryErrorType::Retryable); // Falls back to retryable
259+
}
219260
}
220261

221262
// Tests for gRPC error classification using public interface

0 commit comments

Comments
 (0)