.../EventSubscriber/DefaultExceptionSubscriber.php | 28 +++++++++ .../Comment/CommentResourceTestBase.php | 8 +-- .../EventSubscriber/DefaultExceptionSubscriber.php | 66 ++++++++++++++++++++-- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php index 3ae147d..836b667 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php @@ -133,6 +133,34 @@ protected function onHtml(GetResponseForExceptionEvent $event) { } /** + * Handles any exception as a generic error page for JSON. + * + * @todo This should probably check the error reporting level. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + protected function onJson(GetResponseForExceptionEvent $event) { + $exception = $event->getException(); + $error = Error::decodeException($exception); + + // Display the message if the current error reporting level allows this type + // of message to be displayed, + $data = NULL; + if (error_displayable($error) && $message = $exception->getMessage()) { + $data = ['message' => sprintf('A fatal error occurred: %s', $message)]; + } + + $response = new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR); + if ($exception instanceof HttpExceptionInterface) { + $response->setStatusCode($exception->getStatusCode()); + $response->headers->add($exception->getHeaders()); + } + + $event->setResponse($response); + } + + /** * Handles an HttpExceptionInterface exception for unknown formats. * * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php index f6645f4..7eb62d0 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php @@ -278,10 +278,8 @@ public function testPostDxWithoutCriticalBaseFields() { // DX: 422 when missing 'entity_type' field. $request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE]), static::$format); $response = $this->request('POST', $url, $request_options); - // @todo Uncomment, remove next 3 lines in https://www.drupal.org/node/2820364. - $this->assertSame(500, $response->getStatusCode()); - $this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type')); - $this->assertSame('Internal Server Error', (string) $response->getBody()); + // @todo Uncomment, remove next line in https://www.drupal.org/node/2820364. + $this->assertResourceErrorResponse(500, 'Internal Server Error', $response); //$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nentity_type: This value should not be null.\n", $response); // DX: 422 when missing 'entity_id' field. @@ -304,7 +302,7 @@ public function testPostDxWithoutCriticalBaseFields() { $request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['field_name' => TRUE]), static::$format); $response = $this->request('POST', $url, $request_options); // @todo Uncomment, remove next line in https://www.drupal.org/node/2820364. - $this->assertResourceErrorResponse(500, FALSE, $response); + $this->assertResourceErrorResponse(500, 'A fatal error occurred: Field is unknown.', $response); //$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nfield_name: This value should not be null.\n", $response); } diff --git a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php index a1e7bad..e58d980 100644 --- a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php @@ -3,12 +3,15 @@ namespace Drupal\serialization\EventSubscriber; use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase; +use Drupal\Core\Utility\Error; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\Serializer\SerializerInterface; /** - * Handles default error responses in serialization formats. + * Handles default and fatal error responses in serialization formats. */ class DefaultExceptionSubscriber extends HttpExceptionSubscriberBase { @@ -56,6 +59,26 @@ protected static function getPriority() { } /** + * {@inheritdoc} + */ + public function onException(GetResponseForExceptionEvent $event) { + parent::onException($event); + + // If the exception was an HttpExceptionInterface exception, then + // propagation will already have stopped. If propagation has not yet stopped + // this must be a fatal error. + if (!$event->isPropagationStopped() && in_array($event->getRequest()->getRequestFormat(), $this->getHandledFormats(), TRUE)) { + $exception = $event->getException(); + $error = Error::decodeException($exception); + $message = error_displayable($error) + ? sprintf('A fatal error occurred: %s', $exception->getMessage()) + : 'A fatal error occurred.'; + + $event->setResponse($this->buildResponse($message, 500, [], $event->getRequest())); + } + } + + /** * Handles all 4xx errors for all serialization failures. * * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event @@ -64,18 +87,49 @@ protected static function getPriority() { public function on4xx(GetResponseForExceptionEvent $event) { /** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */ $exception = $event->getException(); - $request = $event->getRequest(); + $event->setResponse($this->buildResponse( + $exception->getMessage(), + $exception->getStatusCode(), + $exception->getHeaders(), + $event->getRequest() + )); + } + + /** + * Handles all 500 errors for all serialization failures. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on500(GetResponseForExceptionEvent $event) { + return $this->on4xx($event); + } + + /** + * Builds an error response. + * + * @param string $message + * The message to use in the error response. + * @param int $status_code + * The response status code. + * @param array $headers + * An array of response headers. + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @return \Symfony\Component\HttpFoundation\Response + * The error response to send. + */ + protected function buildResponse($message, $status_code, $headers, Request $request) { $format = $request->getRequestFormat(); - $content = ['message' => $event->getException()->getMessage()]; + $content = ['message' => $message]; $encoded_content = $this->serializer->serialize($content, $format); - $headers = $exception->getHeaders(); // Add the MIME type from the request to send back in the header. $headers['Content-Type'] = $request->getMimeType($format); - $response = new Response($encoded_content, $exception->getStatusCode(), $headers); - $event->setResponse($response); + return new Response($encoded_content, $status_code, $headers); } }