Discovered as part of #2972808: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships.

The acceptable status code for a successful relationship for POST requests is 204 when there are not already relationships at that resource (i.e. the client is creating all of the relationships) or if the data array is empty and the existing relationships are also empty.

A server MUST return a 204 No Content status code if an update is successful and the representation of the resource in the request matches the result. — Updating relationship responses (204)

If there are existing resource identifier objects at the resource not given in the POST request, then the appropriate response is 200:

If a server accepts an update but also changes the targeted relationship(s) in other ways than those specified by the request, it MUST return a 200 OK response. The response document MUST include a representation of the updated relationship(s).

A server MUST return a 200 OK status code if an update is successful, the client’s current data remain up to date, and the server responds only with top-level meta data. In this case the server MUST NOT include a representation of the updated relationship(s). — Updating relationship responses (200)


One might ask why 201 Created is not acceptable given that the spec also says "a server MAY respond with other HTTP status codes."

The reason is the MUST if X, Y Z language given for the 204 and 200 responses. When all conditions are true, the server has no choice but to follow that guidance. IOW, the MAY language is only for all other cases not already covered by the MUSTs.

Comments

gabesullice created an issue. See original summary.

wim leers’s picture

Nice find!

Can't we fix this in 8.x-1.x?

gabesullice’s picture

Can't we fix this in 8.x-1.x?

You're right. This can happen in 8.1.x. I was muddling this up in my mind w/ #2977659: Spec Compliance: POST|PATCH|DELETE on relationships should respect arity rules which has a small potential for breakage.

gabesullice’s picture

Version: 8.x-2.x-dev » 8.x-1.x-dev
wim leers’s picture

Assigned: Unassigned » wim leers
Status: Active » Needs review
StatusFileSize
new6.68 KB

Following the @todo instructions in the existing test coverage in HEAD. This will cause failures; in theory I should not need to make any additional changes to test coverage; only to controller/normalizer logic.

Status: Needs review » Needs work

The last submitted patch, 5: 2977653-5.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new2.4 KB
new7.63 KB

I missed a few in #5.

Status: Needs review » Needs work

The last submitted patch, 7: 2977653-7.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new3.46 KB
new11.04 KB

Status: Needs review » Needs work

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

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new4.29 KB
new14.73 KB

I managed to fix most test failures by updating expectations. But I'm stuck on

// 10. Successful PATCH to related endpoint.

in JsonApiFunctionalTest::testWrite().

For some reason the EntityResource::patchRelationship()$entity->validate()\Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraintValidator::validate()::loadUnchanged() call chain is resulting in an exception because a the appropriate node DB table does not exist 😵😱 This is the exact exception:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'd8.test84784340node' doesn't exist: SELECT revision.vid AS vid, revision.langcode AS langcode, revision.revision_uid AS revision_uid, revision.revision_timestamp AS revision_timestamp, revision.revision_log AS revision_log, revision.revision_default AS revision_default, base.nid AS nid, base.type AS type, base.uuid AS uuid, CASE base.vid WHEN revision.vid THEN 1 ELSE 0 END AS isDefaultRevision
FROM 
{node} base
INNER JOIN {node_revision} revision ON revision.vid = base.vid
WHERE base.nid IN (:db_condition_placeholder_0); Array
(
    [:db_condition_placeholder_0] => 1
)

I'm clueless. I've spent more than an hour and a half on this already, still nothing. So just posting it here, hoping somebody else figures this out!

Status: Needs review » Needs work

The last submitted patch, 11: 2977653-11.patch, failed testing. View results

wim leers’s picture

  1. +++ b/src/Controller/EntityResource.php
    @@ -543,13 +543,59 @@ class EntityResource {
    +    $original_field_list = clone $field_list;
    

    This is the only change I can see could possibly be causing this problem, in some insanely roundabout/complex way.

    Another round of debugging didn't help. Tomorrow with a fresh set of eyes, this should be solvable.

  2. +++ b/src/Controller/EntityResource.php
    @@ -543,13 +543,59 @@ class EntityResource {
    +   *   The new (udpated) entity references.
    

    s/udp/upd/

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new3.09 KB
new16.3 KB

I can no longer reproduce the bizarreness of #11

In fact, it actually was pretty easy now to make the entire patch green! Both on 8.5 and 8.6.

Maybe something else in JSON API changed that solved the root cause. Maybe it was my machine. Maybe it was me. 😳

wim leers’s picture

Assigned: Unassigned » gabesullice
Status: Needs review » Reviewed & tested by the community

Self-review:

  1. +++ b/tests/src/Functional/ResourceTestBase.php
    @@ -1392,16 +1392,11 @@ abstract class ResourceTestBase extends BrowserTestBase {
    -      // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2977653.
    -      $this->assertResourceResponse(201, FALSE, $response);
    -      // $this->assertResourceResponse(204, NULL, $response);
    +      $this->assertResourceResponse(204, NULL, $response);
    

    All changes in this class are like this: removing @todos and uncommenting associated intended test coverage, replacing the old test coverage.

    These were added by @gabesullice in #2972808: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships. While working on this patch, I vetted every one. I consulted the JSON API spec while doing so.

  2. +++ b/src/Controller/EntityResource.php
    @@ -555,13 +555,59 @@ class EntityResource {
    -    return $this->getRelationship($entity, $related_field, $request, 201);
    +    $status = static::relationshipArityIsAffected($original_field_list, $field_list)
    +      ? 200
    +      : 204;
    +    return $this->getRelationship($entity, $related_field, $request, $status);
    
    @@ -600,7 +646,7 @@ class EntityResource {
    -    return $this->getRelationship($entity, $related_field, $request);
    +    return $this->getRelationship($entity, $related_field, $request, 204);
    
    @@ -693,7 +739,7 @@ class EntityResource {
    -    return $this->getRelationship($entity, $related_field, $request, 201);
    +    return $this->getRelationship($entity, $related_field, $request, 204);
    

    These are the necessary logic changes to make the test coverage in point 1 pass.

  3. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -744,7 +744,7 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
    -    $this->assertEquals(201, $response->getStatusCode());
    +    $this->assertEquals(200, $response->getStatusCode());
    
    +++ b/tests/src/Kernel/Controller/EntityResourceTest.php
    @@ -691,7 +691,7 @@ class EntityResourceTest extends JsonapiKernelTestBase {
    -    $this->assertEquals(201, $response->getStatusCode());
    +    $this->assertEquals(204, $response->getStatusCode());
    

    The changes in these test classes are necessary to make them pass; they follow the example set by the test coverage in point 1. I also verified each of these changes.

I can't find any problems. I mostly just executed on the stubs that @gabesullice had already added, and also ensured this actually matches the JSON API spec. Still, I'd like @gabesullice to do the final sign-off (and hopefully commit).

  • gabesullice committed 0529de7 on 8.x-1.x
    Issue #2977653 by Wim Leers, gabesullice: Spec Compliance: Return 204 or...
  • gabesullice committed 6a2d6cd on 8.x-2.x
    Issue #2977653 by Wim Leers, gabesullice: Spec Compliance: Return 204 or...
gabesullice’s picture

+1
\O/

gabesullice’s picture

Assigned: gabesullice » Unassigned
Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

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