5
5
//! gRPC RetryInfo metadata.
6
6
7
7
use crate :: retry:: RetryErrorType ;
8
- use std:: time:: Duration ;
9
8
10
9
#[ cfg( feature = "grpc-tonic" ) ]
11
10
use tonic;
@@ -14,8 +13,10 @@ use tonic;
14
13
use tonic_types:: StatusExt ;
15
14
16
15
/// HTTP-specific error classification with Retry-After header support.
16
+ #[ cfg( feature = "experimental-http-retry" ) ]
17
17
pub mod http {
18
18
use super :: * ;
19
+ use std:: time:: Duration ;
19
20
20
21
/// Classifies HTTP errors based on status code and headers.
21
22
///
@@ -76,14 +77,17 @@ pub mod http {
76
77
}
77
78
78
79
/// 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?
82
80
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 ( ) )
87
91
}
88
92
}
89
93
@@ -160,6 +164,7 @@ pub mod grpc {
160
164
#[ cfg( test) ]
161
165
mod tests {
162
166
use super :: * ;
167
+ use std:: time:: Duration ;
163
168
164
169
// Tests for HTTP error classification
165
170
mod http_tests {
@@ -216,6 +221,42 @@ mod tests {
216
221
assert_eq ! ( classify_http_error( 200 , None ) , RetryErrorType :: Retryable ) ;
217
222
assert_eq ! ( classify_http_error( 300 , None ) , RetryErrorType :: Retryable ) ;
218
223
}
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
+ }
219
260
}
220
261
221
262
// Tests for gRPC error classification using public interface
0 commit comments