Problem/Motivation
When an error or relationship document is serialized, the standard jsonapimember is not added. This is inconsistent with the serialization of individual, collection and related documents.
Since the jsonapi member can be used to improve client-side tooling and helps communicate support features, we should ensure this member is serialized into every response document.
Proposed resolution
Add the standard jsonapi member to every response document.
Remaining tasks
Test cases.
Untangle HttpExceptionNormalizerValue from FieldNormalizerValue.
Comments
Comment #2
gabesulliceTest only patch.
Comment #3
gabesulliceI removed
link.selffrom the issue title and summary. I no longer think aselflink should be in the error response, because a link implies support for the linked resource.Until we are able to do some logic to determine that we _do_ support the link, we shouldn't be including it. I.e. if we could determine that the error was temporary (like a DB lock), then it should be included, but a link for a bundle which doesn't exist should not be included. We don't have the infrastructure to do that right now and it may not even be possible. Perhaps when the related issue is done, we could reconsider.
Comment #4
wim leersI think #2 is ready for review?Ah, no, it's blocked on probably. But that means #2 should be tested to prove that it fails (although it obviously will, so little point).
Changing to then.
Comment #5
gabesullice@Wim Leers, #2 is a test only patch ;)
Comment #6
wim leersYes, I know. By "being ready for review", I meant it probably should be tested by testbot to prove that it's failing. I explained that it'll obviously fail, so little point in doing so.
Comment #7
gabesulliceI've discovered more places where this is not added to the document. Updated the IS to reflect it.
Comment #8
gabesulliceComment #9
wim leersComment #10
wim leersThe spec says in principle that the top-level
jsonapimember is a … but in practice I think it's a in our implementation, because right now it's behaving inconsistently. This violates the spirit of the spec IMHO.Comment #11
wim leersComment #12
wim leersThe scope of this issue just got reduced, because thanks to #2948666: Remove JSON API's use of $context['cacheable_metadata'], relationship responses now do have the
jsonapitop-level member!Comment #13
gabesulliceDialing down.
Comment #14
wim leers👍
Comment #15
gabesulliceRerolled.
Comment #16
chanduvkp commentedHi,
I am a new for this module. I have patched #15. I appreciate if you give me steps for testing?
Thanks,
Chandu
Comment #17
gabesulliceHi @chanduvkp! I'm glad to see that you're interested in this issue!
#15 only provides tests, it does not actually change any production behavior. I use a phpunit command to run tests, but that can vary per site. What are you trying to do?
Comment #18
wim leersThanks to the work I've been doing yesterday on #2984911: Remove access to the Request object in the normalization process, I had a better sense of how to approach this issue. Attached patch makes tests pass for
Node. It's possible (likely) that more test assertions need to be updated.FYI: the approach could be even simpler if we didn't have the concept of
meta.errors: thenJsonApiDocumentTopLevelcould continue to receive a single set of values, and if they're all exceptions, then it'd result in a JSON API document with only theerrorstop-level member.P.S.: I clearly ran into cacheability-related aspects here, in part helped by #2948666: Remove JSON API's use of $context['cacheable_metadata'], in part a next step to #2948666. This will certainly also help #2929428: [>=8.5] Convert "throw new *HttpException" into "throw new Cacheable*HttpException" where possible!
Comment #19
wim leersTests were already taken care of by @gabesullice in #15!
Comment #21
wim leersGot one detail wrong 😡
Comment #22
wim leersThe one detail was that the
X-Drupal-Cache-Contextsheader is being generated for error responses, but there's an empty set of cache contextsX-Drupal-Cache-Contexts:(which parses to['']). That is what is causing most of the failures in #18, because our test coverage is creating a "merged" response, and in doing so is trying to create a merged response with$cache_contexts = [''], which of course is not a valid cache context, and hence the assertion fails…This fixes that.
Note that this problem will cease to exist with #2929428: [>=8.5] Convert "throw new *HttpException" into "throw new Cacheable*HttpException" where possible, because then exceptions will have associated cache contexts, and hence error responses will also have associated cache contexts.
Comment #24
wim leersFix some one-off failures.
Comment #26
gabesulliceWhat if we made an
ErrorCollectionobject that extends\ArrayIterator(i.e. nothing fancy).With that one could make the case that the single argument that the function receives is its "primary data". That frees us from awkwardly tracking this boolean value and needing these mutually exclusive arguments.
Comment #27
wim leersThe
4xx-responsecache tag is now appearing on responses, and hence also on merged responses. But an individual response being 4xx doesn't cause the collection response to also be 4xx — the collection will still remain a 200 response! This fixes that.Comment #28
wim leersHah, #27 does the same as #2991389-4: Test coverage: relationship response cacheabiliity! We're converging on finding the same problems from different starting points! 😆
Comment #30
wim leersThis should make most tests pass.
The problem is that 4xx responses used to be not cacheable and now they are. Hence
testCollection()'s automatically generated "expected responses" (which are generated from responses to each individual resource expected to be in the collection) are now merging 4xx responses' cacheability too, which is resulting in these test failures.Comment #31
wim leersNote: I'm quite hopeful that #2991389: Test coverage: relationship response cacheabiliity will land before this and will make this patch simpler!
Comment #33
wim leersUpdate
UserTest::testQueryInvolvingRoles()'s expectations.Comment #35
wim leersUpdated
MessageTest::testCollection()'s expectations.Comment #37
wim leersUpdated
RestJsonApiUnsupported's expectations.Comment #39
wim leersAh, and here's the last bit! This should be green! 🙏🙏🙏🙏🙏🙏🙏
(Stupidly, I had indented this code many hours ago because it needed to be scrutinized again. It somehow ended up being added to one of the interdiffs … and hence I'd forgotten about it. Gah!)
Comment #40
wim leers#26: I hadn't forgotten about you/this comment! I just wanted to finish what I started before going onto nice-to-have things like this :)
I tried that — I explained this in #18:
That attempt failed because it detected an "errors only" document by checking that all values in
JsonApiDocumentTopLevelwereHttpExceptionInterfaceinstances. But doing that makes it impossible to e.g. have a collection response with emptydataand onlymeta.errors(because all nodes are unpublished for example).Wouldn't the same happen in your proposal?
Comment #41
wim leersComment #42
gabesulliceNo, I don't think so. The approach discussed in #18 was not able to do
$this->data instanceof ErrorCollectionbecause it still used anEntityCollectionto carry the errors. That made it impossible to determine if it should be an error document or a data document withmeta.errors.In code, what I'm saying is:
$this->data instanceof ErrorCollection === $this->isErrorDocument. IOW, we can use a concrete type to carry the information rather than using two mutually exclusive arguments. Then, exceptions in anEntityCollectionobject must bemeta.errorserrors.Comment #43
wim leersIt wasn't using an
EntityCollectionto carry errors, it was just a plain array ofHttpExceptionInterfaceobjects.(That's not what the current docs for
JsonApiDocumentTopLevelsay, I know, but … PHP is not strictly typed and there's noassert(), so … yeah, plain arrays are already being passed in there in HEAD too!)I like what you said, so I gave it a try … and it works! :) I do slightly prefer your proposal/this patch because it gives us more strict types, rather than array inspection/manipulation logic. I think that as other parts of the logic become stricter typed/cleaned up, the approach introduced here will become gradually simpler to understand.
So: 👍 from me.
Comment #44
gabesulliceThis looks impeccable :) I just have a couple observations.
😗👌
['is_error_document' => TRUE]could be an independent variable. But that's a super nit and only needed if you agree.Isn't this true for all documents, not just resource documents? Perhaps we don't put a
linksobject on error documents?Comment #45
wim leerslinkson error documents. Plus, thelinksinside error objects (http://jsonapi.org/format/#error-objects) are all absolutew3.orgURLs! So at least for now, this is correct.Comment #46
gabesullice2+3: works for me!
Thanks for this awesome work!
Comment #47
wim leersYay!
Since your RTBC though, both #2985426: Spec compliance: `related` routes should return 200, not 403, if field access is allowed but the related resources are forbidden and #2991389: Test coverage: relationship response cacheabiliity have been committed, both of which require changes to be made here.
First, let's rebase. Notable changes:
DefaultExceptionSubscribernow has additional deletions; lines that were only added in #2991389!JsonApiDocumentTopLevelNormalizerValuestill makes the same set of changes, but the existing code now looks a little differentComment #49
wim leersI got #47.1 wrong! Fixed.
If this works, then one of my main reservations about this patch actually is already fully fixed, thanks to #2985426: Spec compliance: `related` routes should return 200, not 403, if field access is allowed but the related resources are forbidden! 😁 🍻
Comment #50
wim leersYay, #49 passed on 8.5/8.6/8.7!
Fixing CS violations.
@gabesullice, please re-confirm RTBC.
Comment #51
gabesulliceComment #53
wim leers🚢
Comment #54
wim leersCreated change record: https://www.drupal.org/node/2991990.