@@ -25,6 +25,9 @@ public class GithubClient
2525 private const int SECONDARY_RATE_LIMIT_MAX_RETRIES = 3 ;
2626 private const int SECONDARY_RATE_LIMIT_DEFAULT_DELAY = 60 ; // 60 seconds default delay
2727
28+ internal int _secondaryRateLimitMaxRetries = SECONDARY_RATE_LIMIT_MAX_RETRIES ; // Exposed for testing purposes
29+ internal int _secondaryRateLimitDefaultDelay = SECONDARY_RATE_LIMIT_DEFAULT_DELAY ; // Exposed for testing purposes
30+
2831 public GithubClient ( OctoLogger log , HttpClient httpClient , IVersionProvider versionProvider , RetryPolicy retryPolicy , DateTimeProvider dateTimeProvider , string personalAccessToken )
2932 {
3033 _log = log ;
@@ -93,6 +96,36 @@ public virtual async Task<JToken> PostGraphQLAsync(
9396 return data ;
9497 }
9598
99+ public virtual async Task < JToken > PostGraphQLWithRetryAsync (
100+ string url ,
101+ object body ,
102+ Dictionary < string , string > customHeaders = null ,
103+ int retryCount = 0 )
104+ {
105+ var currentRetryCount = retryCount ;
106+
107+ while ( true )
108+ {
109+ var ( response , headers ) = await PostWithRetry ( url , body , customHeaders ) ;
110+
111+ if ( IsGraphQLSecondaryRateLimit ( response ) )
112+ {
113+ ( response , _ ) = await HandleSecondaryRateLimit ( HttpMethod . Post , url , body , HttpStatusCode . OK , customHeaders , headers , currentRetryCount ) ;
114+
115+ if ( IsGraphQLSecondaryRateLimit ( response ) )
116+ {
117+ currentRetryCount ++ ;
118+ continue ;
119+ }
120+ }
121+
122+ var data = JObject . Parse ( response ) ;
123+ EnsureSuccessGraphQLResponse ( data ) ;
124+
125+ return data ;
126+ }
127+ }
128+
96129 public virtual async IAsyncEnumerable < JToken > PostGraphQLWithPaginationAsync (
97130 string url ,
98131 object body ,
@@ -156,6 +189,13 @@ public virtual async Task<string> PatchAsync(string url, object body, Dictionary
156189 HttpStatusCode expectedStatus = HttpStatusCode . OK ) =>
157190 await _retryPolicy . Retry ( async ( ) => await SendAsync ( HttpMethod . Get , url , customHeaders : customHeaders , expectedStatus : expectedStatus ) ) ;
158191
192+ private async Task < ( string Content , KeyValuePair < string , IEnumerable < string > > [ ] ResponseHeaders ) > PostWithRetry (
193+ string url ,
194+ object body ,
195+ Dictionary < string , string > customHeaders = null ,
196+ HttpStatusCode expectedStatus = HttpStatusCode . OK ) =>
197+ await _retryPolicy . Retry ( async ( ) => await SendAsync ( HttpMethod . Post , url , body , customHeaders : customHeaders , expectedStatus : expectedStatus ) ) ;
198+
159199 private async Task < ( string Content , KeyValuePair < string , IEnumerable < string > > [ ] ResponseHeaders ) > SendAsync (
160200 HttpMethod httpMethod ,
161201 string url ,
@@ -312,6 +352,22 @@ private bool IsSecondaryRateLimit(HttpStatusCode statusCode, string content)
312352 statusCode == HttpStatusCode . TooManyRequests ;
313353 }
314354
355+ private bool IsGraphQLSecondaryRateLimit ( string content )
356+ {
357+ var response = JObject . Parse ( content ) ;
358+
359+ if ( response . TryGetValue ( "errors" , out var jErrors ) && jErrors is JArray { Count : > 0 } errors )
360+ {
361+ var error = ( JObject ) errors [ 0 ] ;
362+ if ( error . TryGetValue ( "type" , out var jType ) && jType . Type == JTokenType . String )
363+ {
364+ return ( ( string ) jType ) == "RATE_LIMIT" ;
365+ }
366+ }
367+
368+ return false ;
369+ }
370+
315371 private async Task < ( string Content , KeyValuePair < string , IEnumerable < string > > [ ] ResponseHeaders ) > HandleSecondaryRateLimit (
316372 HttpMethod httpMethod ,
317373 string url ,
@@ -321,7 +377,7 @@ private bool IsSecondaryRateLimit(HttpStatusCode statusCode, string content)
321377 KeyValuePair < string , IEnumerable < string > > [ ] headers ,
322378 int retryCount = 0 )
323379 {
324- if ( retryCount >= SECONDARY_RATE_LIMIT_MAX_RETRIES )
380+ if ( retryCount >= _secondaryRateLimitMaxRetries )
325381 {
326382 throw new OctoshiftCliException ( $ "Secondary rate limit exceeded. Maximum retries ({ SECONDARY_RATE_LIMIT_MAX_RETRIES } ) reached. Please wait before retrying your request.") ;
327383 }
@@ -359,6 +415,6 @@ private int GetSecondaryRateLimitDelay(KeyValuePair<string, IEnumerable<string>>
359415 }
360416
361417 // Otherwise use exponential backoff: 1m → 2m → 4m
362- return SECONDARY_RATE_LIMIT_DEFAULT_DELAY * ( int ) Math . Pow ( 2 , retryCount ) ;
418+ return _secondaryRateLimitDefaultDelay * ( int ) Math . Pow ( 2 , retryCount ) ;
363419 }
364420}
0 commit comments