Problem

Every request handled by a JSON:API route results in an instance of JSON:API's ResourceResponse class. This class implements the CacheableResponseInterface. This response object is returned for routes and HTTP methods that are uncacheable. When Drupal core encounters a response object that implements the CacheableResponseInterface it checks to ensure that no "cacheable metadata was leaked" and if it detects this scenario it throws a LogicException. This can happen when a module does a number of things, but it is most frequently associated with rendering an entity or field in a contrib/custom module and failing to "capture" cache-affecting side-effects of that rendering process. The "proper" way to do this would be to capture that metadata and then associate it with the response object so that Drupal core can eventually cache that response appropriately.

The LogicException that Drupal core throws is a good thing. Improperly cached response can have nasty consequences (think access bypass vulnerabilities). However, there are many cases in which the HTTP response should be uncacheable. For example, HTTP PATCH, POST, and DELETE methods. Thus, there is an entire class of requests which result in LogicExceptions entirely unnecessarily.

Steps to Reproduce

  • Standard Drupal install
  • Enable JSON:API module and configure to accept all methods (GET, POST, PATCH, DELETE)
  • Have a module that leaks cacheable metadata. For example, triggering URL rendering in hook_entity_presave().
  • Attempt to make a non HEAD/GET request and the LogicException will trigger.

Proposed resolution

If JSON:API were to no longer return a response object that implements CacheableResponseInterface it would not only be semantically correct, it would immediately prevent a number of erroneous exceptions from being thrown. How can we do that?

  1. Move the logic in ResourceResponse that implements CacheableResponseInterface to a CacheableResponseTrait
  2. Stop implementing CacheableResponseInterface on the ResourceResponse class.
  3. Create a new CacheableResourceResponse class that extends ResourceResponse
  4. use CacheableResponseTrait in the new CacheableResourceResponse
  5. Comb through JSON:API's codebase and convert all response objects to CacheableResourceResponse wherever the response is actually cacheable. Keep ResourceResponse in all other cases.

This is identical to how the REST module approached the same situation: https://www.drupal.org/project/drupal/issues/2626298.

A workaround that can be used until this issue lands is https://www.drupal.org/project/jsonapi_earlyrendering_workaround.

Release note

Prior versions of the JSON:API module incorrectly returned ResourceResponse objects that implemented CacheableResponseInterface for all of its responses, even uncacheable ones. This caused avoidable fatal errors under specific conditions.

In order for JSON:API to be more nuanced about cacheable vs. uncacheable responses, a new class has been introduced: CacheableResourceResponse. This class implements CacheableResponseInterface. Accordingly, Drupal\jsonapi\ResourceResponse no longer implements CacheableResponseInterface.

See https://www.drupal.org/node/3163310 for more details

CommentFileSizeAuthor
#87 interdiff-74-87.txt2.6 KBJordanDukart
#87 jsonapi-3072076-87.patch24.75 KBJordanDukart
#74 jsonapi-3072076-74.patch25.5 KBJordanDukart
#73 jsonapi-3072076-73.patch25.5 KBJordanDukart
#72 jsonapi-3072076-72.patch25.51 KBJordanDukart
#60 jsonapi-3072076-60.patch25.48 KBJordanDukart
#57 jsonapi-3072076-57.patch22.77 KBJordanDukart
#57 interdiff_52-57.txt3.24 KBJordanDukart
#50 jsonapi-complete-3072076-50.patch25.38 KBJordanDukart
#50 jsonapi-testsonly-3072076-50.patch3.94 KBJordanDukart
#50 interdiff_42-50.txt2.48 KBJordanDukart
#42 jsonapi-testsonly-3072076-42.patch4 KBJordanDukart
#42 jsonapi-complete-3072076-42.patch25.44 KBJordanDukart
#41 jsonapi-tests-3072076-41.patch25.44 KBJordanDukart
#40 jsonapi-tests-3072076-40.patch25.44 KBJordanDukart
#32 3072076-31.patch21.45 KBcgoffin
#29 3072076-29.patch21.18 KBpatrickfweston
#21 3072076-21.patch21.36 KBgabesullice
#21 interdiff-20-21.txt1.41 KBgabesullice
#20 3072076-20.patch21.13 KBlogickal
#19 3072076-19.patch95.67 KBlogickal
#18 3072076-18.patch95.39 KBlogickal
#16 3072076-16.patch25.53 KBlogickal
#13 3072076-13.patch32 KBlogickal
#10 3072076-9.patch169.86 KBlogickal
#5 3072076-5.patch29.4 KBlogickal
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

logickal created an issue. See original summary.

gabesullice’s picture

Thank you for the excellent bug report @logickal! @logickal++

Obviously, it would seem that the real issue might indeed be Views (why render the whole view like this for field validation?)

I agree that this is probably the root cause of the bug.

...or even in the caching system as a whole (why hit these cache problems when doing a PATCH?) (emphasis added)

I completely agree that this is WTF bug. There's no good reason to unnecessarily fail on an uncacheable response. I see two ways to handle it:

  1. Create a child class of Drupa\jsonapi\ResourceResponse called CacheableResourceResponse and only use it for actually cacheable responses
  2. Find where the 500 error is thrown elsewhere in core and only throw the response-ruining exception for cacheable HTTP methods.

I'll let @Wim Leers weigh in. He might have a third way.

Wim Leers’s picture

Obviously, it would seem that the real issue might indeed be Views (why render the whole view like this for field validation?)

I want to agree this is problematic. But I don't think it is. For \Drupal\views\Plugin\EntityReferenceSelection\ViewsSelection::validateReferenceableEntities() to be able to validate, it needs to know which entities are listed by the view, so it needs to run the view.

  1. Create a child class of Drupa\jsonapi\ResourceResponse called CacheableResourceResponse and only use it for actually cacheable responses

+1 :)

Wim Leers’s picture

FYI: REST already does what you suggested and what I just +1'd:

  • \Drupal\rest\ResourceResponse
  • \Drupal\rest\ModifiedResourceResponse

This was implemented in #2626298: REST module must cache only responses to GET requests. We should essentially do the same for JSON:API.

logickal’s picture

Thanks @gabesullice and @Wim Leers for the feedback and direction on this. Attaching a first pass at a patch - I'm certain I don't have all of the necessary adjustments made to the tests, but the appropriate Kernel tests pass locally.

logickal’s picture

Status: Active » Needs review
logickal’s picture

So, obviously - I specified this issue as 8.7.x but then built the patch on 8.8.x. I could rebase this when I have some more cycles or we can just change the version on the issue. Also as I suspected, there are still issues with the functional tests, I will try to address those next.

logickal’s picture

Version: 8.7.x-dev » 8.8.x-dev
Wim Leers’s picture

Thanks for working on this, @logickal! 🙏

logickal’s picture

Ok, I got my class inheritance completely wrong in the first patch. Along with fixing that, I've started making adjustments to the functional tests to get them to match reality with this change. Let's see what the testbot thinks....

Wim Leers’s picture

It looks like that patch includes lots of unrelated changes 😅

logickal’s picture

logickal’s picture

Ok, THIS patch should have the correct changes.

The last submitted patch, 10: 3072076-9.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 13: 3072076-13.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

logickal’s picture

Status: Needs work » Needs review
FileSize
25.53 KB

Ok, locally I think I fixed the issue with the failing tests, which was a missed addCacheableDependency() inside EntityResource::createIndividual(). Let's give ol' TestBot a whirl.

gabesullice’s picture

Title: Patching Entities with Entity Reference fields using Views fail to handle cache metadata » JSON:API returns a CacheableResponseInterface instance for non-cacheable methods; causes unnecessary exceptions.
Status: Needs review » Needs work
Issue tags: -D8 cacheability +Needs issue summary update

Thanks for this! This is 90% there. I have a few small nits, a couple remarks and one last medium-sized thing to add.

I think this patch covers a broader scope than the original issue summary and so I think we can clean it up a bit. Also, we should have a regression test to prove the bug we're fixing.

My first thought (IOW, it's off the cuff and if you have a better way, do it!) is to make a test module that implements hook_entity_presave() and hook_entity_predelete() and within those functions, we do something that'll bubble cacheability (like render a Drupal\Core\Url). Then, the regression test can assert 2xx level responses for HEAD|GET|POST|PATCH|DELETE. The tests should fail for everything except HEAD/GET before this patch is applied and should pass afterward.

🙏 I so appreciate your help @Logickal!

  1. +++ b/core/modules/jsonapi/src/CacheableResourceResponse.php
    @@ -0,0 +1,28 @@
    + * Extends Resource Response with Cacheability.
    

    Nit: no need to capitalize anything after Extends in this sentence.

  2. +++ b/core/modules/jsonapi/src/CacheableResourceResponse.php
    @@ -0,0 +1,28 @@
    + * to the REST module's Modified Resource Response.
    

    Same. This should be modified resource response or ModifiedResourceResponse without any spaces.

  3. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -195,7 +196,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ent
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    
    @@ -290,7 +290,7 @@ public function createIndividual(ResourceType $resource_type, Request $request)
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    
    @@ -491,7 +491,7 @@ protected function executeQueryInRenderContext(QueryInterface $query, CacheableM
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    
    @@ -569,7 +573,7 @@ public function getRelationship(ResourceType $resource_type, FieldableEntityInte
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    
    @@ -638,7 +642,7 @@ public function addToRelationshipData(ResourceType $resource_type, FieldableEnti
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    
    @@ -719,7 +723,7 @@ protected function doPatchMultipleRelationship(EntityInterface $entity, array $r
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    
    @@ -970,7 +974,7 @@ protected static function relationshipResponseRequiresBody(array $received_resou
    -   * @return \Drupal\jsonapi\ResourceResponse
    +   * @return \Drupal\jsonapi\CacheableResourceResponse
    

    These can all remain ResourceResponse since CacheableResourceResponse is inherited from ResourceResponse.

    Also, many of these renames are on methods that can only ever return instances of ResourceResponse. I'm guessing this is just a leftover from some automatic refactoring in your editor :)

  4. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -520,7 +520,9 @@ function (EntityInterface $entity) {
    -    $response->addCacheableDependency($entity);
    +    if ($response instanceof CacheableResourceResponse) {
    +      $response->addCacheableDependency($entity);
    +    }
    
    @@ -553,7 +555,9 @@ public function getRelationship(ResourceType $resource_type, FieldableEntityInte
    -    $response->addCacheableDependency($entity);
    +    if ($response instanceof CacheableResourceResponse) {
    +      $response->addCacheableDependency($entity);
    +    }
    

    👌 Good catch, even though these are in "get" methods on the class, they can be called from non-cacheable methods too.

  5. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -980,14 +984,22 @@ protected function buildWrappedResponse($data, Request $request, IncludedData $i
    +    if ($request->isMethod('GET') || $request->isMethod('HEAD')) {
    

    We should use $request->isMethodCacheable() here.

  6. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -980,14 +984,22 @@ protected function buildWrappedResponse($data, Request $request, IncludedData $i
    +      $response->addCacheableDependency($cacheability);
    +
    +    }
    ...
    +      $response = new ResourceResponse($document, $response_code, $headers);
    +
    +    }
    

    Nit: there are some extra newlines here.

  7. +++ b/core/modules/jsonapi/src/EventSubscriber/DefaultExceptionSubscriber.php
    @@ -58,7 +58,7 @@ public function onException(GetResponseForExceptionEvent $event) {
    -    $response = new ResourceResponse(new JsonApiDocumentTopLevel(new ErrorCollection([$exception]), new NullIncludedData(), new LinkCollection([])), $exception->getStatusCode(), $exception->getHeaders());
    +    $response = new CacheableResourceResponse(new JsonApiDocumentTopLevel(new ErrorCollection([$exception]), new NullIncludedData(), new LinkCollection([])), $exception->getStatusCode(), $exception->getHeaders());
    

    We can conditionally return a CacheableResourceResponse here with:

    if ($exception->getRequest()->isMethodCacheable()) {...}

  8. +++ b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -120,10 +121,12 @@ protected function renderResponseBody(Request $request, ResourceResponse $respon
    -      assert($jsonapi_doc_object instanceof CacheableNormalization);
    -      $response->addCacheableDependency($jsonapi_doc_object);
    -      // Finally, encode the normalized data (JSON:API's encoder rasterizes it
    -      // automatically).
    +      if ($response instanceof CacheableResourceResponse) {
    +        assert($jsonapi_doc_object instanceof CacheableNormalization);
    

    It's a little odd, but let's keep the assert outside of the conditional block. The assert is here not to validate that the normalization is safe to be passed to addCacheableDependency, but rather to enforce the contract that JSON:API serializer is only allowed to return CacheableNormalization objects.

  9. +++ b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -120,10 +121,12 @@ protected function renderResponseBody(Request $request, ResourceResponse $respon
    +        // Finally, encode the normalized data (JSON:API's encoder rasterizes it
    +        // automatically).
    +      }
    

    Nit: let's leave this comment outside of the conditional.

logickal’s picture

Thanks @gabesullice!

1. Fixed.
2. Fixed.
3. Overzealous docblock type renames? Me? :p Fixed.
4. Yep, it’s been a few days, but I’m pretty sure I found this specifically because the methods were called in the context of testPatchIndividual…
5. I swear I had this same thought, must not have circled back on it. Fixed.
6. Ooops. Fixed.
7. It doesn’t look to me like HttpException comes with getRequest(), but maybe I’m missing something? If it’s not possible to determine if the request is actually cacheable, for now I’m turning that back into a ResourceResponse, but I’m honestly not sure if that’s the right course of action.
8. Fixed.
9. Fixed.

I haven’t yet started yet with the regression test, I’ll pick that up the next opportunity I have and start diving in there.

logickal’s picture

That solution to item 7 didn't work, looks like it needs to be a CacheableResourceResponse, at least as how the tests are written now. Re-rolled putting that item back the way it was until we can have further discussion.

logickal’s picture

Last patch came along with some unintended rebase artifacts.

gabesullice’s picture

+++ b/core/modules/jsonapi/src/EventSubscriber/DefaultExceptionSubscriber.php
@@ -58,7 +59,7 @@ public function onException(GetResponseForExceptionEvent $event) {
-    $response = new ResourceResponse(new JsonApiDocumentTopLevel(new ErrorCollection([$exception]), new NullIncludedData(), new LinkCollection([])), $exception->getStatusCode(), $exception->getHeaders());
+    $response = new CacheableResourceResponse(new JsonApiDocumentTopLevel(new ErrorCollection([$exception]), new NullIncludedData(), new LinkCollection([])), $exception->getStatusCode(), $exception->getHeaders());

#18.7: 🙈 my bad! I pointed you in the wrong direction. GetResponseForExceptionEvent has a getRequest method. My example should have been $event->getRequest() not $exception->getRequest(). Fixed.

Other than that, I think everything else is good to go once we have a test. Fantastic work @logickal!

Wim Leers’s picture

Thanks for pushing this forward! 👏

+++ b/core/modules/jsonapi/src/CacheableResourceResponse.php
@@ -0,0 +1,28 @@
+class CacheableResourceResponse extends ResourceResponse implements CacheableResponseInterface {
  1. \Drupal\jsonapi\ResourceResponse already does class ResourceResponse extends Response implements CacheableResponseInterface.
  2. I would understand this if it were also changing ResourceResponse to no longer implement this interface.
  3. Right now, it's confusing that both the non-cacheable resource response and the cacheable one both implement this interface.
logickal’s picture

@Wim Leers - We are indeed actually changing ResourceResponse:

 /**
@@ -22,9 +20,7 @@
  *
  * @see \Drupal\rest\ModifiedResourceResponse
  */
-class ResourceResponse extends Response implements CacheableResponseInterface {
-
-  use CacheableResponseTrait;
+class ResourceResponse extends Response {
Wim Leers’s picture

D'oh. I'd swear dreditor wasn't showing this?! 😨Can't be. My bad! Please ignore all of #22 😅 I agree the key remaining thing is test coverage then!

+++ b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php
@@ -121,9 +122,11 @@ protected function renderResponseBody(Request $request, ResourceResponse $respon
+      if ($response instanceof CacheableResourceResponse) {

Nit: why hardcode this to the concrete class instead of checking the interface? \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber::flattenResponse() also doesn't do this.

Wim Leers’s picture

gabesullice’s picture

WHOA, what a blast from the past! That should already have been closed by #2984964: JSON API + hook_node_grants() implementations: accessing /jsonapi/node/article as non-admin user results in a cacheability metadata leak. I went ahead and did that.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

bradjones1’s picture

Issue tags: +DrupalCon Amsterdam 2019

I just put this on a project and I think it's enough a WTF it would be great to get in sooner rather than later. Tagging for sprints at DrupalCon Amsterdam. Thanks everyone for your work on this! I was relieved to find it in the issue queue rather than scratching my head in bewilderment.

patrickfweston’s picture

I modified this for a project running on 8.7.x and the patch in #21 just needed a slight update to work. I've attached what works for me on 8.7.8.

cgoffin’s picture

On it during contribution day at DrupalCon Amsterdam 2019. Trying to recreate the patch against the 8.9.x-dev version.

scuba_fly’s picture

I will be helping with this issue during DrupalCon Amsterdam 2019 as mentor.

cgoffin’s picture

I refactored the patch against 8.9.x. Here it is.

cgoffin’s picture

Status: Needs work » Needs review
nehiryeli’s picture

The patch on #32 is working on 8.9.x, I've tested it.

The last submitted patch, 29: 3072076-29.patch, failed testing. View results

rachel_norfolk’s picture

Issue tags: -DrupalCon Amsterdam 2019 +Amsterdam2019

retagging

nehiryeli’s picture

The patch on #32 is working on 8.9.0-dev, I've tested it.

scuba_fly’s picture

@nehiryeli Thank you very much for the work you have done.
I've seen that you did the steps to reproduce the issue, applied the patch and tested that it now works.

@cgoffin Thanks you very much for the rewriting of the patch. I see that it passes now :)

logickal’s picture

Thanks to everyone who has jumped in on this during DrupalCon! Unfortunately, I have been a blocker for the AC on this, as I have not been able to refocus on getting a test written. If anyone wants to take a stab at writing a test per Gabe's comment in #17, please do! (see #3072076-17: JSON:API returns a CacheableResponseInterface instance for non-cacheable methods; causes unnecessary exceptions. )

JordanDukart’s picture

JordanDukart’s picture

JordanDukart’s picture

Apologies, first Drupal core issue I've worked on so missed rolling a tests only patch. Included here with the complete to show the fail then succeed.

JordanDukart’s picture

Status: Needs review » Needs work

The last submitted patch, 42: jsonapi-testsonly-3072076-42.patch, failed testing. View results

JordanDukart’s picture

Status: Needs work » Needs review
bradjones1’s picture

@JordanDukart Congrats and THANKS for your first core patch with a test!

borisson_’s picture

Status: Needs review » Needs work
Issue tags: +ContributionWeekend2020

I have a smal remark about the test. I'm not sure about the rest of the code, I don't know the JSON:API subsystem well enough.

  1. +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php
    @@ -1240,4 +1240,67 @@ public function testAliasedFieldsWithVirtualRelationships() {
    +    $this->assertTrue($this->container->get('module_installer')->install([
    +      'jsonapi_test_non_cacheable_methods',
    +    ], TRUE), 'Installed modules.');
    

    I don't think we need to assert that this returns true. We don't need to test the module installer here.

  2. +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php
    @@ -1240,4 +1240,67 @@ public function testAliasedFieldsWithVirtualRelationships() {
    +    $methods = [
    +      'HEAD',
    +      'GET',
    +      'PATCH',
    +      'DELETE',
    +    ];
    ...
    +    foreach ($methods as $method) {
    +      $response = $this->request($method, Url::fromUri('internal:/jsonapi/node/article/' . $node->uuid()), $non_update_delete_request_options);
    +      $this->assertSame($method === 'DELETE' ? 204 : 200, $response->getStatusCode());
    +    }
    

    This looks like it could benefit from using a @dataProvider instead of doing it like this.

    This adds too much logic to the test class I think.

JordanDukart’s picture

As I mentioned before this my first core patch, and into testing with 8 at least. The asserting on the module install is all over the existing regression tests which I was cribbing from so that is easily removed.

Haven't explored @dataProvider too much. If I'm understanding correctly you are suggesting providing a separate test function with that annotation to feed in a method and its response code?

borisson_’s picture

Yeah, but as I think about it more, it probably shouldn't do that, because this is a functional test and that would slow them down a lot, so all you'd need to change is the other remark.

JordanDukart’s picture

JordanDukart’s picture

Status: Needs work » Needs review
JordanDukart’s picture

Issue tags: -Needs tests
gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

This looks great! Thank you @JordanDukart! Thanks for helping with the review @borisson_. I agree with your assessment about the data provider. I'm ambivalent about the $this->assertTrue() around the module installer. I think it's nice to have that there in case the module ever gets renamed or something. Then the test would fail with a really clear error message whereas it might be a little unclear otherwise.

I don't think that difference of opinion is worth holding this patch up though, so RTBC! Great work everyone!

The last submitted patch, 50: jsonapi-testsonly-3072076-50.patch, failed testing. View results

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/jsonapi/src/CacheableResourceResponse.php
    @@ -0,0 +1,28 @@
    + * @see https://www.drupal.org/project/jsonapi/issues/3032787
    

    Not sure why we're pointing to the jsonapi project from core. I guess this is c&p from ResourceResponse - I think there should be a follow-up to address these links - they are all over jsonapi.

  2. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -567,7 +569,9 @@ public function getRelationship(ResourceType $resource_type, FieldableEntityInte
    +    if ($response instanceof CacheableResourceResponse) {
    +      $response->addCacheableDependency($entity);
    +    }
    
    @@ -994,14 +998,20 @@ protected function buildWrappedResponse(TopLevelDataInterface $data, Request $re
    +    else {
    +      $response = new ResourceResponse($document, $response_code, $headers);
    +    }
    

    So what's interesting here is that ResourceResponse implements CacheableResponseInterface so this is pretty messy and we're not checking on the interface. Did we consider going the other way around? Ie creating an UncacheableResourceResponse? Or removing CacheableResponseInterface and the trait from ResourceResponse?

  3. +++ b/core/modules/jsonapi/src/ResourceResponse.php
    @@ -2,8 +2,6 @@
    -use Drupal\Core\Cache\CacheableResponseInterface;
    -use Drupal\Core\Cache\CacheableResponseTrait;
     use Symfony\Component\HttpFoundation\Response;
     
     /**
    @@ -22,9 +20,7 @@
    
    @@ -22,9 +20,7 @@
      *
      * @see \Drupal\rest\ModifiedResourceResponse
      */
    -class ResourceResponse extends Response implements CacheableResponseInterface {
    -
    -  use CacheableResponseTrait;
    +class ResourceResponse extends Response {
    

    Ah I see we've done that. Nice. So the point above can be ignored but it'll be great to change conditions like f ($response instanceof CacheableResourceResponse) { to test against the interface and not the concrete class.

  4. +++ b/core/modules/jsonapi/src/Controller/EntryPoint.php
    @@ -124,7 +124,7 @@ public function index() {
    -    $response = new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([]), new NullIncludedData(), $urls, $meta));
    +    $response = new CacheableResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([]), new NullIncludedData(), $urls, $meta));
         return $response->addCacheableDependency($cacheability);
    

    Yeah this route is only for GETs - $entry_point->setMethods(['GET']);. Looks good.

  5. +++ b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php
    @@ -121,9 +122,11 @@ protected function renderResponseBody(Request $request, ResourceResponse $respon
           // Having just normalized the data, we can associate its cacheability with
           // the response object.
           assert($jsonapi_doc_object instanceof CacheableNormalization);
    -      $response->addCacheableDependency($jsonapi_doc_object);
           // Finally, encode the normalized data (JSON:API's encoder rasterizes it
           // automatically).
    +      if ($response instanceof CacheableResourceResponse) {
    +        $response->addCacheableDependency($jsonapi_doc_object);
    +      }
    

    The comment above the new code doesn't apply to it. Why has it moved position?

    Also I think maybe we only need to do the assert about CacheableNormalization if $response is a CacheableResourceResponse.

JordanDukart’s picture

Assigned: Unassigned » JordanDukart

Will roll a patch to address the above.

JordanDukart’s picture

Patch and interdiff attached.

alexpott’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 57: jsonapi-3072076-57.patch, failed testing. View results

JordanDukart’s picture

Patch in #57 was incomplete. Re-attaching / running tests.

JordanDukart’s picture

Status: Needs work » Needs review
JordanDukart’s picture

Assigned: JordanDukart » Unassigned
gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

#55:

1. Good call. Done: #3122113: Convert all PHPDoc links targeting JSON:API contrib issues to target Drupal core issues.
2.
3. Good call. It looks like @JordanDukart has already done that too 👌
4. 👍
5. #57 addresses this too. Thanks @JordanDukart!

gabesullice’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 60: jsonapi-3072076-60.patch, failed testing. View results

gabesullice’s picture

Status: Needs work » Reviewed & tested by the community

This was kicked back to needs work for an unrelated test failure. I queued a retest and it's green again. Back to RTBC :)

tolu_’s picture

If you are still pulling your hair out after #60, you might need this https://www.drupal.org/project/jsonapi_earlyrendering_workaround

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

xjm’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

It's great to see all the collaboration on this issue and the patience over time.

The latest patch no longer applies to 9.1.x, so it needs a reroll. It also could use that IS update so that reviewers can follow more easily.

xjm’s picture

Saving issue credits, including for the mentor and participants who worked on the issue during the sprint. Usually we like to see comments that are a bit more detailed about how you review the issue, but since your mentor vouched for your work I'm adding credit. :)

JordanDukart’s picture

Assigned: Unassigned » JordanDukart

Will roll the 9.1 patch, not entirely sure how the IS should be re-written but will bring up in the API-First meeting.

JordanDukart’s picture

JordanDukart’s picture

Missed the deprecation in https://www.drupal.org/node/3072702, another patch.

JordanDukart’s picture

Garbled patch in 73, rectifying.

ravi.shankar’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
JordanDukart’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update
JordanDukart’s picture

Assigned: JordanDukart » Unassigned
gabesullice’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Thanks for all the effort and shepherding of this issue @JordanDukart! This patch looks perfect to me. I've updated the IS by providing more context and description of these changes.

gabesullice’s picture

Issue summary: View changes
rachel_norfolk’s picture

Heh - I just used a screenshot of this issue on the contributions page for DrupalCon Global 2020, as an example of a real, live open issue. It looks might it might even be closed before the event - good going everyone!

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/modules/jsonapi/src/CacheableResourceResponse.php
    @@ -0,0 +1,28 @@
    + * where the response is expected to be cacheable.  This approach is similar
    + * to the REST module's ModifiedResourceResponse.
    

    I'm not sure referencing what Rest module does is relevant enough to warrant a comment in the code that may need to be removed in the future, fair enough here on the issue, but in the code?

  2. +++ b/core/modules/jsonapi/src/CacheableResourceResponse.php
    @@ -0,0 +1,28 @@
    + * @see https://www.drupal.org/project/jsonapi/issues/3032787
    

    alexpott pointed this out earlier too - why are we linking to an issue in json api module?

  3. +++ b/core/modules/jsonapi/src/Controller/EntityResource.php
    @@ -994,14 +999,20 @@ protected function buildWrappedResponse(TopLevelDataInterface $data, Request $re
    +      $response->addCacheableDependency($cacheability);
    

    nit: we could return early here and avoid an else

  4. +++ b/core/modules/jsonapi/src/ResourceResponse.php
    @@ -22,9 +20,7 @@
    +class ResourceResponse extends Response {
    

    this is a BC break - there is possibly downstream code that assumes the previous interface was implemented I think we'd need to do the opposite, i.e. keep the existing behaviour but add a uncacheable flavour

    I realise alex also flagged this above and was OK with it, but I'm not sure its the right approach for BC, will ping him to discuss

    None of the other feedback warrants a new patch, so just putting to needs review till I can discuss with him

alexpott’s picture

Re #81.4 The class has the following documentation - (dreditor-- for lacking context).

 * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
 *   may change at any time and could break any dependencies on it.

It was added to core with this documentation

I think we should only make this change in minor but I think we can make the change this way otherwise our documentation is not true.

larowlan’s picture

Thanks Alex, I'd missed that.

This is 9.1 only then

Can we get a release notes snippet and change notice here

gabesullice’s picture

mglaman’s picture

+1 to the CR

JordanDukart’s picture

Assigned: Unassigned » JordanDukart
Status: Needs review » Active

Will handle #81.1-3.

JordanDukart’s picture

RE: #81.1
Will remove this reference.

#81.2
@alexpott pointed this out above and it spawned https://www.drupal.org/project/drupal/issues/3122113. Will update the referenced links in this patch to point to Drupal.

#81.3
Will return early.

Interdiff + patch attached. Re-rolled some things as my previous patch in #74 no longer applied cleanly in 9.1.x see: https://www.drupal.org/project/drupal/issues/3133033 and https://www.drupal.org/project/drupal/issues/3138766.

JordanDukart’s picture

Assigned: JordanDukart » Unassigned
Status: Active » Needs review
gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for addressing #81 @JordanDukart! This looks good to me.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 87: jsonapi-3072076-87.patch, failed testing. View results

JordanDukart’s picture

Status: Needs work » Reviewed & tested by the community

Unrelated test fail.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 87: jsonapi-3072076-87.patch, failed testing. View results

JordanDukart’s picture

Status: Needs work » Reviewed & tested by the community

Another unrelated test fail.

catch’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Fixed
Issue tags: -Needs release note

Committed 83c181f and pushed to 9.1.x. Thanks!

Added the release note.

  • catch committed 83c181f on 9.1.x
    Issue #3072076 by JordanDukart, logickal, gabesullice, cgoffin,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.