From 0abb43e983ba088db7cb0ff00eb8ac111b3d668f Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Tue, 20 Oct 2026 17:25:30 +0200 Subject: [PATCH] #69 - Throw exceptions implementing ProcessExceptionInterface instead of raw SPL exceptions As cleverage/process-bundle#317: new exceptions of the bundle extending the SPL exceptions thrown before and implementing ProcessExceptionInterface (5 throw replaced, same messages). Tests and CHANGELOG updated. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + src/Client/Client.php | 3 +- src/Exception/RestException.php | 4 +- src/Exception/UnexpectedValueException.php | 23 +++++++++ src/Registry/ClientRegistry.php | 7 +-- src/Task/RequestTask.php | 6 ++- tests/Exception/SplExceptionsTest.php | 57 ++++++++++++++++++++++ tests/Task/RequestTaskTest.php | 3 +- 8 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/Exception/UnexpectedValueException.php create mode 100644 tests/Exception/SplExceptionsTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index bade465..04c1441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Latest ------ ### Changes +* [#69](https://github.com/cleverage/rest-process-bundle/issues/69) The bundle throws exceptions implementing `ProcessExceptionInterface` of the process bundle instead of raw SPL exceptions (as [cleverage/process-bundle#317](https://github.com/cleverage/process-bundle/issues/317)): new `Exception\UnexpectedValueException`, extending the SPL exceptions thrown before (same messages, the existing `catch` keep working). `RestException` (and so `RestRequestException` and `MissingClientException`) implements `ProcessExceptionInterface`; `RequestTask` throws a `RestRequestException` instead of a raw `\Exception` for an invalid response code (same message). Add tests. * [#67](https://github.com/cleverage/rest-process-bundle/issues/67) Composer: require PHP `^8.2` instead of `>=8.2` (PHP 9 is no longer accepted before being tested), remove the authorization of the `symfony/flex` plugin, which is not required (the CI installs it globally), as [cleverage/process-bundle#319](https://github.com/cleverage/process-bundle/issues/319). * [#62](https://github.com/cleverage/rest-process-bundle/issues/62) Define the public API (Symfony BC promise, as [cleverage/process-bundle#279](https://github.com/cleverage/process-bundle/issues/279)): `RequestTask`, `ClientRegistry` and `CleverAgeRestProcessBundle` are marked `@final since 3.3`, they will be `final` in 4.0: extending them triggers a deprecation with the Symfony `DebugClassLoader`. `Client` (extended by the applications) and the exceptions stay open. `CleverAgeRestProcessExtension` and `RegisterClientsPass` are marked `@internal`. * [#61](https://github.com/cleverage/rest-process-bundle/issues/61) `CleverAgeRestProcessExtension::findServices()` loads the service definition files with a glob pattern instead of a `Finder` (same files, as [cleverage/process-bundle#271](https://github.com/cleverage/process-bundle/issues/271)), `symfony/finder` is no longer required. Add tests. diff --git a/src/Client/Client.php b/src/Client/Client.php index 3dfeceb..6c2e5db 100644 --- a/src/Client/Client.php +++ b/src/Client/Client.php @@ -14,6 +14,7 @@ namespace CleverAge\RestProcessBundle\Client; use CleverAge\RestProcessBundle\Exception\RestRequestException; +use CleverAge\RestProcessBundle\Exception\UnexpectedValueException; use Psr\Log\LoggerInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -212,7 +213,7 @@ static function (&$item, $key) { $replace = array_map( static function (mixed $value): string { if (!\is_scalar($value)) { - throw new \UnexpectedValueException(\sprintf('URL parameters must be scalar values, %s given', get_debug_type($value))); + throw new UnexpectedValueException(\sprintf('URL parameters must be scalar values, %s given', get_debug_type($value))); } return rawurlencode((string) $value); diff --git a/src/Exception/RestException.php b/src/Exception/RestException.php index 786a041..5f44d9f 100644 --- a/src/Exception/RestException.php +++ b/src/Exception/RestException.php @@ -13,11 +13,13 @@ namespace CleverAge\RestProcessBundle\Exception; +use CleverAge\ProcessBundle\Exception\ProcessExceptionInterface; + /** * Class RestException. * * @author Madeline Veyrenc */ -class RestException extends \Exception +class RestException extends \Exception implements ProcessExceptionInterface { } diff --git a/src/Exception/UnexpectedValueException.php b/src/Exception/UnexpectedValueException.php new file mode 100644 index 0000000..0b118ad --- /dev/null +++ b/src/Exception/UnexpectedValueException.php @@ -0,0 +1,23 @@ +clients[$code] = $client; $this->serviceIds[$code] = $serviceId; @@ -104,10 +105,10 @@ public function getClient(string $code): ClientInterface $client = $this->locator->get($code); if (!$client instanceof ClientInterface) { - throw new \UnexpectedValueException(\sprintf('The service of the REST client "%s" must implement "%s".', $code, ClientInterface::class)); + throw new UnexpectedValueException(\sprintf('The service of the REST client "%s" must implement "%s".', $code, ClientInterface::class)); } if ($code !== $client->getCode()) { - throw new \UnexpectedValueException(\sprintf('The REST client "%s" is registered with the code "%s" but its getCode() method returns "%s": both must be the same.', $client::class, $code, $client->getCode())); + throw new UnexpectedValueException(\sprintf('The REST client "%s" is registered with the code "%s" but its getCode() method returns "%s": both must be the same.', $client::class, $code, $client->getCode())); } return $this->clients[$code] = $client; diff --git a/src/Task/RequestTask.php b/src/Task/RequestTask.php index a553c1c..a8c420e 100644 --- a/src/Task/RequestTask.php +++ b/src/Task/RequestTask.php @@ -17,6 +17,8 @@ use CleverAge\ProcessBundle\Model\AbstractConfigurableTask; use CleverAge\ProcessBundle\Model\ProcessState; use CleverAge\RestProcessBundle\Exception\MissingClientException; +use CleverAge\RestProcessBundle\Exception\RestRequestException; +use CleverAge\RestProcessBundle\Exception\UnexpectedValueException; use CleverAge\RestProcessBundle\Registry\ClientRegistry; use Psr\Log\LoggerInterface; use Symfony\Component\OptionsResolver\Exception\AccessException; @@ -103,7 +105,7 @@ public function execute(ProcessState $state): void $state->setStopped(true); } - throw new \Exception('Invalid response code'); + throw new RestRequestException('Invalid response code'); } // The status code is valid: do not throw for 3xx / 4xx / 5xx codes listed in valid_response_code @@ -193,7 +195,7 @@ protected function getRequestOptions(ProcessState $state): array $input = $state->getInput() ?: []; if (!\is_array($input)) { - throw new \UnexpectedValueException(\sprintf('RequestTask expects an array or empty input, %s given', get_debug_type($input))); + throw new UnexpectedValueException(\sprintf('RequestTask expects an array or empty input, %s given', get_debug_type($input))); } /** @var RequestOptions $mergedOptions */ diff --git a/tests/Exception/SplExceptionsTest.php b/tests/Exception/SplExceptionsTest.php new file mode 100644 index 0000000..a3c49a4 --- /dev/null +++ b/tests/Exception/SplExceptionsTest.php @@ -0,0 +1,57 @@ +, class-string<\Throwable>}> + */ + public static function provideExceptions(): iterable + { + yield 'UnexpectedValueException' => [UnexpectedValueException::class, \UnexpectedValueException::class]; + } + + public function testExceptionsOfTheBundleImplementTheInterface(): void + { + self::assertInstanceOf(ProcessExceptionInterface::class, new RestRequestException('message')); + } + + /** + * @param class-string<\Throwable> $class + * @param class-string<\Throwable> $splClass + */ + #[DataProvider('provideExceptions')] + public function testExtendsTheSplExceptionAndImplementsTheInterface(string $class, string $splClass): void + { + $exception = new $class('message', 12); + + self::assertInstanceOf($splClass, $exception); + self::assertInstanceOf(ProcessExceptionInterface::class, $exception); + self::assertSame(['message', 12], [$exception->getMessage(), $exception->getCode()]); + } +} diff --git a/tests/Task/RequestTaskTest.php b/tests/Task/RequestTaskTest.php index 61651e3..2c17432 100644 --- a/tests/Task/RequestTaskTest.php +++ b/tests/Task/RequestTaskTest.php @@ -20,6 +20,7 @@ use CleverAge\ProcessBundle\Model\ProcessState; use CleverAge\RestProcessBundle\Client\Client; use CleverAge\RestProcessBundle\Exception\MissingClientException; +use CleverAge\RestProcessBundle\Exception\RestRequestException; use CleverAge\RestProcessBundle\Registry\ClientRegistry; use CleverAge\RestProcessBundle\Task\RequestTask; use PHPUnit\Framework\Attributes\CoversClass; @@ -106,7 +107,7 @@ public function testInvalidResponseCode(string $errorStrategy, bool $skipped, bo try { $task->execute($state); self::fail('The task must fail'); - } catch (\Exception $e) { + } catch (RestRequestException $e) { self::assertSame('Invalid response code', $e->getMessage()); }