Closed (fixed)
Project:
Drupal core
Version:
8.4.x-dev
Component:
serialization.module
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Reporter:
Created:
14 Nov 2016 at 11:25 UTC
Updated:
7 Mar 2017 at 12:00 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
wim leersOops.
Comment #3
larowlanWouldn't this constitute an api break? Consumers would be catching the existing exception only
Comment #4
wim leersIt's still a 4xx response. So this will still end up in the general "error response handling" bucket.
We've changed many of our error responses (status codes, but especially the bodies), and need to, to provide sensible feedback. I think there's no way around that.
Comment #5
dawehnerMaybe @larowlan brings up a good point, let's discuss BC on #2550249-122: [meta] Document @internal APIs both explicitly in phpdoc and implicitly in d.o documentation
Comment #6
wim leersComment #7
larowlanI'm not talking about http error handling, there are other ways in which the serializer is used outside the REST controllers.
I'm talking about something like this:
With this proposed change, all of a sudden you've got uncaught exceptions.
Comment #8
dawehnerI totally believe that semantically 422 is the right exception, see https://tools.ietf.org/html/rfc4918#page-78
What about introducing a new exception subclassing the existing one, which indicates that kind of semantic meaning and use it?
Comment #10
dawehnerLet's fix the remaining failures ...
Comment #11
wim leers#7: ahhh!
@dawehner Well, @larowlan is right that this change would break BC for code that does this:
I think this means we want to wrap every
(de)normalize()call and every(de)serialize()call in a try/catch and convert any exception to a\Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException?Comment #12
dawehner@Wim Leers
Did you had a look at what the patch is actually doing?
Comment #13
larowlanperfect
Comment #14
wim leersThis will change again in #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers, but that's not really a problem.
My question is simply this: what if we just always convert a caught
UnexpectedValueExceptionhere to aUnprocessableEntityHttpException?(That's what I'm getting at in #11.)
In other words:
UnexpectedValueException? Especially if every single normalizer out there (including contrib ones) will be usingUnexpectedValueException.Comment #15
wim leersComment #16
dawehnerWell, the usecase of the serializer is more than just REST. Given that, exceptions of the HTTP layer should not sneak into the serializer level, and the other way round.
Keep the exception in the serializer namespace, allows you to add additional semantic meaning, while both not breaking the API (see #7), and making it useful for others.
This way we could distinct between the 400 and 422 case properly.
Comment #17
wim leersThat's my point exactly! What I'm basically proposing is this:
That'd be even less disruptive, and would achieve the same AFAICT.
Am I missing something?
Comment #18
dawehnerWell, this exception is also thrown when the content is not decodeable in the first place for example. That one though should be a 400 instead of the 422
Comment #19
wim leersOMG, what a design fail in Symfony's serializer :/
Just to confirm what Daniel is saying: see
\Symfony\Component\Serializer\Encoder\JsonDecode::decode(),\Symfony\Component\Serializer\Encoder\JsonEncode::encode(),\Symfony\Component\Serializer\Encoder\XmlEncoder::buildXml()and\Symfony\Component\Serializer\Encoder\XmlEncoder::decode(): they all throw\Symfony\Component\Serializer\Exception\UnexpectedValueException.This means that the Symfony serializer's default exceptions do not allow us to distinguish between exceptions that should cause 400 exceptions and 422 exceptions.
Hence the approach in #10 presents the only possible solution: a new exception that extends the existing one, and defines additional semantic meaning. That way, all existing code continues to work. And code that wants to be aware of exceptions with the semantic meaning of a HTTP 422 response, can do so.
This is that new exception:
Comment #20
wim leersSorry for holding this up, and thanks for the extra clarifications. Now this patch totally makes sense!
Comment #21
wim leersThe right thing to do is to +1 any relevant issues. But all of these issue searches result in zero matches:
And I also browsed Symfony's Serializer component docs. Turns out they're at least as bad, if not worse than Drupal's. No useful information regarding all this there either. That's how I stumbled upon https://api-platform.com/ though, which apparently is a Symfony bundle providing functionality similar to
rest.modulein Drupal 8. It's about a year old. Sorest.modulepredates it by years. Surprisingly, zero mentions of 422 responses there too:https://github.com/api-platform/core/search?utf8=%E2%9C%93&q=Unprocessab... or https://github.com/api-platform/core/search?utf8=%E2%9C%93&q=422.
So, created a new issue: https://github.com/symfony/symfony/issues/20534.
Comment #22
wim leersWhile doing the research for #21, found a problem:
These, and many like it, make sense. No BC break. Because the exception is being replaced by a subclassed exception.
But these do break BC… :(
Comment #24
dawehnerWell the ones in 2) sound like a bug for me. They seem to throw the wrong exceptions?
Comment #25
MixologicComment #26
catchI agree those two exceptions look like a bug, also no-one should be catching InvalidArgumentException?
This issue should have a change notice though - both for the different HTTP response code and the new exception.
Comment #27
dawehnerChange record is written: https://www.drupal.org/node/2828773
Comment #28
catchNeeds a re-roll for 8.3.x
Also given the new exception, I'd prefer to just put this in 8.3.x anyway.
Comment #29
rosinegrean commentedComment #30
rosinegrean commentedRe rolled patch.
Anything else to do here ?
Comment #32
rosinegrean commentedComment #33
dawehner@prics
Well, we would have to agree whether its worth doing this change for 8.2.x itself. I'm not sure whether the slightly better HTTP status code is worth the BC thing. This issue is marked as task, and well, the current major should always just retrieve bug fixes, so I think not committing this to 8.2.x is the way to go.
Comment #34
damiankloip commentedI am not sure why there is an expectation for the serializer to return exceptions that relate to HTTP status codes. The serializer is not the REST module, and it isn't governed by REST implementations. Shouldn't REST module just be returning a 422 if it catches these exceptions? As noted above somewhere, the serialization component upstream also uses these exceptions, which I think it correct - as serialization is not really tied to the HTTP protocol. I would say that the REST module should catch these exceptions as they are and throw the correct HttpException that will return a 422 response.
IMO, this is the only part of the code that this patch should really be making changes to.
Comment #35
dawehner@damiankloip
Well, do you know a way how the serializer can communicate: this is not valid JSON/XML? This is basically the question we ask here.
Comment #36
damiankloip commentedIt does not, but I think the solution to that is to separate how the serializer is called in RequestHnadler maybe? So you would first decode(), then denormalize(), and check each step.
Comment #37
damiankloip commentedComment #38
damiankloip commentedI.e something a bit like this?
Comment #39
dawehnerThat sounds like a good alternative approach! Note: We should still adapt all the tests ...
Comment #40
darrenwh commentedHi, Quite trivial, but Exception is spelt incorrectly here
Comment #42
damiankloip commentedYeah, let's try and get those resource tests passing now. The fails show that this patch is working though, I think. Also fixed the 'exceptin' typo :)
Comment #44
dawehner@damiankloip
That's an interesting approach!
Comment #45
wim leersI first had reservations about putting more logic in
RequestHandler. But it's done for a good reason: to convert an exception to a 400 error response when decoding doesn't work, and to convert to a 422 response when denormalizing doesn't work. This makes a lot of sense.Great insight, @damiankloip! :)
Let's put the exception handling logic in a helper method that we pass either 400 or 422. That will make this code less repetitive and more legible.
Comment #46
damiankloip commentedThanks Daniel, and Wim. A helper sounds good!
Comment #47
damiankloip commentedComment #49
damiankloip commentedThought so!
Comment #50
wim leersWhy should these not also have a similar structure?
Something's wrong in this sentence.
Missing doc lines.
This is consistent with HEAD, but is also a problem, because everywhere else we have
['messsage' => …].However, we have #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers to fix this, and AFAICT doing that will stay equally simple. In fact, in #2813853 we should be able to remove this helper method altogether and throw a 400/422 exception in those
catchstatements above.Comment #51
damiankloip commented1. My thinking was if it's something completely different, we should not send out usual REST response and just let the system handle it. That exception could catch anything. If we catch everything, then do we should default to a 500 response maybe? It's hard to know in a generic way.
2. Yes, that wasn't my best attempt at my native language :)
3. Fixed
4. I'll leave this for now then? I guess we would just throw the appropriate HttpException and be done. It can then we converted to the correct format etc... in the exception subscriber.
Comment #52
wim leersUnexpectedValueExceptionandInvalidArgumentException) that denormalizers might throw?Exactly. That's what #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers is all about :)
Comment #53
damiankloip commented1. Yes, I see what you mean now. This just avoids having another catch block, but doesn't really make the code any cleaner, just further nesting. I'll split it. I am not sure we whether we should use the same responses/exceptions for the generic exception, we would have to default the status code to 500 maybe? There is no real way to reliably determine this.
4. Yes, I agree it is sensible and totally doable. I have changed this patch to throw HttpExceptions instead. It currently requires the boilerplate for the body serialization and the headers, but we could remove that in #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers.
Comment #54
damiankloip commentedMeant to remove these return statements too.
Comment #55
dawehnerCould we use multiple catch statements, see https://3v4l.org/HQ7pZ ?
This method throws an exception, instaed of returnign something
Comment #56
damiankloip commentedYeah, we discussed just using multiple catch statements instead but we are still not sure whether we want to catch everything and assume it's a 422 at that stage (denormalization) or just catch the ones we care about like we are now?
Comment #57
dawehnerThat is a tricky question indeed. For me
UnexpectedValueException|InvalidArgumentExceptionkind of cuts it. If you throw a random different exception it might be caused by something totally else, not caused by the unprocessable entity. Just imagine if the DB connection stops or anything like that, in this case throwing a 422 would be kind of wrong, wouldn't it?Comment #58
damiankloip commentedYeah, that was basically my thoughts on using those two things originally!
Comment #59
damiankloip commentedComment #60
wim leersAgreed! But how do we know there's not additional exceptions that may be thrown that should signify a 422?
AFAICT this is a huge oversight on Symfony's side: the serializer component should have its own exception base interface for this reason.
Comment #61
dawehnerWell, if they should signify, they should be one of those exceptions, indeed. I think though there is not really anything we can help with.
Alternative serialization systems like https://github.com/schmittjoh/serializer/tree/master/src/JMS/Serializer/... seems to deal with that problem at least.
I believe proposing a similar variant upstream could help. There was just one response, maybe others might be more open to that idea.
Comment #64
damiankloip commentedI think we need to just catch these two types for now, otherwise we are not really sure what the problem should be. We could get additional exception that signify 422, but we could also get additional exceptions that are nothing to do with that. I would much prefer to use that serializer! I always have but we already went with the Symfony one because we brought into Symfony in general. That serializer has events and everything.
Comment #65
wim leersThe failing tests here look like this:
That's being fixed in #2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json, which is blocked on #2739617: Make it easier to write on4xx() exception subscribers, which is RTBC :)
Comment #66
wim leersIt's blocked on two issues, not one.
Comment #67
dawehnerComment #68
wim leersAnd #2739617: Make it easier to write on4xx() exception subscribers, so now it's only blocked on one issue anymore.
Comment #69
wim leers#2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json was committed, this is now unblocked!
Comment #70
damiankloip commentedNoted in IRC too, it seems that there is some overlap between the direction this headed in and #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers. It seems like that issue is kind of redundant now we have this one. This one throws the exceptions directly already and has the improved status codes. I think I just referenced the wrong issue in the latest patch. Removing the exception throwing helper method was more dependent on the changes to improved exception responses?
Comment #71
wim leersComment #72
wim leersOh, darn, I see what you mean, @damiankloip!
// @todo … https://www.drupal.org/node/2813853.RequestHandler::handle()But…
errorkey tomessageRequestHandler::handle()and then dos/400/422/in a few places in the tests.So in other words: the concern of #2813853 is , the concern of this issue is
400 -> 422.Which made me realize:
Comment #73
wim leersThis is a straight reroll of #59. Let's see how it does now.
Comment #75
damiankloip commentedThis should wait for #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers now.
Comment #76
wim leersPer #75.
Comment #77
wim leers#2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers landed, this is now unblocked! :)
Straight reroll of #73, with conflicts resolved.
Back to RTBC, all of the actual work here was done by @damiankloip — see #59 and earlier.
Comment #78
wim leersComparing #73 and #77 makes it clear that it was a good idea to wait for #2813853: RequestHandler has its own error handling rather than leaving it to KernelEvents::EXCEPTION event subscribers: the patch now is trivial. :)
Comment #80
xjmThis no longer applies, apparently. The issue was not automatically marked needs work because I had to queue with the 8.4.x run manually. Thanks!
Comment #81
wim leersConflicted with #2838144: Update "bundle missing" error message in entity denormalization to indicate which field is actually missing. Simple conflict to resolve.
Comment #82
wim leersComment #83
damiankloip commentedLooks good, Thanks Wim!
Comment #84
alexpottCommitted d2fdd97 and pushed to 8.4.x. Thanks!
Comment #85
alexpottComment #86
damiankloip commentedDid something happen to this commit? or not get pushed or something?
Comment #88
wim leersAhh there it is :)
Comment #90
wim leersPer #28, this didn't make it into 8.2.x. But sadly, this also didn't make 8.3.x. Alas.