@@ -5,12 +5,12 @@ use chrono::Utc;
55use futures:: Stream ;
66use reqwest:: {
77 header:: { HeaderMap , HeaderName , HeaderValue , ACCEPT , AUTHORIZATION , LINK } ,
8- Client ,
8+ Client , StatusCode ,
99} ;
1010use serde:: { Deserialize , Serialize } ;
1111use thiserror:: Error ;
1212use tokio:: time:: sleep;
13- use tracing:: info;
13+ use tracing:: { error , info} ;
1414
1515use crate :: {
1616 config:: { GithubApiConfig , MessageConfig } ,
@@ -38,6 +38,8 @@ pub enum GithubApiError {
3838 TaskJoin ( #[ from] tokio:: task:: JoinError ) ,
3939 #[ error( "to str error: {0}" ) ]
4040 ToStr ( #[ from] axum:: http:: header:: ToStrError ) ,
41+ #[ error( "unsuccesful response: {0}" ) ]
42+ UnsuccesfulResponse ( StatusCode ) ,
4143}
4244
4345#[ derive( Debug , Deserialize ) ]
@@ -178,6 +180,28 @@ impl GithubApi {
178180 Ok ( ( ) )
179181 }
180182
183+ pub ( crate ) async fn get_issue (
184+ & self ,
185+ number : i32 ,
186+ repository_full_name : & str ,
187+ ) -> Result < IssueWithComments , GithubApiError > {
188+ let url = format ! (
189+ "https://api.github.com/repos/{}/issues/{}" ,
190+ repository_full_name, number
191+ ) ;
192+ let issue = self . client . get ( & url) . send ( ) . await ?. json :: < Issue > ( ) . await ?;
193+ let comments = self
194+ . client
195+ . get ( & issue. comments_url )
196+ . query ( & [ ( "direction" , "asc" ) ] )
197+ . send ( )
198+ . await ?
199+ . json :: < Vec < Comment > > ( )
200+ . await ?;
201+
202+ Ok ( IssueWithComments :: new ( issue, comments) )
203+ }
204+
181205 pub ( crate ) fn get_issues (
182206 & self ,
183207 from_page : i32 ,
@@ -202,23 +226,30 @@ impl GithubApi {
202226 let link_header = res. headers( ) . get( LINK ) . cloned( ) ;
203227 let ratelimit_remaining = res. headers( ) . get( X_RATELIMIT_REMAINING ) . cloned( ) ;
204228 let ratelimit_reset = res. headers( ) . get( X_RATELIMIT_RESET ) . cloned( ) ;
229+ if handle_ratelimit( ratelimit_remaining, ratelimit_reset) . await ? {
230+ continue ;
231+ }
205232 let issues = res. json:: <Vec <Issue >>( ) . await ?;
206233 info!( "fetched {} issues from page {}, getting comments for each issue next" , issues. len( ) , page) ;
207- handle_ratelimit( ratelimit_remaining, ratelimit_reset) . await ?;
208234 let page_issue_count = issues. len( ) ;
209235 for ( i, issue) in issues. into_iter( ) . enumerate( ) {
210- let res = client
211- . get( & issue. comments_url)
212- . query( & [ ( "direction" , "asc" ) ] )
213- . send( )
214- . await ?;
215- let ratelimit_remaining = res. headers( ) . get( X_RATELIMIT_REMAINING ) . cloned( ) ;
216- let ratelimit_reset = res. headers( ) . get( X_RATELIMIT_RESET ) . cloned( ) ;
217- handle_ratelimit( ratelimit_remaining, ratelimit_reset) . await ?;
218- let comments = res
219- . json:: <Vec <Comment >>( )
220- . await ?;
221- yield ( IssueWithComments :: new( issue, comments) , ( i + 1 == page_issue_count) . then_some( page) ) ;
236+ loop {
237+ let res = client
238+ . get( & issue. comments_url)
239+ . query( & [ ( "direction" , "asc" ) ] )
240+ . send( )
241+ . await ?;
242+ let ratelimit_remaining = res. headers( ) . get( X_RATELIMIT_REMAINING ) . cloned( ) ;
243+ let ratelimit_reset = res. headers( ) . get( X_RATELIMIT_RESET ) . cloned( ) ;
244+ if handle_ratelimit( ratelimit_remaining, ratelimit_reset) . await ? {
245+ continue ;
246+ }
247+ let comments = res
248+ . json:: <Vec <Comment >>( )
249+ . await ?;
250+ yield ( IssueWithComments :: new( issue, comments) , ( i + 1 == page_issue_count) . then_some( page) ) ;
251+ break ;
252+ }
222253 }
223254 if get_next_page( link_header) ?. is_none( ) {
224255 break ;
@@ -229,23 +260,23 @@ impl GithubApi {
229260 }
230261}
231262
263+ /// returns true if rate limited and sleeps until reset
232264async fn handle_ratelimit (
233265 remaining : Option < HeaderValue > ,
234266 reset : Option < HeaderValue > ,
235- ) -> Result < ( ) , GithubApiError > {
267+ ) -> Result < bool , GithubApiError > {
236268 match ( remaining, reset) {
237269 ( Some ( remaining) , Some ( reset) ) => {
238270 let remaining: i32 = remaining. to_str ( ) ?. parse ( ) ?;
239271 let reset: i64 = reset. to_str ( ) ?. parse ( ) ?;
240- if remaining == 0 {
272+ let rate_limited = remaining == 0 ;
273+ if rate_limited {
241274 let duration = Duration :: from_secs ( ( reset - Utc :: now ( ) . timestamp ( ) + 2 ) as u64 ) ;
242275 info ! ( "rate limit reached, sleeping for {}s" , duration. as_secs( ) ) ;
243276 sleep ( duration) . await ;
244277 }
278+ Ok ( rate_limited)
245279 }
246- ( remaining, reset) => {
247- return Err ( GithubApiError :: MissingRateLimitHeaders ( remaining, reset) )
248- }
280+ ( remaining, reset) => Err ( GithubApiError :: MissingRateLimitHeaders ( remaining, reset) ) ,
249281 }
250- Ok ( ( ) )
251282}
0 commit comments