From c34a8ee38fe32f4ae6f99b00db2e853cc4c298ee Mon Sep 17 00:00:00 2001 From: cyppe Date: Thu, 30 Jul 2026 22:27:58 +0200 Subject: [PATCH] Carry the HTTP status code in the exceptions ApiCall::getException() creates Every exception built by ApiCall::getException() was constructed without a code, so $exception->getCode() is always 0. A caller that wants the status (to log it, to decide between retry and give-up, or to map it to its own error) has to reverse-map the exception class, and the classes cannot express the difference between, say, a 429 and a 502: both arrive as a bare TypesenseClientError. Pass the status code as the exception code. The message is still empty at construction time and is still set by ApiCall::setMessage() afterwards, so messages, exception classes and the retry behaviour are unchanged; the only difference is that getCode() now returns the HTTP status where it returned 0 before (HTTPStatus0Error keeps 0). Two tests: one for every mapped and unmapped status, and one end to end through ApiCall::get() with a mocked 404 response that checks both the code and the server message. --- src/ApiCall.php | 18 ++++++------ tests/Feature/ApiCallRetryTest.php | 44 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/ApiCall.php b/src/ApiCall.php index 31b131b5..401be2d4 100644 --- a/src/ApiCall.php +++ b/src/ApiCall.php @@ -421,23 +421,23 @@ public function getException(int $httpCode): TypesenseClientError { switch ($httpCode) { case 0: - return new HTTPStatus0Error(); + return new HTTPStatus0Error('', $httpCode); case 400: - return new RequestMalformed(); + return new RequestMalformed('', $httpCode); case 401: - return new RequestUnauthorized(); + return new RequestUnauthorized('', $httpCode); case 404: - return new ObjectNotFound(); + return new ObjectNotFound('', $httpCode); case 409: - return new ObjectAlreadyExists(); + return new ObjectAlreadyExists('', $httpCode); case 422: - return new ObjectUnprocessable(); + return new ObjectUnprocessable('', $httpCode); case 500: - return new ServerError(); + return new ServerError('', $httpCode); case 503: - return new ServiceUnavailable(); + return new ServiceUnavailable('', $httpCode); default: - return new TypesenseClientError(); + return new TypesenseClientError('', $httpCode); } } diff --git a/tests/Feature/ApiCallRetryTest.php b/tests/Feature/ApiCallRetryTest.php index b5f5f175..5b570fbd 100644 --- a/tests/Feature/ApiCallRetryTest.php +++ b/tests/Feature/ApiCallRetryTest.php @@ -7,6 +7,7 @@ use Typesense\Lib\Configuration; use Typesense\Exceptions\ServerError; use Typesense\Exceptions\RequestMalformed; +use Typesense\Exceptions\ObjectNotFound; use Http\Client\Exception\HttpException; use Http\Client\Exception\TransferException; use JsonException; @@ -31,6 +32,49 @@ private function createJsonResponseMock(string $json, int $statusCode = 200): Re return $response; } + public function testExceptionsCarryTheHttpStatusCode(): void + { + $config = new Configuration([ + 'api_key' => 'test-key', + 'nodes' => [ + ['host' => 'node1', 'port' => 8108, 'protocol' => 'http'], + ], + 'client' => $this->createMock(ClientInterface::class), + ]); + $apiCall = new ApiCall($config); + + foreach ([0, 400, 401, 403, 404, 408, 409, 422, 429, 500, 502, 503, 504] as $statusCode) { + $exception = $apiCall->getException($statusCode); + + $this->assertSame($statusCode, $exception->getCode()); + $this->assertSame('', $exception->getMessage()); + } + } + + public function testThrownExceptionsCarryTheStatusCodeAndTheServerMessage(): void + { + $httpClient = $this->createMock(ClientInterface::class); + $httpClient->method('sendRequest') + ->willReturn($this->createJsonResponseMock('{"message": "Not found."}', 404)); + + $config = new Configuration([ + 'api_key' => 'test-key', + 'nodes' => [ + ['host' => 'node1', 'port' => 8108, 'protocol' => 'http'], + ], + 'client' => $httpClient, + ]); + $apiCall = new ApiCall($config); + + try { + $apiCall->get('/collections/missing', []); + $this->fail('Expected an ObjectNotFound exception.'); + } catch (ObjectNotFound $exception) { + $this->assertSame(404, $exception->getCode()); + $this->assertSame('Not found.', $exception->getMessage()); + } + } + public function testRetriesOnHttpExceptionWithNon408Status(): void { $callCount = 0;