Skip to content

Commit 3b6d372

Browse files
feature #50767 [HttpKernel] RequestPayloadValueResolver Add support for custom http status code (zim32)
This PR was merged into the 6.4 branch. Discussion ---------- [HttpKernel] RequestPayloadValueResolver Add support for custom http status code | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | no | License | MIT | Doc PR | Will do if accepted Reading this discussion #49134 (comment) it looks like a good idea to have ability to customize http status code in case of a validation error. This MR does not create any BC's. **MapQueryString** and **MapRequestPayload** attributes now have additional parameter `validationFailedStatusCode`, which allows to specify which http status code will be used if validation fails. Commits ------- 4d118c0 [HttpKernel] RequestPayloadValueResolver Add support for custom http status code
2 parents 3b34568 + 4d118c0 commit 3b6d372

File tree

5 files changed

+83
-3
lines changed

5 files changed

+83
-3
lines changed

src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\HttpKernel\Attribute;
1313

14+
use Symfony\Component\HttpFoundation\Response;
1415
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
1516
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1617
use Symfony\Component\Validator\Constraints\GroupSequence;
@@ -29,6 +30,7 @@ public function __construct(
2930
public readonly array $serializationContext = [],
3031
public readonly string|GroupSequence|array|null $validationGroups = null,
3132
string $resolver = RequestPayloadValueResolver::class,
33+
public readonly int $validationFailedStatusCode = Response::HTTP_NOT_FOUND,
3234
) {
3335
parent::__construct($resolver);
3436
}

src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\HttpKernel\Attribute;
1313

14+
use Symfony\Component\HttpFoundation\Response;
1415
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
1516
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1617
use Symfony\Component\Validator\Constraints\GroupSequence;
@@ -30,6 +31,7 @@ public function __construct(
3031
public readonly array $serializationContext = [],
3132
public readonly string|GroupSequence|array|null $validationGroups = null,
3233
string $resolver = RequestPayloadValueResolver::class,
34+
public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY,
3335
) {
3436
parent::__construct($resolver);
3537
}

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* `BundleInterface` no longer extends `ContainerAwareInterface`
88
* Add optional `$className` parameter to `ControllerEvent::getAttributes()`
99
* Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass`
10+
* Add argument `$validationFailedStatusCode` to `#[MapQueryString]` and `#[MapRequestPayload]`
1011

1112
6.3
1213
---

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
8888
foreach ($arguments as $i => $argument) {
8989
if ($argument instanceof MapQueryString) {
9090
$payloadMapper = 'mapQueryString';
91-
$validationFailedCode = Response::HTTP_NOT_FOUND;
91+
$validationFailedCode = $argument->validationFailedStatusCode;
9292
} elseif ($argument instanceof MapRequestPayload) {
9393
$payloadMapper = 'mapRequestPayload';
94-
$validationFailedCode = Response::HTTP_UNPROCESSABLE_ENTITY;
94+
$validationFailedCode = $argument->validationFailedStatusCode;
9595
} else {
9696
continue;
9797
}

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function testNullableValueArgument()
124124

125125
$resolver->onKernelControllerArguments($event);
126126

127-
$this->assertEquals([null], $event->getArguments());
127+
$this->assertSame([null], $event->getArguments());
128128
}
129129

130130
public function testQueryNullableValueArgument()
@@ -251,6 +251,7 @@ public function testValidationNotPassed()
251251
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
252252
} catch (HttpException $e) {
253253
$validationFailedException = $e->getPrevious();
254+
$this->assertSame(404, $e->getStatusCode());
254255
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
255256
$this->assertSame('This value should be of type unknown.', $validationFailedException->getViolations()[0]->getMessage());
256257
$this->assertSame('Test', $validationFailedException->getViolations()[1]->getMessage());
@@ -601,6 +602,73 @@ public static function provideValidationGroupsOnManyTypes(): iterable
601602
new MapQueryString(validationGroups: new Assert\GroupSequence(['strict'])),
602603
];
603604
}
605+
606+
public function testQueryValidationErrorCustomStatusCode()
607+
{
608+
$serializer = new Serializer([new ObjectNormalizer()], []);
609+
610+
$validator = $this->createMock(ValidatorInterface::class);
611+
612+
$validator->expects($this->once())
613+
->method('validate')
614+
->willReturn(new ConstraintViolationList([new ConstraintViolation('Page is invalid', null, [], '', null, '')]));
615+
616+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
617+
618+
$argument = new ArgumentMetadata('page', QueryPayload::class, false, false, null, false, [
619+
MapQueryString::class => new MapQueryString(validationFailedStatusCode: 400),
620+
]);
621+
$request = Request::create('/?page=123');
622+
623+
$kernel = $this->createMock(HttpKernelInterface::class);
624+
$arguments = $resolver->resolve($request, $argument);
625+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
626+
627+
try {
628+
$resolver->onKernelControllerArguments($event);
629+
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
630+
} catch (HttpException $e) {
631+
$validationFailedException = $e->getPrevious();
632+
$this->assertSame(400, $e->getStatusCode());
633+
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
634+
$this->assertSame('Page is invalid', $validationFailedException->getViolations()[0]->getMessage());
635+
}
636+
}
637+
638+
public function testRequestPayloadValidationErrorCustomStatusCode()
639+
{
640+
$content = '{"price": 50, "title": ["not a string"]}';
641+
$payload = new RequestPayload(50);
642+
$serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]);
643+
644+
$validator = $this->createMock(ValidatorInterface::class);
645+
$validator->expects($this->once())
646+
->method('validate')
647+
->with($payload)
648+
->willReturn(new ConstraintViolationList([new ConstraintViolation('Test', null, [], '', null, '')]));
649+
650+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
651+
652+
$argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [
653+
MapRequestPayload::class => new MapRequestPayload(validationFailedStatusCode: 400),
654+
]);
655+
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content);
656+
657+
$kernel = $this->createMock(HttpKernelInterface::class);
658+
$arguments = $resolver->resolve($request, $argument);
659+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
660+
661+
try {
662+
$resolver->onKernelControllerArguments($event);
663+
$this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
664+
} catch (HttpException $e) {
665+
$validationFailedException = $e->getPrevious();
666+
$this->assertSame(400, $e->getStatusCode());
667+
$this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
668+
$this->assertSame('This value should be of type unknown.', $validationFailedException->getViolations()[0]->getMessage());
669+
$this->assertSame('Test', $validationFailedException->getViolations()[1]->getMessage());
670+
}
671+
}
604672
}
605673

606674
class RequestPayload
@@ -612,3 +680,10 @@ public function __construct(public readonly float $price)
612680
{
613681
}
614682
}
683+
684+
class QueryPayload
685+
{
686+
public function __construct(public readonly float $page)
687+
{
688+
}
689+
}

0 commit comments

Comments
 (0)
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