diff --git a/src/Exceptions/ErrorException.php b/src/Exceptions/ErrorException.php index 5f28481b..c6468592 100644 --- a/src/Exceptions/ErrorException.php +++ b/src/Exceptions/ErrorException.php @@ -14,12 +14,15 @@ final class ErrorException extends Exception /** * Creates a new Exception instance. * - * @param array{message: string|array, type: ?string, code: string|int|null} $contents + * @param array{message?: string|array, type?: ?string, code?: string|int|null}|string $contents */ - public function __construct(private readonly array $contents, public readonly ResponseInterface $response) + public function __construct(private readonly string|array $contents, public readonly ResponseInterface $response) { $this->statusCode = $response->getStatusCode(); - $message = ($contents['message'] ?: (string) $this->contents['code']) ?: 'Unknown error'; + + // Errors can be a string or an object with message, type, and code + $contents = is_string($contents) ? ['message' => $contents] : $contents; + $message = ($contents['message'] ?? null) ?: (string) ($contents['code'] ?? null) ?: 'Unknown error'; if (is_array($message)) { $message = implode(PHP_EOL, $message); @@ -51,7 +54,7 @@ public function getErrorMessage(): string */ public function getErrorType(): ?string { - return $this->contents['type']; + return $this->contents['type'] ?? null; } /** @@ -59,6 +62,6 @@ public function getErrorType(): ?string */ public function getErrorCode(): string|int|null { - return $this->contents['code']; + return $this->contents['code'] ?? null; } } diff --git a/src/Transporters/HttpTransporter.php b/src/Transporters/HttpTransporter.php index 2a571f76..ee69e91e 100644 --- a/src/Transporters/HttpTransporter.php +++ b/src/Transporters/HttpTransporter.php @@ -162,7 +162,7 @@ private function throwIfJsonError(ResponseInterface $response, string|ResponseIn } try { - /** @var array{error?: array{message: string|array, type: string, code: string}} $data */ + /** @var array{error?: string|array{message: string|array, type: string, code: string}} $data */ $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); if (isset($data['error'])) { diff --git a/tests/Transporters/HttpTransporter.php b/tests/Transporters/HttpTransporter.php index 688cf186..225ef0d1 100644 --- a/tests/Transporters/HttpTransporter.php +++ b/tests/Transporters/HttpTransporter.php @@ -190,6 +190,50 @@ }); })->with('request methods'); +test('error code may be string for no permission', function (string $requestMethod) { + $payload = Payload::create('completions', ['model' => 'gpt-42']); + + $response = new Response(404, ['Content-Type' => 'application/json; charset=utf-8'], json_encode([ + 'error' => 'You have insufficient permissions for this operation. Missing scopes: api.model.read', + ])); + + $this->client + ->shouldReceive('sendRequest') + ->once() + ->andReturn($response); + + expect(fn () => $this->http->$requestMethod($payload)) + ->toThrow(function (ErrorException $e) { + expect($e->getMessage())->toBe('You have insufficient permissions for this operation. Missing scopes: api.model.read') + ->and($e->getErrorMessage())->toBe('You have insufficient permissions for this operation. Missing scopes: api.model.read') + ->and($e->getErrorCode())->toBeNull() + ->and($e->getErrorType())->toBeNull(); + }); +})->with('request methods'); + +test('error code may have only message', function (string $requestMethod) { + $payload = Payload::create('completions', ['model' => 'gpt-42']); + + $response = new Response(404, ['Content-Type' => 'application/json; charset=utf-8'], json_encode([ + 'error' => [ + 'message' => 'The engine is currently overloaded, please try again later', + ], + ])); + + $this->client + ->shouldReceive('sendRequest') + ->once() + ->andReturn($response); + + expect(fn () => $this->http->$requestMethod($payload)) + ->toThrow(function (ErrorException $e) { + expect($e->getMessage())->toBe('The engine is currently overloaded, please try again later') + ->and($e->getErrorMessage())->toBe('The engine is currently overloaded, please try again later') + ->and($e->getErrorCode())->toBeNull() + ->and($e->getErrorType())->toBeNull(); + }); +})->with('request methods'); + test('error type may be null on 429', function (string $requestMethod) { $payload = Payload::list('models');