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.
| Comment | File | Size | Author |
|---|---|---|---|
| #17 | 2813853-17.patch | 7.07 KB | wim leers |
Comments
Comment #2
wim leersThis means we leave it to
KernelEvents::EXCEPTIONevent subscribers to generate the appropriate response, rather than duplicating it inRequestHandlerin an inconsistent way.Comment #3
wim leersComment #5
wim leersSo there are two cases in Drupal core where we assert this inconsistent error response body is returned. Simple enough to fix.
Comment #6
dawehnerSo all we need here is to adapt the expected error messages and be done?
Comment #7
wim leersBasically, 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.
Comment #8
wim leers#2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method landed!
Comment #9
wim leersAnd, 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.
Comment #11
wim leersThe failures in #9 are related to #2813755: JSON responses encoded inconsistently: make them all RFC4627-compliant, possibly also to #2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json. Postponing.
Comment #12
wim leersYay, #2813755: JSON responses encoded inconsistently: make them all RFC4627-compliant landed!
But… with the additional knowledge I have now, I know for certain the failures are definitely blocked on #2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json (which is in turn blocked on #2739617: Make it easier to write on4xx() exception subscribers), not on #2813755: JSON responses encoded inconsistently: make them all RFC4627-compliant.
Comment #13
wim leers#2739617: Make it easier to write on4xx() exception subscribers landed, #2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json is now up for review. The PP-1 is correct, it should've been PP-2.
Comment #14
wim leersComment #15
wim leers#2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json was committed, this is now unblocked!
Comment #16
wim leersRebased #9. Straight reroll!
Comment #17
wim leersThe patch in #9 forgot to change a few lines in
HalEntityNormalizationTrait, lines that actually even had explicit todo's pointing to this issue!Comment #19
damiankloip commentedYes, 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.
This is the main change in this patch. The rest is just fixing the test coverage (which was already planned for).
Comment #20
dawehnerThis is so much cleaner!
Comment #22
wim leersCI error at 13 Jan 2017 at 20:26 CET. Now green again.
Comment #24
damiankloip commentedLooks like another random/unrelated test failure.
Comment #25
wim leersThis is blocking #2827084: EntityNormalizer::denormalize should not throw UnexpectedValueException, but \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException, so we get a 422 response instead of 400. This has been RTBC for almost two weeks now.
Comment #26
alexpottI'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.
Comment #27
wim leersHow is the IS not honest? It says , 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:
Also note that it's only for some 400 responses, as this existing code in HEAD proves:
But, sure, I've added an explicit mention of the edge case that is now behaving consistently to the section. "Dishonest" is a pretty strong word to use for this simple oversight IMHO.
CR created: https://www.drupal.org/node/2846775.
Comment #28
alexpottCommitted 5ce4fce and pushed to 8.3.x. Thanks!
Fixed unused use on commit.
Comment #30
alexpottSorry 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.
Comment #31
wim leersNo worries. I'd expect to be used for that.
Comment #32
wim leersAlso, yay, #2827084: EntityNormalizer::denormalize should not throw UnexpectedValueException, but \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException, so we get a 422 response instead of 400 is now unblocked :)