Discovered as part of #2953321: Comprehensive JSON API integration test coverage phase 5: nested includes and sparse field sets.
Compound documents require “full linkage”, meaning that every included resource MUST be identified by at least one resource identifier object in the same document. These resource identifier objects could either be primary data or represent resource linkage contained within primary or included resources.
The only exception to the full linkage requirement is when relationship fields that would otherwise contain linkage data are excluded via sparse fieldsets. (emphasis mine)
This implies that if compound documents are to be supported, include should operated independently of fields, meaning it should be possible to include a related resource even if the relationship field is not a member of the request's sparse fieldset.
Proposed Solution
Evaluate includes independently from relationship field normalization because included resources can be resolved and added to JsonApiDocumentTopLevel within EntityResource. Doing this prior to field normalization means that included resources won't depend on fields being in the primary data of the response.
| Comment | File | Size | Author |
|---|---|---|---|
| #8 | 2976108-8.patch | 7.49 KB | wim leers |
| #8 | interdiff.txt | 3.59 KB | wim leers |
| #6 | 2976108-6.patch | 6.37 KB | gabesullice |
| #6 | interdiff-5-6.txt | 3.15 KB | gabesullice |
| #5 | 2976108-5.patch | 6.29 KB | wim leers |
Comments
Comment #2
gabesulliceThis is related to #2946537: Test coverage: Inclusion of intermediate resources when include is a multi-part relationship path because it also has to do with how included resources are added to the document.
Also added a proposed solution to the IS
Comment #3
gabesulliceComment #4
wim leersConfirmed the proposed resolution. This will mean lifting this logic out of
\Drupal\jsonapi\Normalizer\RelationshipItemNormalizer::normalize():and running it independently of relationship normalization.
Comment #5
wim leersObservation: I'm not sure how/if this will be possible, because this (only
includes of visible relationships) has been pretty much an essential basic assumption in the entire normalization architecture. This is whatValueExtractorInterfaceis designed for: a tree of objects implementing this interface allows includes to bubble up from the deepest level to the top level, and to the response.The solution proposed in the IS would change this fundamental assumption. I believe it'd be simpler, or at least more maintainable for now, to stick to the current design.
I believe that's doable by converting
RelationshipNormalizerValuevalue objects to some new object, which has novaluesto be rasterized, but onlyincludes. That'd be perfectly consistent with what we already have.Patch attached. This patch works! We can prove that by enabling the test coverage that #2953321: Comprehensive JSON API integration test coverage phase 5: nested includes and sparse field sets added. In doing so, I noticed that it's not wise to list
preferred_langcodeas one of the fields in the sparse fieldset. Because that particular field is only viewable by the user itself, which means that the access checking adds theusercache context. Replacing it withcreatedstill tests the same principle, while removing the need for complex test alterations.Comment #6
gabesulliceI find this somewhat hard to follow, especially when it ends in an "early return" (the
continue). IOW, I think this can be rearranged.WRT to
$field instanceof EntityReferenceFieldItemListInterface, let's try to reuseResourceType::getRelatableFieldsas much and as often as possible so we don't need to hardcode the ER field, doing so maximizes compatibility with contrib.Nice, this is a shockingly simple way of doing this. Way to go! I was thinking this issue was going to be a slog.
Why not
assert?I made my own suggestions. RTBC if @Wim Leers is +1.
FWIW, I enable the test coverage for #2946537: Test coverage: Inclusion of intermediate resources when include is a multi-part relationship path locally and this does fix it!
Edit: welp, dunno what changed.
Comment #8
wim leersI spent >30 minutes trying to reproduce the test failures; but then I suddenly could. I must've done something wrong, but don't know what!
\Drupal\jsonapi\Normalizer\Value\EntityNormalizerValue::__construct()is pretty hard to follow IMHO.I specifically did
instanceof INTERFACErather thaninstanceof CLASSto maximize contrib compatibility. But I agree that your proposal is better.However… the irony is that I find your code much harder to follow :P There now is a conditional early return (well,
continue), and the same conditions reappear later. And in doing so, you introduced a subtle bug that causes the test failures: a field that is not listed in the sparse fieldset and is a relationship field will never enter the if-test with thecontinuestatement, and if no?includeis specified,$has_includeswill always be false, which means that it'll be included inappropriately.This is the danger of repeating complex conditions multiple times. I prefer the structure I had before, because it stresses how exceptional it is: it is only in an extremely rare case that we need to wrap/decorate a normalization value we get back!
That doesn't mean I don't see your point! I think there's another way to achieve DRY. See interdiff. This does result in some unnecessary work, but optimizing that seems premature optimization TBH :) Simplicity & correctness first, performance second!
Comment #9
gabesullice😅hope it wasn't too frustrating to debug...
This looks good to me :)
Comment #11
wim leersNope, that was fine :) And it's fascinating how different things can be simpler to different people!
Pushed!