Problem/Motivation

Consider the following trivialized rest plugin callback.
<?
$results = $query->fetchAll();
return new ResourceResponse($results);
?>

When the query returns an empty set we would expect the JSON serialization to be "[]". We however get and empty response.

Proposed resolution

Fix ResourceHandler to not skip serializing on "false" values like an empty array.

Remaining tasks

Review

User interface changes

N/A

API changes

This is not a behavior that should have been relied upon and is not documented. Since its an inconsistency that could cause errors in javascript or other code relying on consistent returns types from a rest endpoint it is a bug.

Data model changes

N/A

Comments

neclimdul created an issue. See original summary.

neclimdul’s picture

Status: Active » Needs review
StatusFileSize
new4.92 KB
new7.72 KB

A test case that illustrate the bug, some extra tests that do some more unity tests(we only have web tests for this functionality) and a possible fix.

The last submitted patch, 2: resourceresponse_can_t-2661642-2-testonly.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 2: resourceresponse_can_t-2661642-2.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new1.26 KB
new5.05 KB
new7.85 KB

*grumble* missing @group. phpunit runner runs fine without it.

The last submitted patch, 5: resourceresponse_can_t-2661642-5-testonly.patch, failed testing.

wim leers’s picture

Title: ResourceResponse can't serialize empty array. » ResourceResponse can't serialize empty array
Issue tags: +DX (Developer Experience), +API-First Initiative
  1. +++ b/core/modules/rest/src/RequestHandler.php
    @@ -103,26 +103,31 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
    +      $data = $response->getResponseData();
    +      if (!is_scalar($data)) {
    

    What happens in the "else" case?

    AFAICT there's no "else" case. If so, then why indent all this code, why not just add this extra condition in the original if-test?

  2. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,146 @@
    +//    /** @var \Drupal\rest\Plugin\Type\ResourcePluginManager $plugin_manager */
    +//    $plugin_manager = $this->container->get('plugin.manager.rest');
    

    .

  3. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,146 @@
    +  public function testBaseHandler() {
    +
    +    $request = new Request();
    

    Extraneous \n.

  4. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,146 @@
    +    // Assert get plugin method is called.
    

    Useless comment?

  5. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,146 @@
    +    // Setup stubbed out plugin manager that will return our plugin.
    ...
    +    // Setup stubbed out plugin manager that will return our plugin.
    ...
    +    // Setup stubbed out plugin manager that will return our plugin.
    

    s/Setup stubbed out/Stub/

  6. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,146 @@
    +    // Response returns null this time.
    

    Why exactly does it return NULL?

    Also, "returns null" sounds weird, why not say "$response is NULL" this time, and use strict equality?

neclimdul’s picture

StatusFileSize
new7.39 KB
new4.5 KB
new7.72 KB

1) Yeah, I started doing that first. First, the else is we fall through to returning the response unmodified. Second, I didn't realize this but this is not valid:

if (($data = $response->getResponseData() || !is_scalar($data))) {}

$data isn't defined in the is_scalar check.

That said, I missed a case where technically scalars need to be serialized So I think the right thing to do is serialize everything. The documentation for ResourceResponse::getResponseData()'s return is "Response data that should be serialized."

2-6) fixed. (3. man some habits die hard. the first Drupal module I worked on required these and they sneak in from time to time)

Related to #1, lots more test cases exposed through a provider in this patch. Test only again just to confirm we're still catching the failure after I consolidated the test.

edited for clarity.

Status: Needs review » Needs work

The last submitted patch, 8: resourceresponse_can_t-2661642-2.patch, failed testing.

neclimdul’s picture

StatusFileSize
new5.28 KB

lets try that patch again.

wim leers’s picture

  1. +++ b/core/modules/rest/src/RequestHandler.php
    @@ -103,7 +103,8 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
         // Serialize the outgoing data for the response, if available.
    -    if ($response instanceof ResourceResponse && $data = $response->getResponseData()) {
    +    if ($response instanceof ResourceResponse) {
    

    The comment no longer makes sense now. Let's omit the , if available.? Or, actually, let's remove that entire comment now, because it's no longer necessary?

  2. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,135 @@
    +    // a ResourceResponse so it is passed through directory.
    

    directly?

  3. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,135 @@
    +  public function testSerialization($data) {
    ...
    +    $this->assertEquals(json_encode($data), $handler_response->getContent());
    

    Now it's much clearer what this tests: it simply tests that whatever is passed in is also what comes out. That makes a lot of sense :)

  4. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,135 @@
    +      ['Complex \ string $%^&@ with unicode ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
    

    :)

  5. +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
    @@ -0,0 +1,135 @@
    +      // TODO Not supported. https://www.drupal.org/node/2427811
    

    s/TODO/@todo/

The last submitted patch, 8: resourceresponse_can_t-2661642-8-testonly.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new1.67 KB
new5.35 KB

1) sure, serialization isn't even the only thing going on in that block so it was misleading anyway. The check is clear and the code inside the if is well documented (or equally clear) so lets just remove it.
2) dangit
3) 4) :-D
5) thanks.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

  • catch committed 6a10e56 on
    Issue #2661642 by neclimdul: ResourceResponse can't serialize empty...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.1.x, thanks!

This looks like it would probably be fine for a patch release too, so re-open if you particularly want to see it in 8.0.x

neclimdul’s picture

Version: 8.1.x-dev » 8.0.x-dev
Status: Fixed » Patch (to be ported)

I'd like to see it. Currently rest plugins will need to do copy the serialization logic if they return a list of data that has the possibility of ever being empty. Which with permissions and filtering could even catch someone not expecting it.

neclimdul’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new5.35 KB

Not sure how to just re-run #13 against 8.0 So re-uploading #13 for testbot clarity. applied cleanly though so should be cherry-pickable.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

I agree there shouldn't be any harm in cherry picking this for 8.0.x. Committed f32314a and pushed to 8.0.x. Thanks!

  • alexpott committed f32314a on 8.0.x authored by catch
    Issue #2661642 by neclimdul: ResourceResponse can't serialize empty...

Status: Fixed » Closed (fixed)

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