Skip to content

[ValidationException] Allow customization of validation error status code #3808

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion features/graphql/mutation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ Feature: GraphQL mutation support
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "errors[0].extensions.status" should be equal to "400"
And the JSON node "errors[0].extensions.status" should be equal to "422"
And the JSON node "errors[0].message" should be equal to "name: This value should not be blank."
And the JSON node "errors[0].extensions.violations" should exist
And the JSON node "errors[0].extensions.violations[0].path" should be equal to "name"
Expand Down
2 changes: 1 addition & 1 deletion features/hal/problem.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Feature: Error handling valid according to RFC 7807 (application/problem+json)
"""
{}
"""
Then the response status code should be 400
Then the response status code should be 422
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON should be equal to:
Expand Down
2 changes: 1 addition & 1 deletion features/hydra/error.feature
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Feature: Error handling
"""
{}
"""
Then the response status code should be 400
Then the response status code should be 422
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
Expand Down
4 changes: 2 additions & 2 deletions features/jsonapi/errors.feature
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Feature: JSON API error handling
}
}
"""
Then the response status code should be 400
Then the response status code should be 422
And the response should be in JSON
And the JSON should be valid according to the JSON API schema
And the JSON should be equal to:
Expand Down Expand Up @@ -49,7 +49,7 @@ Feature: JSON API error handling
}
}
"""
Then the response status code should be 400
Then the response status code should be 422
And the response should be in JSON
And the JSON should be valid according to the JSON API schema
And the JSON should be equal to:
Expand Down
4 changes: 2 additions & 2 deletions features/main/validation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Feature: Using validations groups
"code": "My Dummy"
}
"""
Then the response status code should be 400
Then the response status code should be 422
And the response should be in JSON
And the JSON should be equal to:
"""
Expand Down Expand Up @@ -52,7 +52,7 @@ Feature: Using validations groups
"code": "My Dummy"
}
"""
Then the response status code should be 400
Then the response status code should be 422
And the response should be in JSON
And the JSON should be equal to:
"""
Expand Down
2 changes: 1 addition & 1 deletion features/security/send_security_headers.feature
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Feature: Send security header
"""
{"name": ""}
"""
Then the response status code should be 400
Then the response status code should be 422
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the header "X-Content-Type-Options" should be equal to "nosniff"
And the header "X-Frame-Options" should be equal to "deny"
1 change: 1 addition & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
<service id="api_platform.listener.exception.validation" class="ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidationExceptionListener">
<argument type="service" id="api_platform.serializer" />
<argument>%api_platform.error_formats%</argument>
<argument>%api_platform.exception_to_status%</argument>

<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
</service>
Expand Down
2 changes: 2 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/graphql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@
</service>

<service id="api_platform.graphql.normalizer.validation_exception" class="ApiPlatform\Core\GraphQl\Serializer\Exception\ValidationExceptionNormalizer">
<argument>%api_platform.exception_to_status%</argument>

<tag name="serializer.normalizer" priority="-780" />
</service>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ final class ValidationExceptionListener
{
private $serializer;
private $errorFormats;
private $exceptionToStatus;

public function __construct(SerializerInterface $serializer, array $errorFormats)
public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [])
{
$this->serializer = $serializer;
$this->errorFormats = $errorFormats;
$this->exceptionToStatus = $exceptionToStatus;
}

/**
Expand All @@ -44,12 +46,22 @@ public function onKernelException(ExceptionEvent $event): void
if (!$exception instanceof ValidationException) {
return;
}
$exceptionClass = \get_class($exception);
$statusCode = Response::HTTP_UNPROCESSABLE_ENTITY;

foreach ($this->exceptionToStatus as $class => $status) {
if (is_a($exceptionClass, $class, true)) {
$statusCode = $status;

break;
}
}

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

$event->setResponse(new Response(
$this->serializer->serialize($exception->getConstraintViolationList(), $format['key']),
Response::HTTP_BAD_REQUEST,
$statusCode,
[
'Content-Type' => sprintf('%s; charset=utf-8', $format['value'][0]),
'X-Content-Type-Options' => 'nosniff',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
*/
final class ValidationExceptionNormalizer implements NormalizerInterface
{
private $exceptionToStatus;

public function __construct(array $exceptionToStatus = [])
{
$this->exceptionToStatus = $exceptionToStatus;
}

/**
* {@inheritdoc}
*/
Expand All @@ -39,7 +46,18 @@ public function normalize($object, $format = null, array $context = []): array
$validationException = $object->getPrevious();
$error = FormattedError::createFromException($object);
$error['message'] = $validationException->getMessage();
$error['extensions']['status'] = Response::HTTP_BAD_REQUEST;

$exceptionClass = \get_class($validationException);
$statusCode = Response::HTTP_UNPROCESSABLE_ENTITY;

foreach ($this->exceptionToStatus as $class => $status) {
if (is_a($exceptionClass, $class, true)) {
$statusCode = $status;

break;
}
}
$error['extensions']['status'] = $statusCode;
$error['extensions']['category'] = 'user';
$error['extensions']['violations'] = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function testValidationException()
$response = $event->getResponse();
$this->assertInstanceOf(Response::class, $response);
$this->assertSame($exceptionJson, $response->getContent());
$this->assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode());
$this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode());
$this->assertSame('application/ld+json; charset=utf-8', $response->headers->get('Content-Type'));
$this->assertSame('nosniff', $response->headers->get('X-Content-Type-Options'));
$this->assertSame('deny', $response->headers->get('X-Frame-Options'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function testNormalize(): void

$normalizedError = $this->validationExceptionNormalizer->normalize($error);
$this->assertSame($exceptionMessage, $normalizedError['message']);
$this->assertSame(400, $normalizedError['extensions']['status']);
$this->assertSame(422, $normalizedError['extensions']['status']);
$this->assertSame('user', $normalizedError['extensions']['category']);
$this->assertArrayHasKey('violations', $normalizedError['extensions']);
$this->assertSame([
Expand Down