-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add specific exceptions on provider errors and add retry #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,9 @@ | |||||||||||||||||||||||||||||||||||||
| namespace Utopia\VCS; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| use Exception; | ||||||||||||||||||||||||||||||||||||||
| use Utopia\VCS\Exception\ProviderRateLimited; | ||||||||||||||||||||||||||||||||||||||
| use Utopia\VCS\Exception\ProviderRequestFailed; | ||||||||||||||||||||||||||||||||||||||
| use Utopia\VCS\Exception\ProviderServerError; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| abstract class Adapter | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -297,33 +300,31 @@ abstract public function getCommit(string $owner, string $repositoryName, string | |||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| abstract public function getLatestCommit(string $owner, string $repositoryName, string $branch): array; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Maximum number of retry attempts for transient failures | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| protected int $maxRetries = 3; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Call | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * Make an API call | ||||||||||||||||||||||||||||||||||||||
| * Make an API call with automatic retries for transient failures. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * @param string $method | ||||||||||||||||||||||||||||||||||||||
| * @param string $path | ||||||||||||||||||||||||||||||||||||||
| * @param array<mixed> $headers | ||||||||||||||||||||||||||||||||||||||
| * @param array<mixed> $params | ||||||||||||||||||||||||||||||||||||||
| * @param array<string, string> $headers | ||||||||||||||||||||||||||||||||||||||
| * @param bool $decode | ||||||||||||||||||||||||||||||||||||||
| * @return array<mixed> | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * @throws Exception | ||||||||||||||||||||||||||||||||||||||
| * @throws ProviderServerError | ||||||||||||||||||||||||||||||||||||||
| * @throws ProviderRateLimited | ||||||||||||||||||||||||||||||||||||||
| * @throws ProviderRequestFailed | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| protected function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| $headers = array_merge($this->headers, $headers); | ||||||||||||||||||||||||||||||||||||||
| $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (!$ch) { | ||||||||||||||||||||||||||||||||||||||
| throw new Exception('Curl failed to initialize'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseHeaders = []; | ||||||||||||||||||||||||||||||||||||||
| $responseStatus = -1; | ||||||||||||||||||||||||||||||||||||||
| $responseType = ''; | ||||||||||||||||||||||||||||||||||||||
| $responseBody = ''; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| switch ($headers['content-type']) { | ||||||||||||||||||||||||||||||||||||||
| case 'application/json': | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -343,81 +344,150 @@ protected function call(string $method, string $path = '', array $headers = [], | |||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $formattedHeaders = []; | ||||||||||||||||||||||||||||||||||||||
| foreach ($headers as $i => $header) { | ||||||||||||||||||||||||||||||||||||||
| $headers[] = $i . ':' . $header; | ||||||||||||||||||||||||||||||||||||||
| unset($headers[$i]); | ||||||||||||||||||||||||||||||||||||||
| $formattedHeaders[] = $i . ':' . $header; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_PATH_AS_IS, 1); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_TIMEOUT, 15); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { | ||||||||||||||||||||||||||||||||||||||
| $len = strlen($header); | ||||||||||||||||||||||||||||||||||||||
| $header = explode(':', $header, 2); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (count($header) < 2) { // ignore invalid headers | ||||||||||||||||||||||||||||||||||||||
| $lastException = null; | ||||||||||||||||||||||||||||||||||||||
| $lastResponseStatus = 0; | ||||||||||||||||||||||||||||||||||||||
| $lastResponseBody = ''; | ||||||||||||||||||||||||||||||||||||||
| $lastResponseHeaders = []; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| for ($attempt = 1; $attempt <= $this->maxRetries; $attempt++) { | ||||||||||||||||||||||||||||||||||||||
| $responseHeaders = []; | ||||||||||||||||||||||||||||||||||||||
| $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (!$ch) { | ||||||||||||||||||||||||||||||||||||||
| throw new Exception('Curl failed to initialize'); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_PATH_AS_IS, 1); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_HTTPHEADER, $formattedHeaders); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_TIMEOUT, 15); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { | ||||||||||||||||||||||||||||||||||||||
| $len = strlen($header); | ||||||||||||||||||||||||||||||||||||||
| $header = explode(':', $header, 2); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (count($header) < 2) { // ignore invalid headers | ||||||||||||||||||||||||||||||||||||||
| return $len; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return $len; | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($method != self::METHOD_GET) { | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_POSTFIELDS, $query); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); | ||||||||||||||||||||||||||||||||||||||
| // Allow self signed certificates | ||||||||||||||||||||||||||||||||||||||
| if ($this->selfSigned) { | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return $len; | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| $responseBody = \curl_exec($ch) ?: ''; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($method != self::METHOD_GET) { | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_POSTFIELDS, $query); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if ($responseBody === true) { | ||||||||||||||||||||||||||||||||||||||
| $responseBody = ''; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Allow self signed certificates | ||||||||||||||||||||||||||||||||||||||
| if ($this->selfSigned) { | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); | ||||||||||||||||||||||||||||||||||||||
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| $curlErrno = curl_errno($ch); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+396
to
+402
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| $curlError = curl_error($ch); | ||||||||||||||||||||||||||||||||||||||
| $responseStatus = \curl_getinfo($ch, CURLINFO_HTTP_CODE); | ||||||||||||||||||||||||||||||||||||||
| curl_close($ch); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Handle curl-level network errors (retry) | ||||||||||||||||||||||||||||||||||||||
| if ($curlErrno) { | ||||||||||||||||||||||||||||||||||||||
| $lastException = new ProviderRequestFailed($curlError . ' with status code ' . $responseStatus, $responseStatus); | ||||||||||||||||||||||||||||||||||||||
| if ($attempt < $this->maxRetries) { | ||||||||||||||||||||||||||||||||||||||
| \usleep($this->getRetryDelay($attempt)); | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| throw $lastException; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseBody = \curl_exec($ch) ?: ''; | ||||||||||||||||||||||||||||||||||||||
| $responseType = $responseHeaders['content-type'] ?? ''; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($responseBody === true) { | ||||||||||||||||||||||||||||||||||||||
| $responseBody = ''; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if ($decode) { | ||||||||||||||||||||||||||||||||||||||
| $length = strpos($responseType, ';') ?: strlen($responseType); | ||||||||||||||||||||||||||||||||||||||
| switch (substr($responseType, 0, $length)) { | ||||||||||||||||||||||||||||||||||||||
| case 'application/json': | ||||||||||||||||||||||||||||||||||||||
| $json = \json_decode($responseBody, true); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseType = $responseHeaders['content-type'] ?? ''; | ||||||||||||||||||||||||||||||||||||||
| $responseStatus = \curl_getinfo($ch, CURLINFO_HTTP_CODE); | ||||||||||||||||||||||||||||||||||||||
| if ($json === null) { | ||||||||||||||||||||||||||||||||||||||
| throw new ProviderRequestFailed('Failed to parse response: ' . $responseBody, $responseStatus); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($decode) { | ||||||||||||||||||||||||||||||||||||||
| $length = strpos($responseType, ';') ?: strlen($responseType); | ||||||||||||||||||||||||||||||||||||||
| switch (substr($responseType, 0, $length)) { | ||||||||||||||||||||||||||||||||||||||
| case 'application/json': | ||||||||||||||||||||||||||||||||||||||
| $json = \json_decode($responseBody, true); | ||||||||||||||||||||||||||||||||||||||
| $responseBody = $json; | ||||||||||||||||||||||||||||||||||||||
| $json = null; | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+419
to
+432
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The JSON decoding block (lines 419–432) executes before the 5xx retry check (line 449). When a server returns a 500/503 with |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($json === null) { | ||||||||||||||||||||||||||||||||||||||
| throw new Exception('Failed to parse response: ' . $responseBody); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| $responseHeaders['status-code'] = $responseStatus; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Rate limited (429 or 403 with rate-limit headers) | ||||||||||||||||||||||||||||||||||||||
| if ($responseStatus === 429 || ($responseStatus === 403 && isset($responseHeaders['x-ratelimit-remaining']) && $responseHeaders['x-ratelimit-remaining'] === '0')) { | ||||||||||||||||||||||||||||||||||||||
| if ($attempt < $this->maxRetries) { | ||||||||||||||||||||||||||||||||||||||
| $retryAfter = isset($responseHeaders['retry-after']) ? (int) $responseHeaders['retry-after'] : null; | ||||||||||||||||||||||||||||||||||||||
| $delay = $retryAfter !== null ? $retryAfter * 1_000_000 : $this->getRetryDelay($attempt); | ||||||||||||||||||||||||||||||||||||||
| \usleep($delay); | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| throw new ProviderRateLimited('Rate limited by provider (HTTP ' . $responseStatus . ')', $responseStatus); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseBody = $json; | ||||||||||||||||||||||||||||||||||||||
| $json = null; | ||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||
| // Server errors (5xx) — retry | ||||||||||||||||||||||||||||||||||||||
| if ($responseStatus >= 500) { | ||||||||||||||||||||||||||||||||||||||
| $lastResponseStatus = $responseStatus; | ||||||||||||||||||||||||||||||||||||||
| $lastResponseBody = $responseBody; | ||||||||||||||||||||||||||||||||||||||
| $lastResponseHeaders = $responseHeaders; | ||||||||||||||||||||||||||||||||||||||
| if ($attempt < $this->maxRetries) { | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+448
to
+453
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| \usleep($this->getRetryDelay($attempt)); | ||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| throw new ProviderServerError( | ||||||||||||||||||||||||||||||||||||||
| 'Provider returned server error (HTTP ' . $responseStatus . ') for ' . $method . ' ' . $path, | ||||||||||||||||||||||||||||||||||||||
| $responseStatus | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ((curl_errno($ch)/* || 200 != $responseStatus*/)) { | ||||||||||||||||||||||||||||||||||||||
| throw new Exception(curl_error($ch) . ' with status code ' . $responseStatus, $responseStatus); | ||||||||||||||||||||||||||||||||||||||
| // Success or client error (4xx) — return immediately, no retry | ||||||||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||||||||
| 'headers' => $responseHeaders, | ||||||||||||||||||||||||||||||||||||||
| 'body' => $responseBody, | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $responseHeaders['status-code'] = $responseStatus; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ($responseStatus === 500) { | ||||||||||||||||||||||||||||||||||||||
| echo 'Server error(' . $method . ': ' . $path . '. Params: ' . json_encode($params) . '): ' . json_encode($responseBody) . "\n"; | ||||||||||||||||||||||||||||||||||||||
| // Should not reach here, but handle gracefully | ||||||||||||||||||||||||||||||||||||||
| if ($lastException) { | ||||||||||||||||||||||||||||||||||||||
| throw $lastException; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||||||||
| 'headers' => $responseHeaders, | ||||||||||||||||||||||||||||||||||||||
| 'body' => $responseBody, | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
| throw new ProviderServerError( | ||||||||||||||||||||||||||||||||||||||
| 'Provider returned server error (HTTP ' . $lastResponseStatus . ') for ' . $method . ' ' . $path, | ||||||||||||||||||||||||||||||||||||||
| $lastResponseStatus | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Get retry delay in microseconds using exponential backoff | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * @param int $attempt Current attempt number (1-based) | ||||||||||||||||||||||||||||||||||||||
| * @return int Delay in microseconds | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| protected function getRetryDelay(int $attempt): int | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| // 1s, 2s, 4s | ||||||||||||||||||||||||||||||||||||||
| return (int) (pow(2, $attempt - 1) * 1_000_000); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\VCS\Exception; | ||
|
|
||
| class ProviderRateLimited extends \Exception | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\VCS\Exception; | ||
|
|
||
| class ProviderRequestFailed extends \Exception | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\VCS\Exception; | ||
|
|
||
| class ProviderServerError extends \Exception | ||
| { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$lastResponseBodyand$lastResponseHeadersBoth variables are assigned inside the retry loop but are never read anywhere in the method. They can be removed.