@@ -2109,12 +2109,7 @@ fn problem_error(response: &HttpResponse) -> RemoteError {
21092109 instance : Option < String > ,
21102110 }
21112111 serde_json:: from_slice :: < Problem > ( & response. body ) . map_or_else (
2112- |_| {
2113- RemoteError :: new (
2114- "SERVICE_UNAVAILABLE" ,
2115- format ! ( "the remote service returned HTTP {}" , response. status) ,
2116- )
2117- } ,
2112+ |_| service_unavailable_error ( response) ,
21182113 |problem| {
21192114 let _ = ( & problem. r#type , & problem. instance ) ;
21202115 if problem. status != response. status {
@@ -2123,14 +2118,45 @@ fn problem_error(response: &HttpResponse) -> RemoteError {
21232118 "the remote problem status does not match HTTP" ,
21242119 ) ;
21252120 }
2126- RemoteError :: new (
2127- problem_code ( & problem. code ) ,
2128- problem. detail . unwrap_or ( problem. title ) ,
2129- )
2121+ let detail = match ( response. status , retry_after_seconds ( response) ) {
2122+ ( 429 , Some ( seconds) ) => {
2123+ let detail = problem. detail . unwrap_or ( problem. title ) ;
2124+ let detail = detail. trim_end_matches ( '.' ) ;
2125+ format ! ( "{detail}. Retry in {seconds}s." )
2126+ }
2127+ _ => problem. detail . unwrap_or ( problem. title ) ,
2128+ } ;
2129+ RemoteError :: new ( problem_code ( & problem. code ) , detail)
21302130 } ,
21312131 )
21322132}
21332133
2134+ fn service_unavailable_error ( response : & HttpResponse ) -> RemoteError {
2135+ match ( response. status , retry_after_seconds ( response) ) {
2136+ ( 429 , Some ( seconds) ) => RemoteError :: new (
2137+ "SERVICE_UNAVAILABLE" ,
2138+ format ! ( "the remote service rate limited the request. Retry in {seconds}s." ) ,
2139+ ) ,
2140+ ( status, _) if ( 500 ..600 ) . contains ( & status) => RemoteError :: new (
2141+ "SERVICE_UNAVAILABLE" ,
2142+ format ! (
2143+ "the remote service returned HTTP {status}. Retry in a minute. If it keeps failing, the service may be down."
2144+ ) ,
2145+ ) ,
2146+ ( status, _) => RemoteError :: new (
2147+ "SERVICE_UNAVAILABLE" ,
2148+ format ! ( "the remote service returned HTTP {status}" ) ,
2149+ ) ,
2150+ }
2151+ }
2152+
2153+ fn retry_after_seconds ( response : & HttpResponse ) -> Option < u64 > {
2154+ response
2155+ . header ( "retry-after" )
2156+ . and_then ( |value| value. parse :: < u64 > ( ) . ok ( ) )
2157+ . filter ( |value| * value <= 3600 )
2158+ }
2159+
21342160fn problem_code ( value : & str ) -> & ' static str {
21352161 match value {
21362162 "AUTH_REQUIRED" => "AUTH_REQUIRED" ,
@@ -2341,3 +2367,54 @@ struct GithubBlob {
23412367 encoding : String ,
23422368 size : u64 ,
23432369}
2370+
2371+ #[ cfg( test) ]
2372+ mod problem_tests {
2373+ use super :: { HttpResponse , problem_error} ;
2374+
2375+ fn response ( status : u16 , headers : & [ ( & str , & str ) ] , body : & str ) -> HttpResponse {
2376+ HttpResponse {
2377+ status,
2378+ headers : headers
2379+ . iter ( )
2380+ . map ( |( name, value) | ( ( * name) . to_owned ( ) , ( * value) . to_owned ( ) ) )
2381+ . collect ( ) ,
2382+ body : body. as_bytes ( ) . to_vec ( ) ,
2383+ }
2384+ }
2385+
2386+ #[ test]
2387+ fn server_errors_state_the_next_step ( ) {
2388+ let error = problem_error ( & response ( 500 , & [ ] , "not json" ) ) ;
2389+ assert_eq ! ( error. code, "SERVICE_UNAVAILABLE" ) ;
2390+ assert_eq ! (
2391+ error. message,
2392+ "the remote service returned HTTP 500. Retry in a minute. If it keeps failing, the service may be down."
2393+ ) ;
2394+ }
2395+
2396+ #[ test]
2397+ fn rate_limits_without_a_body_state_the_wait ( ) {
2398+ let error = problem_error ( & response ( 429 , & [ ( "retry-after" , "7" ) ] , "not json" ) ) ;
2399+ assert_eq ! ( error. code, "SERVICE_UNAVAILABLE" ) ;
2400+ assert_eq ! (
2401+ error. message,
2402+ "the remote service rate limited the request. Retry in 7s."
2403+ ) ;
2404+ }
2405+
2406+ #[ test]
2407+ fn rate_limits_with_a_body_append_the_wait ( ) {
2408+ let body = r#"{"code":"RATE_LIMITED","title":"Too many requests","status":429,"type":"about:blank"}"# ;
2409+ let error = problem_error ( & response ( 429 , & [ ( "retry-after" , "12" ) ] , body) ) ;
2410+ assert_eq ! ( error. code, "RATE_LIMITED" ) ;
2411+ assert_eq ! ( error. message, "Too many requests. Retry in 12s." ) ;
2412+ }
2413+
2414+ #[ test]
2415+ fn client_errors_keep_the_plain_message ( ) {
2416+ let error = problem_error ( & response ( 404 , & [ ] , "not json" ) ) ;
2417+ assert_eq ! ( error. code, "SERVICE_UNAVAILABLE" ) ;
2418+ assert_eq ! ( error. message, "the remote service returned HTTP 404" ) ;
2419+ }
2420+ }
0 commit comments