Problem/Motivation
Discovered while working on #2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only.
While working on the
Blocktest coverage, I found a significant bug in the normalization of config entities. #2733097: [FEATURE] Add GET support for configuration entities added config entityGETsupport, but it did not add any integration test coverage. The only test coverage is\Drupal\Tests\jsonapi\Kernel\Normalizer\JsonApiDocumentTopLevelNormalizerTest::testNormalizeConfig(), but that didn't verify that a config entity'sdependencieskey is exposed correctly.This is what we'd expect:
… 'dependencies' => [ 'theme' => [ 'classy', ], ], …but in reality we get:
… 'dependencies' => [ 'classy', ], …Obviously that's losing very important information!
\Drupal\jsonapi\Normalizer\ConfigEntityNormalizer::serializeField() does:
$output = new FieldNormalizerValue(
[new FieldItemNormalizerValue($field)],
1
);
That 1 means "single cardinality" … which during rasterization results in \Drupal\jsonapi\Normalizer\Value\FieldNormalizerValue::rasterizeValue() being called and this code executing:
if ($this->cardinality == 1) {
assert(count($this->values) === 1);
return $this->values[0] instanceof FieldItemNormalizerValue
? $this->values[0]->rasterizeValue() : NULL;
}
… which then results in only the first value being retained, and this explains the data loss shown above.
Proposed resolution
Fix this in ConfigEntityNormalizer. Details TBD.
Remaining tasks
TBD
User interface changes
None.
API changes
None. (Well, responses for config entities change, but it was impossible until this very issue to use anything under the dependencies key due to this bug!)
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #15 | 2942979-15.patch | 25.23 KB | wim leers |
Comments
Comment #2
wim leersComment #3
e0ipsoWow! That was a good catch. We should add this issue to our current focus points.
Comment #4
wim leersRan into this again in #2932031-24: Comprehensive JSON API integration test coverage phase 2: for every entity type, complex use-cases and #2932031-28: Comprehensive JSON API integration test coverage phase 2: for every entity type, complex use-cases.
I wrote
But @gabesullice correctly pointed out that that doesn't work:
To which I replied:
Comment #5
gabesullice+1Edit: Actually, don't they have data definitions? I thought that they did have definitions when derived from a value, you just can't get them out of thin air.
I.e.
I have not confirmed this fuzzy memory.
Edit #2: I'm still fine just making config an exception as suggested, but am curious what @e0ipso thinks if my memory is accurate
Comment #6
e0ipsoAlthough we can probably derive the desired information based on the config schema, I think that
is a good compromise. Mainly because config entities are as close to normalized data as one can get (that's why we were using
->toArray()to normalize them). We need to make sure to not apply any further processing (like the IS proves).With regards to #5 that is probably a good enhancement to ensure that ill-validated config entities stored in Drupal don't provide bad output. Maybe a follow up for this?
Thoughts?
PS: We should add this to the Core issue MVP if it's not already.
Comment #7
wim leers@gabesullice: You may be right, but if those exist, they're definitely not very precise; they'd be super generic. Actually useful data definitions would only exist if they'd be generated based on the config schema.
@e0ipso
Exactly.
Stored config is NEVER allowed to be invalid. We don't need to do validation when normalizing. We do need to validate when denormalizing. And that doesn't exist yet…
Comment #8
e0ipsoWell for Core inclusion we don't need to worry about that. We already agreed that we won't be supporting unsafe methods until validation is in place, right?
Comment #9
wim leersExactly :)
Comment #10
wim leers#2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only and #2932031: Comprehensive JSON API integration test coverage phase 2: for every entity type, complex use-cases landed, this can now move forward!
Comment #11
wim leersLet's first get some test coverage :) This should fail.
Comment #12
wim leersActually, turns out this is not happening for config entities today anyway. In fact, because JSON API's field normalization architecture is centered on "content entity fields" and JSON API considers the data in config entities to also be "fields" (they aren't!), JSOn API's field normalization architecture is actually what's getting in the way!
For config entities, we don't need ANY post-processing!
In fact, the entire
FieldNormalizerValue+FieldItemNormalizerValuevalue objects to be normalized are exactly what gets in the way for config entities! Because content entities have fields, and fields have items. Those items are keyed by deltas, which are always numerical. In other words: items are always a list with a certain sequence. IOW: the second level always has a numerical key.But for config entities, JSON API treats their data as "fields", but it's possible that that second level has non-numerical keys. The
FieldNormalizerValue+FieldItemNormalizerValuevalue objects do not allow this to be communicated, and this hence gets lost 100% of the time.(I really tried to make it work and then ended up with that conclusion. I should've seen that right away.)
The only way to make it work is to modify
\Drupal\jsonapi\Normalizer\Value\FieldItemNormalizerValue::rasterizeValue. But looking at\Drupal\Core\Entity\Entity,\Drupal\Core\Config\Entity\ConfigEntityInterfaceand\Drupal\Core\Config\Entity\ConfigEntityBase, there's zero mentions of "fields".Therefore I think the best solution is to just massively simplifyDoing this also requires a newConfigEntityNormalizer, and make it as dumb and simple as\Drupal\serialization\Normalizer\ConfigEntityNormalizer.ConfigEntityNormalizerValueclass, because the existingEntityNormalizerValueassumes that it containsFieldNormalizerValueobjects.So perhaps a better solution is to continue to map config entity's data onto "fields" after all, but to introduce a new
\Drupal\jsonapi\Normalizer\Value\FieldNormalizerValueInterfaceimplementation (to overrideFieldItemNormalizerValue::rasterizeValue()as mentioned before, and to also modify the constructor).This approach turned out to be the easiest solution, but not the simplest. (Although it's definitely the simplest patch.) There's a lot of complexity and indirection wrt how config entities are normalized by JSON API … and most of that is arguably unnecessary and definitely unused. However, potentially rearchitecting that is out of scope for this issue.
Comment #15
wim leersThe
ConfigEntityNormalizerTestunit test failed, but it's wrong… in fact, it was documenting exactly what the bug was! Fixed.Also fixed all coding standards violations in the patch.
Comment #16
wim leersBy the way, this broke more than just
dependencies.It broke every case of a single-item array with a non-numerical key.
Comment #17
wim leers#15 is green on both 8.4 and 8.6!
Comment #18
gabesulliceThis looks great!
Comment #19
e0ipsoBoy! Thanks again for catching this one. Merging.
Comment #21
e0ipsoEdit: I posted in the wrong issue.
Comment #22
wim leersYay, glad to have this fixed, with test coverage!