Skip to content

Commit 6f61049

Browse files
fix(api_call): Handle JsonException on success response path (#117)
* Handle JsonException on success response path When a 2xx response contains invalid JSON and asJson is true, json_decode with JSON_THROW_ON_ERROR throws an uncaught JsonException. Wrap the call in a try/catch and re-throw as TypesenseClientError with the HTTP status code and a truncated response body for debugging. Fixes #52 * test(api_call): test jsonexception on success response path --------- Co-authored-by: Fanis Tharropoulos <ftharropoulos@gmail.com>
1 parent 0bc7eb6 commit 6f61049

2 files changed

Lines changed: 51 additions & 2 deletions

File tree

src/ApiCall.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,20 @@ private function makeRequest(string $method, string $endPoint, bool $asJson, arr
270270
->setMessage($errorMessage);
271271
}
272272

273-
return $asJson ? json_decode($responseContents, true, 512, JSON_THROW_ON_ERROR) : $responseContents;
273+
if (!$asJson) {
274+
return $responseContents;
275+
}
276+
277+
try {
278+
return json_decode($responseContents, true, 512, JSON_THROW_ON_ERROR);
279+
} catch (JsonException $exception) {
280+
throw new TypesenseClientError(
281+
'HTTP ' . $response->getStatusCode() . ' response is not valid JSON: '
282+
. substr($responseContents, 0, 200),
283+
0,
284+
$exception
285+
);
286+
}
274287
} catch (HttpException $exception) {
275288
$statusCode = $exception->getResponse()->getStatusCode();
276289

tests/Feature/ApiCallRetryTest.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
use Typesense\Exceptions\RequestMalformed;
1010
use Http\Client\Exception\HttpException;
1111
use Http\Client\Exception\TransferException;
12+
use JsonException;
1213
use Psr\Http\Client\ClientInterface;
1314
use Psr\Http\Message\ResponseInterface;
1415
use Psr\Http\Message\StreamInterface;
1516
use Psr\Http\Message\RequestInterface;
17+
use Typesense\Exceptions\TypesenseClientError;
1618

1719
class ApiCallRetryTest extends TestCase
1820
{
@@ -464,4 +466,38 @@ public function testDoesNotSleepOnFinalRetryAttempt(): void
464466
"Execution was too fast ({$actualDuration}s), suggesting sleep intervals were skipped"
465467
);
466468
}
467-
}
469+
470+
public function testThrowsTypesenseClientErrorWhenSuccessResponseContainsInvalidJson(): void
471+
{
472+
$httpClient = $this->createMock(ClientInterface::class);
473+
$httpClient->method('sendRequest')
474+
->willReturnCallback(function () {
475+
$response = $this->createMock(ResponseInterface::class);
476+
$response->method('getStatusCode')->willReturn(200);
477+
$stream = $this->createMock(StreamInterface::class);
478+
$stream->method('getContents')->willReturn('{invalid json');
479+
$response->method('getBody')->willReturn($stream);
480+
return $response;
481+
});
482+
483+
$config = new Configuration([
484+
'api_key' => 'test-key',
485+
'nodes' => [
486+
['host' => 'node1', 'port' => 8108, 'protocol' => 'http']
487+
],
488+
'num_retries' => 0,
489+
'client' => $httpClient
490+
]);
491+
492+
$apiCall = new ApiCall($config);
493+
494+
try {
495+
$apiCall->get('/test', []);
496+
$this->fail('Expected TypesenseClientError to be thrown');
497+
} catch (TypesenseClientError $exception) {
498+
$this->assertStringContainsString('HTTP 200 response is not valid JSON:', $exception->getMessage());
499+
$this->assertStringContainsString('{invalid json', $exception->getMessage());
500+
$this->assertInstanceOf(JsonException::class, $exception->getPrevious());
501+
}
502+
}
503+
}

0 commit comments

Comments
 (0)