From 5aa6b2936fa60bf19c611a75959840a006b8fef5 Mon Sep 17 00:00:00 2001 From: meyerbaptiste Date: Wed, 3 Feb 2021 14:38:24 +0100 Subject: [PATCH] Add the ability to customize multiple status codes based on the validation exception --- .../ValidationExceptionListener.php | 4 +- ...ntViolationListAwareExceptionInterface.php | 28 +++++++++++ .../Exception/ValidationException.php | 5 +- .../ValidationExceptionListenerTest.php | 49 +++++++++++++++++++ 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 src/Bridge/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php diff --git a/src/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php b/src/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php index d0da61f1d2d..82ebfe35f37 100644 --- a/src/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php +++ b/src/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Validator\EventListener; -use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException; +use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface; use ApiPlatform\Core\Util\ErrorFormatGuesser; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -43,7 +43,7 @@ public function __construct(SerializerInterface $serializer, array $errorFormats public function onKernelException(ExceptionEvent $event): void { $exception = method_exists($event, 'getThrowable') ? $event->getThrowable() : $event->getException(); // @phpstan-ignore-line - if (!$exception instanceof ValidationException) { + if (!$exception instanceof ConstraintViolationListAwareExceptionInterface) { return; } $exceptionClass = \get_class($exception); diff --git a/src/Bridge/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php b/src/Bridge/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php new file mode 100644 index 00000000000..cc47928de0b --- /dev/null +++ b/src/Bridge/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Bridge\Symfony\Validator\Exception; + +use ApiPlatform\Core\Exception\ExceptionInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; + +/** + * An exception which has a constraint violation list. + */ +interface ConstraintViolationListAwareExceptionInterface extends ExceptionInterface +{ + /** + * Gets constraint violations related to this exception. + */ + public function getConstraintViolationList(): ConstraintViolationListInterface; +} diff --git a/src/Bridge/Symfony/Validator/Exception/ValidationException.php b/src/Bridge/Symfony/Validator/Exception/ValidationException.php index 02af2a21629..b3a1ca6372f 100644 --- a/src/Bridge/Symfony/Validator/Exception/ValidationException.php +++ b/src/Bridge/Symfony/Validator/Exception/ValidationException.php @@ -21,7 +21,7 @@ * * @author Kévin Dunglas */ -final class ValidationException extends BaseValidationException +final class ValidationException extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface { private $constraintViolationList; @@ -32,9 +32,6 @@ public function __construct(ConstraintViolationListInterface $constraintViolatio parent::__construct($message ?: $this->__toString(), $code, $previous); } - /** - * Gets constraint violations related to this exception. - */ public function getConstraintViolationList(): ConstraintViolationListInterface { return $this->constraintViolationList; diff --git a/tests/Bridge/Symfony/Validator/EventListener/ValidationExceptionListenerTest.php b/tests/Bridge/Symfony/Validator/EventListener/ValidationExceptionListenerTest.php index b1128b477c0..af84cd340c0 100644 --- a/tests/Bridge/Symfony/Validator/EventListener/ValidationExceptionListenerTest.php +++ b/tests/Bridge/Symfony/Validator/EventListener/ValidationExceptionListenerTest.php @@ -14,8 +14,10 @@ namespace ApiPlatform\Core\Tests\Bridge\Symfony\Validator\EventListener; use ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidationExceptionListener; +use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface; use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException; use ApiPlatform\Core\Tests\ProphecyTrait; +use ApiPlatform\Core\Validator\Exception\ValidationException as BaseValidationException; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -23,6 +25,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationListInterface; /** * @author Kévin Dunglas @@ -62,4 +65,50 @@ public function testValidationException() $this->assertSame('nosniff', $response->headers->get('X-Content-Type-Options')); $this->assertSame('deny', $response->headers->get('X-Frame-Options')); } + + public function testOnKernelValidationExceptionWithCustomStatus(): void + { + $serializedConstraintViolationList = '{"foo": "bar"}'; + $constraintViolationList = new ConstraintViolationList([]); + $exception = new class($constraintViolationList) extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface { + private $constraintViolationList; + + public function __construct(ConstraintViolationListInterface $constraintViolationList, $message = '', $code = 0, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->constraintViolationList = $constraintViolationList; + } + + public function getConstraintViolationList(): ConstraintViolationListInterface + { + return $this->constraintViolationList; + } + }; + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->serialize($constraintViolationList, 'hydra')->willReturn($serializedConstraintViolationList)->shouldBeCalledOnce(); + + $exceptionEvent = new ExceptionEvent( + $this->prophesize(HttpKernelInterface::class)->reveal(), + new Request(), + HttpKernelInterface::MASTER_REQUEST, + $exception + ); + + (new ValidationExceptionListener( + $serializerProphecy->reveal(), + ['hydra' => ['application/ld+json']], + [\get_class($exception) => Response::HTTP_BAD_REQUEST] + ))->onKernelException($exceptionEvent); + + $response = $exceptionEvent->getResponse(); + + self::assertInstanceOf(Response::class, $response); + self::assertSame($serializedConstraintViolationList, $response->getContent()); + self::assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode()); + self::assertSame('application/ld+json; charset=utf-8', $response->headers->get('Content-Type')); + self::assertSame('nosniff', $response->headers->get('X-Content-Type-Options')); + self::assertSame('deny', $response->headers->get('X-Frame-Options')); + } }