Skip to content

Commit 9632b64

Browse files
Damien-Levdamien levallois
andauthored
feat: serialize error title from ValidationException (api-platform#5313)
Co-authored-by: damien levallois <[email protected]>
1 parent b5734a7 commit 9632b64

File tree

4 files changed

+42
-6
lines changed

4 files changed

+42
-6
lines changed

src/Symfony/Validator/EventListener/ValidationExceptionListener.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Exception\FilterValidationException;
1717
use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
1818
use ApiPlatform\Util\ErrorFormatGuesser;
19+
use ApiPlatform\Validator\Exception\ValidationException;
1920
use Symfony\Component\HttpFoundation\Response;
2021
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
2122
use Symfony\Component\Serializer\SerializerInterface;
@@ -53,8 +54,13 @@ public function onKernelException(ExceptionEvent $event): void
5354

5455
$format = ErrorFormatGuesser::guessErrorFormat($event->getRequest(), $this->errorFormats);
5556

57+
$context = [];
58+
if ($exception instanceof ValidationException && ($errorTitle = $exception->getErrorTitle())) {
59+
$context['title'] = $errorTitle;
60+
}
61+
5662
$event->setResponse(new Response(
57-
$this->serializer->serialize($exception instanceof ConstraintViolationListAwareExceptionInterface ? $exception->getConstraintViolationList() : $exception, $format['key']),
63+
$this->serializer->serialize($exception instanceof ConstraintViolationListAwareExceptionInterface ? $exception->getConstraintViolationList() : $exception, $format['key'], $context),
5864
$statusCode,
5965
[
6066
'Content-Type' => sprintf('%s; charset=utf-8', $format['value'][0]),

src/Symfony/Validator/Exception/ValidationException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
*/
2424
final class ValidationException extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface, \Stringable
2525
{
26-
public function __construct(private readonly ConstraintViolationListInterface $constraintViolationList, string $message = '', int $code = 0, \Exception $previous = null)
26+
public function __construct(private readonly ConstraintViolationListInterface $constraintViolationList, string $message = '', int $code = 0, \Throwable $previous = null, ?string $errorTitle = null)
2727
{
28-
parent::__construct($message ?: $this->__toString(), $code, $previous);
28+
parent::__construct($message ?: $this->__toString(), $code, $previous, $errorTitle);
2929
}
3030

3131
public function getConstraintViolationList(): ConstraintViolationListInterface

src/Validator/Exception/ValidationException.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,13 @@
2222
*/
2323
class ValidationException extends RuntimeException
2424
{
25+
public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, protected readonly ?string $errorTitle = null)
26+
{
27+
parent::__construct($message, $code, $previous);
28+
}
29+
30+
public function getErrorTitle(): ?string
31+
{
32+
return $this->errorTitle;
33+
}
2534
}

tests/Symfony/Validator/Metadata/ValidationExceptionListenerTest.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function testValidationException(): void
5757
$list = new ConstraintViolationList([]);
5858

5959
$serializerProphecy = $this->prophesize(SerializerInterface::class);
60-
$serializerProphecy->serialize($list, 'hydra')->willReturn($exceptionJson)->shouldBeCalled();
60+
$serializerProphecy->serialize($list, 'hydra', [])->willReturn($exceptionJson)->shouldBeCalled();
6161

6262
$listener = new ValidationExceptionListener($serializerProphecy->reveal(), ['hydra' => ['application/ld+json']]);
6363
$event = new ExceptionEvent($this->prophesize(HttpKernelInterface::class)->reveal(), new Request(), \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, new ValidationException($list));
@@ -89,7 +89,7 @@ public function getConstraintViolationList(): ConstraintViolationListInterface
8989
};
9090

9191
$serializerProphecy = $this->prophesize(SerializerInterface::class);
92-
$serializerProphecy->serialize($constraintViolationList, 'hydra')->willReturn($serializedConstraintViolationList)->shouldBeCalledOnce();
92+
$serializerProphecy->serialize($constraintViolationList, 'hydra', [])->willReturn($serializedConstraintViolationList)->shouldBeCalledOnce();
9393

9494
$exceptionEvent = new ExceptionEvent(
9595
$this->prophesize(HttpKernelInterface::class)->reveal(),
@@ -120,7 +120,7 @@ public function testValidationFilterException(): void
120120
$exception = new FilterValidationException([], 'my message');
121121

122122
$serializerProphecy = $this->prophesize(SerializerInterface::class);
123-
$serializerProphecy->serialize($exception, 'hydra')->willReturn($exceptionJson)->shouldBeCalled();
123+
$serializerProphecy->serialize($exception, 'hydra', [])->willReturn($exceptionJson)->shouldBeCalled();
124124

125125
$listener = new ValidationExceptionListener($serializerProphecy->reveal(), ['hydra' => ['application/ld+json']]);
126126
$event = new ExceptionEvent($this->prophesize(HttpKernelInterface::class)->reveal(), new Request(), \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $exception);
@@ -134,4 +134,25 @@ public function testValidationFilterException(): void
134134
$this->assertSame('nosniff', $response->headers->get('X-Content-Type-Options'));
135135
$this->assertSame('deny', $response->headers->get('X-Frame-Options'));
136136
}
137+
138+
public function testValidationExceptionWithHydraTitle(): void
139+
{
140+
$exceptionJson = '{"foo": "bar"}';
141+
$list = new ConstraintViolationList([]);
142+
143+
$serializerProphecy = $this->prophesize(SerializerInterface::class);
144+
$serializerProphecy->serialize($list, 'hydra', ['title' => 'foo'])->willReturn($exceptionJson)->shouldBeCalled();
145+
146+
$listener = new ValidationExceptionListener($serializerProphecy->reveal(), ['hydra' => ['application/ld+json']]);
147+
$event = new ExceptionEvent($this->prophesize(HttpKernelInterface::class)->reveal(), new Request(), \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, new ValidationException($list, errorTitle: 'foo'));
148+
$listener->onKernelException($event);
149+
150+
$response = $event->getResponse();
151+
$this->assertInstanceOf(Response::class, $response);
152+
$this->assertSame($exceptionJson, $response->getContent());
153+
$this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode());
154+
$this->assertSame('application/ld+json; charset=utf-8', $response->headers->get('Content-Type'));
155+
$this->assertSame('nosniff', $response->headers->get('X-Content-Type-Options'));
156+
$this->assertSame('deny', $response->headers->get('X-Frame-Options'));
157+
}
137158
}

0 commit comments

Comments
 (0)