Problem/Motivation
Quoting @plach in #2794431-82: [META] Formalize translations support:
In particular, I think it's concerning that we are silently applying fallback rules while performing update and delete operations. Drupal's HTML UI does apply fallback rules as well, but performs a GET first, so users have a chance to notice that they are dealing with a translation not matching the content language: both on the entity form and the deletion confirmation form this is explicitly signaled. In this case we might be happily performing changes to the wrong target without the client even noticing it, until the next GET.
Additionally, deleting a translation and deleting the whole entity are very different operations, so we can't really apply a DELETE operation to a non-default translation and fall back to the default one if the former is missing, because that would mean deleting all existing translations and the entity itself.
I'm wondering whether a possible mitigation strategy while we figure out a proper way forward (the solution proposed in the IS makes sense to me FWIW), could be to ignore the fallback logic in controllers responsible to handle the PATCH and DELETE requests and return a 404 if an explicit check reveals that there is no translation matching the negotiated content language.
POST operations do not seem as much concerning at first sight, since there is no fallback possible given that no translation exists yet.
Proposed resolution
Add necessary test coverage.
Remaining tasks
Write tests asserting current behavior.- Agree which things (if any) need to change to ensure data integrity.
- Sign-off.
User interface changes
None.
API changes
TBD
Data model changes
None.
Release notes snippet
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #39 | 3038308-39.patch | 21.44 KB | wim leers |
| #39 | interdiff.txt | 2.63 KB | wim leers |
| #38 | 3038308-38.patch | 21.19 KB | wim leers |
| #38 | interdiff.txt | 2.09 KB | wim leers |
| #37 | 3038308-37.patch | 21.19 KB | wim leers |
Comments
Comment #2
wim leersLet's start by addressing the TODO in the pre-existing test coverage.
Comment #3
wim leersAnd let's make it more clear which modules are installed by those tests.
Comment #4
wim leersTests coverage that shows
PATCHing a translation is possible, and it does so without modifying the default translation (i.e. data integrity is preserved).Comment #5
wim leersTranslation fallback time!
Tests coverage that shows
GETting a non-existing translation for an existing langcode returns the fallback translationPATCHing a translation fallback is possible.Comment #6
wim leersPOSTing to a langcode-prefixed JSON:API URL works, but it'll always create the entity in the default translation, even when specifying alangcodefield.Comment #7
wim leersDELETEing a langcode-prefixed JSON:API URL works, but it'll always delete the entity, not the translation.Comment #8
wim leers#2 through #7 added test coverage asserting the current behavior, powered by core's automatic entity route parameter translation.
#2 and #3 are just preparation steps.
How does the current behavior #4, #5, #6 and #7 assert compare to what @plach's concerns are?
GETDELETEDELETEon a translation indeed deletes the entire entity. I think we should change that behavior. This is unambiguously confusing. So I completely agree with @plach that we should make this impossible.405 Method Not Allowedwhen aDELETErequest is performed when it is a non-default translation or when the default translation's langcode does not match$this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId().DELETE /jsonapi/…is allowed, and neverDELETE /xx/jsonapi/….POSTPOSTalways results in an entity in the default translation, even when both a language prefix is specified and thelangcodefield is specified.405 Method Not Allowedwhen aPOSTrequest is performed when a non-default language is negotiatedPOST /jsonapi/…is allowed, and neverPOST /xx/jsonapi/….PATCHa translationPATCHa translation fallbackPATCHed, but it is modifying the fallback, it's not creating the requested translation. Whether that's sensible is another matter of course.PATCHrequests like @plach proposed, this would result in a 4xx response because the client is attempting to modify a non-existing translation.Comment #15
wim leersDiscussed this on a call with @plach, @effulgentsia, @xjm, @gabesullice and I. Outcome:
@plach confirmed this is correct.
@plach confirms this looks good.
BUT we should add test coverage for attempting to change the
langcodefield. en+ca+it languages. en+ca translations exist. PATCH en, set it to 'it'.Correc.t
@plach confirmed that this is wrong/undesirable. Same solution as
POST.Working as expected per @plach: this is how this API is designed to work.
Incorrect per @plach, since this is using the intended API, yet not yielding the expected result.
We agreed that we want to use the same mitigation as for
POST.Also needs test for
POSTing to/jsonapi/node/article, withlangcodebeing set. This should also result in a 405 if it currently is resulting in a non-ca.@plach confirmed that the proposed solution in #8 is what we want to do.
Comment #16
wim leersFirst: fix the tests. I broke this in the second interdiff 😅
Comment #20
gábor hojtsyThe proposal in #8 sounds good to me. I agree that issuing a DELETE request on a non-existent translation DELETE-ing the whole translation set is a concerning data loss issue.
Comment #21
wim leersTurns out this is working as expected, I just had a tiny bug in my test coverage. 😅
I had
but forgot to do:
So a request body was sent without the
langcodefield. That explains it!Conclusion: All
POSTrequests are working as expected, no data integrity issues, so no action needs to be taken.Comment #22
wim leersThis adds the additional
POSTtest coverage @plach asked in #15.7:Thanks to #21's test coverage fix, this works as expected (before I found that, this was failing in just the same way).
Comment #23
wim leersThis adds the additional
PATCHtest coverage @plach asked in #15.2:This is disallowed by
\Drupal\Core\Entity\ContentEntityBase::onChange(): it throws an exception, which @plach explicitly called out during the call in response to a concern raised by @effulgentsia 👍, and now we have test coverage for it ✅It's great when things work as we think they do!
Comment #24
wim leersRespond with a 405 to
DELETErequests that act on translations. Concrete error message:Deleting a resource object translation is not yet supported. See https://www.drupal.org/docs/8/modules/jsonapi/translations.Reason: in HEAD, this is a data integrity issue, since it seems you're deleting a particular translation, but it deletes the entire entity.
Comment #25
wim leersRespond with a 405 to
PATCHrequests that act on translation fallbacks. Concrete error message:The requested translation of the resource object does not exist, instead modify one the translations that do exist: l1, l2, l3.Reason: in HEAD, this is a data integrity issue, since it seems you're patching the requested translation, but you're actually patching another translation: the fallback translation.
Comment #28
wim leersDouble negation oopsie.
Comment #29
gábor hojtsyI did not sleep much last night due to various reason (I see you've been posting stuff at 3am as well). So while the proposed changes and the tests look good, it would be nice to get another pair of eyes like @plach :)
I found these two super minor things :D
Is this a TODO for this patch or later on? If the later, then an URL would be needed to point to.
one of the
Comment #30
gábor hojtsyUpdated title to what is going on :)
Comment #31
wim leers#29:
#30: thanks, much better indeed.
Comment #32
wim leersD'oh:
That wasn't happening in my local environment … ah, apparently #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing just made that happen two days ago! 😬
Fixed. Tested locally on both 8.6 and 8.7.
Comment #33
wim leersApparently a JSON:API kernel test is suddenly failing on 8.7 only, not on 8.6:
That wasn't happening in my local environment … ah, apparently #2976035: Entity type CRUD operations must use the last installed entity type and field storage definitions broke this 17 hours ago! 😬GAaaah. That seems like a pretty significant BC break in core? :/ Investigating…
Comment #34
wim leersYep, confirmed that this is only failing because that broke JSON:API's tests on 8.7: #2976035-122: Entity type CRUD operations must use the last installed entity type and field storage definitions .
Comment #35
wim leersNote that #32 is passing on 8.6, and once #2976035-122: Entity type CRUD operations must use the last installed entity type and field storage definitions is fixed, it'll also pass on 8.7. The failures are 100% unrelated to this patch.
Comment #36
wim leers#3038604: Fix tests following a change in Drupal 8.7.0: #2976035 fixed those failures. Re-testing #32.
Comment #37
wim leers@plach just pointed out in chat:
For
$document_cafr,/ca-fr/jsonapi/…is requested. Good!For
$document_ca,/ca/jsonapi/…is requested. Oops! This is a small typo in the test coverage, but precisely becauseca-frfalls back toca, all this needs is a URL change :)::testPatchTranslation(). Did that.Comment #38
wim leersFix CS violations.
Comment #39
wim leers@plach just pointed out in chat:
DELETErequests (resulting in 405 responses) that aDELETErequest on the non-prefixed URL still works.While doing the latter, I fixed one small oversight to ensure the correct error response is being asserted.
Comment #40
plachNit: this should say something like "But modifying the language of a non-default translation is still not allowed".
Comment #42
wim leers#40: WFM, did that on commit!