Private/GitHub/Invoke-CgrGitHubApiReadRequest.ps1
|
$script:ResolveCgrGitHubApiReadResponse = { param( [Parameter(Mandatory)] [object] $Result, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Path, [switch] $AllowNotFound ) if ($Result.ExitCode -ne 0) { $errorText = Protect-CgrDiagnosticText -Text ([string] $Result.ErrorText) if ($AllowNotFound -and $errorText -match '404|Not Found') { return [pscustomobject] @{ Status = 'NotFound' Data = $null ErrorRecord = $null } } $attemptText = if ($Result.RetryAttempts -gt 1) { " after $($Result.RetryAttempts) attempts" } else { '' } $retryDiagnostic = if ([string]::IsNullOrWhiteSpace([string] $Result.RetryDiagnostic)) { '' } else { ' ' + (Protect-CgrDiagnosticText -Text ([string] $Result.RetryDiagnostic)) } $message = "GitHub CLI API request failed for '$Path'$attemptText. $errorText$retryDiagnostic" $exception = [System.InvalidOperationException]::new($message.Trim()) $errorRecord = [System.Management.Automation.ErrorRecord]::new( $exception, 'GitHubApiRequestFailed', [System.Management.Automation.ErrorCategory]::InvalidOperation, $Path ) return [pscustomobject] @{ Status = 'Error' Data = $null ErrorRecord = $errorRecord } } $json = ($Result.Output | Out-String).Trim() if ([string]::IsNullOrWhiteSpace($json)) { return [pscustomobject] @{ Status = 'Empty' Data = $null ErrorRecord = $null } } try { $data = $json | ConvertFrom-Json -Depth 100 -ErrorAction Stop } catch { $diagnostic = Protect-CgrDiagnosticText -Text $_.Exception.Message $message = "GitHub CLI API response for '$Path' was not valid JSON. $diagnostic" $exception = [System.IO.InvalidDataException]::new($message.Trim(), $_.Exception) $errorRecord = [System.Management.Automation.ErrorRecord]::new( $exception, 'GitHubApiResponseInvalid', [System.Management.Automation.ErrorCategory]::InvalidData, $Path ) return [pscustomobject] @{ Status = 'Error' Data = $null ErrorRecord = $errorRecord } } return [pscustomobject] @{ Status = 'Success' Data = $data ErrorRecord = $null } } function Invoke-CgrGitHubApiReadRequest { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string[]] $ArgumentList, [ValidateRange(1, 10)] [int] $MaxAttempts = 3, [ValidateRange(1, 60000)] [int] $BaseDelayMilliseconds = 250, [ValidateRange(1, 60000)] [int] $MaxDelayMilliseconds = 2000, [ValidateRange(1, 300)] [int] $MaxServerRetryAfterSeconds = 60 ) for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) { $result = Invoke-CgrNativeCommand -FilePath 'gh' -ArgumentList $ArgumentList $result | Add-Member -NotePropertyName RetryAttempts -NotePropertyValue $attempt -Force $result | Add-Member -NotePropertyName RetryDiagnostic -NotePropertyValue $null -Force if ($result.ExitCode -eq 0) { return $result } $errorText = [string] $result.ErrorText $retryable = $errorText -match '(?i)(HTTP\s+(429|502|503|504)\b|secondary rate limit|rate limit exceeded|abuse detection|connection (was )?reset|connection timed out|temporary failure|TLS handshake timeout|server disconnected|unexpected EOF)' if (-not $retryable -or $attempt -ge $MaxAttempts) { return $result } $retryAfterSeconds = $null $retryAfterMatch = [regex]::Match( $errorText, '(?im)(?:Retry-After\s*[:=]\s*|retry after\s+)(?<seconds>\d+)\s*(?:seconds?)?' ) if ($retryAfterMatch.Success) { $retryAfterSeconds = [int] $retryAfterMatch.Groups['seconds'].Value } if ($null -ne $retryAfterSeconds) { if ($retryAfterSeconds -gt $MaxServerRetryAfterSeconds) { $result.RetryDiagnostic = "Automatic retry was not attempted because the server requested a $retryAfterSeconds second delay, exceeding the $MaxServerRetryAfterSeconds second automatic-wait limit." return $result } $delayMilliseconds = $retryAfterSeconds * 1000 } else { $exponentialDelay = [Math]::Min( $MaxDelayMilliseconds, $BaseDelayMilliseconds * [Math]::Pow(2, $attempt - 1) ) $jitterCeiling = [Math]::Max(1, [int] [Math]::Ceiling($BaseDelayMilliseconds / 4)) $jitterMilliseconds = Get-Random -Minimum 0 -Maximum ($jitterCeiling + 1) $delayMilliseconds = [int] [Math]::Min( $MaxDelayMilliseconds, $exponentialDelay + $jitterMilliseconds ) } Start-Sleep -Milliseconds $delayMilliseconds } } |