diff --git a/CHANGELOG.md b/CHANGELOG.md index 688cd51..d6c52cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Require custom implementations and subclasses of public command APIs to match native method signatures * Require service client response transformers to return `ResultInterface` values * Require custom middleware, transformers, and `executeAll()` callbacks to accept exact argument types instead of relying on scalar coercion +* Preserve requests from PSR-18 request and network exceptions when wrapping command failures ## 1.4.0 - 2026-05-18 diff --git a/composer.json b/composer.json index 98c3f8d..2f8dcd0 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "php": "^7.4 || ^8.0", "guzzlehttp/guzzle": "^8.0@dev", "guzzlehttp/promises": "^3.0@dev", - "guzzlehttp/psr7": "^3.0@dev" + "guzzlehttp/psr7": "^3.0@dev", + "psr/http-client": "^1.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", diff --git a/src/Exception/CommandException.php b/src/Exception/CommandException.php index b5d3743..7453514 100644 --- a/src/Exception/CommandException.php +++ b/src/Exception/CommandException.php @@ -7,6 +7,8 @@ use GuzzleHttp\Command\CommandInterface; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\RequestException; +use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Http\Client\RequestExceptionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -28,10 +30,14 @@ public static function fromPrevious(CommandInterface $command, \Exception $prev) return $prev; } - // If the exception is a RequestException, get the Request and Response. + // If the exception is request-aware, preserve the Request. $request = $response = null; - if ($prev instanceof RequestException) { + if ($prev instanceof RequestExceptionInterface || $prev instanceof NetworkExceptionInterface) { $request = $prev->getRequest(); + } + + // Guzzle RequestException also exposes the optional Response. + if ($prev instanceof RequestException) { $response = $prev->getResponse(); } diff --git a/tests/Exception/CommandExceptionTest.php b/tests/Exception/CommandExceptionTest.php index d3f374c..048259d 100644 --- a/tests/Exception/CommandExceptionTest.php +++ b/tests/Exception/CommandExceptionTest.php @@ -8,8 +8,10 @@ use GuzzleHttp\Command\Exception\CommandClientException; use GuzzleHttp\Command\Exception\CommandException; use GuzzleHttp\Command\Exception\CommandServerException; +use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; use PHPUnit\Framework\TestCase; +use Psr\Http\Client\RequestExceptionInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -62,4 +64,41 @@ public function testFactoryReturnsServerExceptionFor500LevelStatusCode(): void $exception = CommandException::fromPrevious($command, $previous); $this->assertInstanceOf(CommandServerException::class, $exception); } + + public function testFactoryCopiesRequestFromNetworkException(): void + { + $command = $this->createMock(CommandInterface::class); + $request = $this->createMock(RequestInterface::class); + $previous = new ConnectException('error', $request); + + $exception = CommandException::fromPrevious($command, $previous); + $this->assertSame($request, $exception->getRequest()); + $this->assertNull($exception->getResponse()); + $this->assertSame($previous, $exception->getPrevious()); + } + + public function testFactoryCopiesRequestFromPsrRequestException(): void + { + $command = $this->createMock(CommandInterface::class); + $request = $this->createMock(RequestInterface::class); + $previous = new class('error', $request) extends \RuntimeException implements RequestExceptionInterface { + private RequestInterface $request; + + public function __construct(string $message, RequestInterface $request) + { + parent::__construct($message); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + }; + + $exception = CommandException::fromPrevious($command, $previous); + $this->assertSame($request, $exception->getRequest()); + $this->assertNull($exception->getResponse()); + $this->assertSame($previous, $exception->getPrevious()); + } }