From 4d118c0f54ed6c5766b6f1d2fc266b21f326f22d Mon Sep 17 00:00:00 2001 From: zim32 Date: Sun, 25 Jun 2023 13:19:44 +0300 Subject: [PATCH] [HttpKernel] RequestPayloadValueResolver Add support for custom http status code --- .../HttpKernel/Attribute/MapQueryString.php | 2 + .../Attribute/MapRequestPayload.php | 2 + src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../RequestPayloadValueResolver.php | 4 +- .../RequestPayloadValueResolverTest.php | 77 ++++++++++++++++++- 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php b/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php index 6ba85e1bddd9e..83722266ee4e3 100644 --- a/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php +++ b/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpKernel\Attribute; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Validator\Constraints\GroupSequence; @@ -29,6 +30,7 @@ public function __construct( public readonly array $serializationContext = [], public readonly string|GroupSequence|array|null $validationGroups = null, string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_NOT_FOUND, ) { parent::__construct($resolver); } diff --git a/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php b/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php index 02c01fa40bf39..cbac606e83fe1 100644 --- a/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php +++ b/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpKernel\Attribute; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Validator\Constraints\GroupSequence; @@ -30,6 +31,7 @@ public function __construct( public readonly array $serializationContext = [], public readonly string|GroupSequence|array|null $validationGroups = null, string $resolver = RequestPayloadValueResolver::class, + public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY, ) { parent::__construct($resolver); } diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index b3d888889f541..aed4b408cb845 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * `BundleInterface` no longer extends `ContainerAwareInterface` * Add optional `$className` parameter to `ControllerEvent::getAttributes()` * Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass` + * Add argument `$validationFailedStatusCode` to `#[MapQueryString]` and `#[MapRequestPayload]` 6.3 --- diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php index 370097cda4b08..0904a34232a60 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -88,10 +88,10 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo foreach ($arguments as $i => $argument) { if ($argument instanceof MapQueryString) { $payloadMapper = 'mapQueryString'; - $validationFailedCode = Response::HTTP_NOT_FOUND; + $validationFailedCode = $argument->validationFailedStatusCode; } elseif ($argument instanceof MapRequestPayload) { $payloadMapper = 'mapRequestPayload'; - $validationFailedCode = Response::HTTP_UNPROCESSABLE_ENTITY; + $validationFailedCode = $argument->validationFailedStatusCode; } else { continue; } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index 4ca326392be56..454170df1fb2e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -124,7 +124,7 @@ public function testNullableValueArgument() $resolver->onKernelControllerArguments($event); - $this->assertEquals([null], $event->getArguments()); + $this->assertSame([null], $event->getArguments()); } public function testQueryNullableValueArgument() @@ -251,6 +251,7 @@ public function testValidationNotPassed() $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); } catch (HttpException $e) { $validationFailedException = $e->getPrevious(); + $this->assertSame(404, $e->getStatusCode()); $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException); $this->assertSame('This value should be of type unknown.', $validationFailedException->getViolations()[0]->getMessage()); $this->assertSame('Test', $validationFailedException->getViolations()[1]->getMessage()); @@ -601,6 +602,73 @@ public static function provideValidationGroupsOnManyTypes(): iterable new MapQueryString(validationGroups: new Assert\GroupSequence(['strict'])), ]; } + + public function testQueryValidationErrorCustomStatusCode() + { + $serializer = new Serializer([new ObjectNormalizer()], []); + + $validator = $this->createMock(ValidatorInterface::class); + + $validator->expects($this->once()) + ->method('validate') + ->willReturn(new ConstraintViolationList([new ConstraintViolation('Page is invalid', null, [], '', null, '')])); + + $resolver = new RequestPayloadValueResolver($serializer, $validator); + + $argument = new ArgumentMetadata('page', QueryPayload::class, false, false, null, false, [ + MapQueryString::class => new MapQueryString(validationFailedStatusCode: 400), + ]); + $request = Request::create('/?page=123'); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $validationFailedException = $e->getPrevious(); + $this->assertSame(400, $e->getStatusCode()); + $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException); + $this->assertSame('Page is invalid', $validationFailedException->getViolations()[0]->getMessage()); + } + } + + public function testRequestPayloadValidationErrorCustomStatusCode() + { + $content = '{"price": 50, "title": ["not a string"]}'; + $payload = new RequestPayload(50); + $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->once()) + ->method('validate') + ->with($payload) + ->willReturn(new ConstraintViolationList([new ConstraintViolation('Test', null, [], '', null, '')])); + + $resolver = new RequestPayloadValueResolver($serializer, $validator); + + $argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(validationFailedStatusCode: 400), + ]); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $validationFailedException = $e->getPrevious(); + $this->assertSame(400, $e->getStatusCode()); + $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException); + $this->assertSame('This value should be of type unknown.', $validationFailedException->getViolations()[0]->getMessage()); + $this->assertSame('Test', $validationFailedException->getViolations()[1]->getMessage()); + } + } } class RequestPayload @@ -612,3 +680,10 @@ public function __construct(public readonly float $price) { } } + +class QueryPayload +{ + public function __construct(public readonly float $page) + { + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy