Problem/Motivation
The JSON API specifies that a source pointer may be added to error responses to specify a route in the response JSON to the source of the error in the request. (See http://jsonapi.org/format/#errors ) This would be particularly helpful for validation errors, to indicate in a machine-readable way where the validation failed. Ember.js, for example, utilizes this pointer out-of-the-box to make an error specific to the model's attribute. Not having this pointer specified at all in the response causes the error to not be displayed at all in Ember.js templates.
For example, attempting to update a node without specifying a Title:
{
"data": {
"id": "aadca889-b307-421e-83f0-9bc9df3e56c8",
"attributes": {
"nid": "5",
"uuid": "aadca889-b307-421e-83f0-9bc9df3e56c8",
"created": "1479845577",
"title": "",
"body": {
"value": "The body...",
"format": "basic_html",
"summary": "The summary."
}
},
"relationships": {
"uid": {
"data": {
"type": "user--users",
"id": "349f9a3e-74bd-4782-b6fa-edd19d6f129c"
}
}
},
"type": "node--articles"
}
}
would produce a response like:
{
"errors": [{
"title": "Unprocessable Entity",
"status": 422,
"detail": "Unprocessable Entity: validation failed.\ntitle: This value should not be null.\n",
"source": {
"pointer": "/data/attributes/title"
},
"links": {
"info": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"
},
"code": 0
}]
}
Specifying an invalid text format for the body field would produce a pointer of /data/attributes/body/format. A pointer of /data can be used to indicate an error without a known, specific source.
Proposed resolution
Add a subclass, UnprocessableEntityException that extends \Drupal\jsonapi\Error\SerializableHttpException and holds the validation failures. Delegate normalization/serialization of the error response to SerializableHttpException and UnprocessableEntityException. If more than one validation error occurs, use the non-specific /data pointer. (Alternately, we could create a separate error object in the response's "errors" array for each validation failure.)
Remaining tasks
- Fix TODOs
- Tidy-up patch
- Add/fix tests
User interface changes
None
API changes
TBD
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #19 | interdiff-2832211-15-19.txt | 2.26 KB | hampercm |
| #19 | add_source_pointer-2832211-19.patch | 13.23 KB | hampercm |
Comments
Comment #2
hampercm commentedThis somewhat quick-and-dirty patch mostly implements the feature, with some TODOs. Any feedback is welcome.
Comment #3
hampercm commentedComment #4
e0ipsoThanks for the patch. This will really help consumers to get meaningful server-side form validation.
This is the object to serialize. It should not know how to serialize itself. Instead, the serializer should be the one to do that.
Maybe
UnprocessableHttpEntityExceptionto help avoid confusion?I'd rather have an
addViolation($violation)method in the class than passing the array to the constructor.I'd like to have a normalizer that takes
UnprocessableHttpEntityExceptionand inherits fromHttpExceptionNormalizerto add the pointer.As stated before, let's bring the logic back to this class.
Comment #5
e0ipsoComment #6
hampercm commentedThanks for the review. Working on an updated patch...
Comment #7
hampercm commented- Addressed comment #4.
- The patch now creates a separate JSON API error object for each constraint violation. Each error object has a source pointer for the associated field, if applicable.
- I fixed the issue with the first patch where single-value fields would have a 0 index specified in the source pointer (ex.
/data/attributes/body/0/value). Now, single-value fields don't have any index (ex./data/attributes/body/value), to match the JSON API request's object structure.Comment #8
hampercm commentedComment #9
hampercm commentedComment #10
hampercm commentedKicking off automated tests, which didn't run...
Comment #11
e0ipsoThis is definitely in the good direction! I really like this new feature.
A couple more comments on the current implementation:
Let's add a short class comment explaining when this exception comes into play.
Should this be:
I'm unclear on the specifics at the moment, but that is what the previous code seems to be doing.
Let's add type hinting to the
$exceptionargument.Why are you removing this?
Is there any way ew can ensure not to remove additional
/0/that we're not supposed to remove.Comment #12
e0ipsoWe will need testing for this, to make sure that we adhere to the expectations from the front end libraries.
Also, can you please post the response you get to a serialization error with this patch applied with different user roles? I'm trying to make sure we are not leaking any sensitive information.
Comment #13
hampercm commentedComment #14
hampercm commentedThanks for the review. I (mostly) addressed comment #11:
1. Done. Also added PHPDoc for the HttpExceptionNormalizer class.
2. Good catch. Interestingly, the response output looks the same either way, but I'll revert to be safe.
3. Done.
4. The link doesn't provide any relevant information in this case. The 422 error code is instead defined in the WebDAV spec at https://tools.ietf.org/html/rfc4918#page-78 and unofficially repurposed for this sort of thing. I could link to that standard instead, though having a link to a WebDAV spec might be a bit confusing. Much more relevant information is supplied by the error's title and details, so I'm not sure this link is needed. Thoughts?
5. I've improved the code to make is less likely to wipe out '/0/'s that we don't want removed.
Working on tests...
Comment #15
hampercm commentedThis patch fixes a bug in #14, which caused HttpExceptionNormalizer to not be used, due to an incorrect
$supportedInterfaceOrClass. Also adding an interdiff from the patch before @e0ipso's review.I'm surprised automated tests didn't catch this regression. We should verify that tests for 403 errors exist, and that the JSON response is verified in those cases.
Comment #16
e0ipso@hampercm it seems that we only have functional tests on 403 for write operations. We have, however 403 tests for partial success operations (
// 12. Collection with one access denied).I agree that we should add more testing. Do you want to add that here, or in a follow up?
Comment #17
hampercm commentedI'll add tests for this patch's new functionality, only. Lets put the 403 tests in a separate issue, or a more general Plan to expand tests.
Comment #18
e0ipsoThat sounds good. I think that test coverage is not in bad shape, so a Plan sounds like too much. We probably want just a new ticket.
Comment #19
hampercm commentedAdded tests for the source pointer functionality.
Comment #20
hampercm commentedComment #22
e0ipsoComment #23
e0ipsoI think this is a great DX improvement! Merging.
Comment #25
hampercm commentedThanks!
Comment #27
rpayanmMoving to Drupal core's issue queue.
I'm working on https://www.drupal.org/project/drupal/issues/3122113