Problem/Motivation

99% of REST error responses have this normalization:

['message' => 'THE ERROR MESSAGE']

The exception is any exception that occurs inside \Drupal\rest\RequestHandler::handle()'s deserialization or decoding:

        try {
          if (!empty($definition['serialization_class'])) {
            $unserialized = $serializer->deserialize($received, $definition['serialization_class'], $format, array('request_method' => $method));
          }
          // If the plugin does not specify a serialization class just decode
          // the received data.
          else {
            $unserialized = $serializer->decode($received, $format, array('request_method' => $method));
          }
        }
        catch (UnexpectedValueException $e) {
          $error['error'] = $e->getMessage();
          $content = $serializer->serialize($error, $format);
          return new Response($content, 400, array('Content-Type' => $request->getMimeType($format)));
        }

As you can see, that uses this normalization:

['error' => 'THE ERROR MESSAGE']

This prevents consistent error handling on the client side.

Proposed resolution

Make it throw a UnprocessableEntityHttpException() (to convince yourself: https://httpstatuses.com/400 + https://httpstatuses.com/422).

Remaining tasks

User interface changes

None.

API changes

Changes the response for some invalid request bodies that cause 400 responses: they'll change from

['error' => 'THE ERROR MESSAGE']

to:

['message' => 'THE ERROR MESSAGE']

Data model changes

None.

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Active » Needs review
StatusFileSize
new1.22 KB

This means we leave it to KernelEvents::EXCEPTION event subscribers to generate the appropriate response, rather than duplicating it in RequestHandler in an inconsistent way.

wim leers’s picture

Title: RequestHandler has its own error handling rather than leaving it to the overall system. » RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers

Status: Needs review » Needs work

The last submitted patch, 2: 2813853-2.patch, failed testing.

wim leers’s picture

So there are two cases in Drupal core where we assert this inconsistent error response body is returned. Simple enough to fix.

dawehner’s picture

So all we need here is to adapt the expected error messages and be done?

wim leers’s picture

Title: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers » [PP-1] RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers
Status: Needs work » Postponed

Basically, yes. But by just rethrowing the exception, the system does that for us. As the patch in #2 shows, and as the IS describes.

This should wait for #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method to land, because we'll need to update test coverage there.

wim leers’s picture

Title: [PP-1] RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers » RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers
Status: Postponed » Needs work
wim leers’s picture

Version: 8.2.x-dev » 8.3.x-dev
Status: Needs work » Needs review
StatusFileSize
new3.84 KB
new4.97 KB

And, done. Updating the test coverage introduced by #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method.

Moving to 8.3.x-dev, because fixing this might cause disruption in 8.2.x.

Status: Needs review » Needs work

The last submitted patch, 9: 2813853-9.patch, failed testing.

wim leers’s picture

Title: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers » [PP-1] RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers
Status: Needs work » Postponed
wim leers’s picture

wim leers’s picture

wim leers’s picture

wim leers’s picture

Title: [PP-1] RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers » RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers
Status: Postponed » Needs review
wim leers’s picture

StatusFileSize
new4.97 KB

Rebased #9. Straight reroll!

wim leers’s picture

StatusFileSize
new2.16 KB
new7.07 KB

The patch in #9 forgot to change a few lines in HalEntityNormalizationTrait, lines that actually even had explicit todo's pointing to this issue!

The last submitted patch, 16: 2813853-16.patch, failed testing.

damiankloip’s picture

Status: Needs review » Reviewed & tested by the community

Yes, we discussed this for a while. It would be great to get this in so #2827084: EntityNormalizer::denormalize should not throw UnexpectedValueException, but \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException, so we get a 422 response instead of 400 can depend on this and we can fix it properly in one go.

This patch looks good to me.

+++ b/core/modules/rest/src/RequestHandler.php
@@ -107,9 +108,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
-          $error['error'] = $e->getMessage();
-          $content = $serializer->serialize($error, $format);
-          return new Response($content, 400, array('Content-Type' => $request->getMimeType($format)));
+          throw new BadRequestHttpException($e->getMessage());

This is the main change in this patch. The rest is just fixing the test coverage (which was already planned for).

dawehner’s picture

This is so much cleaner!

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 17: 2813853-17.patch, failed testing.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community

CI error at 13 Jan 2017 at 20:26 CET. Now green again.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 17: 2813853-17.patch, failed testing.

damiankloip’s picture

Status: Needs work » Reviewed & tested by the community

Looks like another random/unrelated test failure.

wim leers’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record, +Needs issue summary update

I'm not sure that the issue summary is honest - there is an API change here for error responses. I think we should have a CR that documents this just in case a third party service is depending on the current format of ['message' => 'THE ERROR MESSAGE']

The consistent introduced by this patch is nice.

wim leers’s picture

Issue summary: View changes
Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs change record, -Needs issue summary update

How is the IS not honest? It says 99% of REST error responses have this normalization:, which is true.

Any actual application running into 400 responses and asserting/parsing them is broken. The errors that you can conceivably run into are 403 or 404 error responses. And those are already using this format:

    // DX: 403 when unauthorized.
    $response = $this->request('GET', $url, $request_options);
    // @todo Update the message in https://www.drupal.org/node/2808233.
    $this->assertResourceErrorResponse(403, '', $response);

…

  protected function assertResourceErrorResponse($expected_status_code, $expected_message, ResponseInterface $response) {
    $expected_body = $this->serializer->encode(['message' => $expected_message], static::$format);
    $this->assertResourceResponse($expected_status_code, $expected_body, $response);
  }

Also note that it's only for some 400 responses, as this existing code in HEAD proves:

    // DX: 400 when no request body.
    $response = $this->request('POST', $url, $request_options);
    $this->assertResourceErrorResponse(400, 'No entity content received.', $response);

But, sure, I've added an explicit mention of the edge case that is now behaving consistently to the API changes section. "Dishonest" is a pretty strong word to use for this simple oversight IMHO.

CR created: https://www.drupal.org/node/2846775.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 5ce4fce and pushed to 8.3.x. Thanks!

diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 141b26a..0e9f41b 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -10,7 +10,6 @@
 use Symfony\Component\DependencyInjection\ContainerAwareTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
 use Symfony\Component\Serializer\Exception\UnexpectedValueException;

Fixed unused use on commit.

  • alexpott committed 5ce4fce on 8.3.x
    Issue #2813853 by Wim Leers: RequestHandler has its own error handling...
alexpott’s picture

Sorry about calling into question the honesty of the issue summary - I was just struggling with how it said none when the problem motivation part made it clear that something was going to change and then I spent time staring at the patch trying to work out what was changing.

wim leers’s picture

No worries. I'd expect inaccurate to be used for that.

  • alexpott committed 5ce4fce on 8.4.x
    Issue #2813853 by Wim Leers: RequestHandler has its own error handling...

  • alexpott committed 5ce4fce on 8.4.x
    Issue #2813853 by Wim Leers: RequestHandler has its own error handling...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.