Problem/Motivation

Discovered while working on #2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only.

\Drupal\jsonapi\Normalizer\EntityAccessDeniedHttpExceptionNormalizer() does this:

      if (isset($entity)) {
        $errors[0]['id'] = sprintf(
          '/%s--%s/%s',
          $entity->getEntityTypeId(),
          $entity->bundle(),
          $entity->uuid()
        );
      }

yet http://jsonapi.org/format/#error-objects says:

An error object MAY have the following members:

- id: a unique identifier for this particular occurrence of the problem.

Yet something like /user--user/550e8400-e29b-41d4-a716-446655440000 is not a unique identifier for this particular occurrence of the problem!

This was introduced in #2844130: Create a custom EntityAccessDeniedHttpException.

Proposed resolution

Remove it.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new1.05 KB
wim leers’s picture

StatusFileSize
new1.55 KB
new2.58 KB

And then we can go one step further…

Status: Needs review » Needs work

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

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new7.59 KB
new10.1 KB

In #3, I forgot to update the code that throws EntityAccessDeniedHttpExceptions.

wim leers’s picture

StatusFileSize
new1.35 KB
new11.42 KB

And this updated the expectations of JsonApiFunctionalTest. Should be green.

wim leers’s picture

This affects the DX of JSON API negatively today.

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

gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

This is much cleaner and makes more sense.

+++ b/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
@@ -321,7 +321,7 @@ class JsonApiDocumentTopLevelNormalizer extends NormalizerBase implements Denorm
-      throw new EntityAccessDeniedHttpException(NULL, AccessResult::forbidden(), '/data/id', 'IDs should be properly generated and formatted UUIDs as described in RFC 4122.');
+      throw new EntityAccessDeniedHttpException(AccessResult::forbidden(), '/data/id', 'IDs should be properly generated and formatted UUIDs as described in RFC 4122.');

:)

e0ipso’s picture

In a collection resource, if you have multiple errored entities, how would you know that a given error applies to a given resource?

gabesullice’s picture

@e0ipso, can't we use the links, source and/or meta keys to identify it, where necessary?

http://jsonapi.org/format/#error-objects

e0ipso’s picture

Status: Reviewed & tested by the community » Needs work

Gabe I'm OK with that. Links seem to be the best way to identify a resource entity, but I don't have an opinion on that.

However I do not think we should just lose the functionality. 👍 or 👎?

wim leers’s picture

Wow, great catch!


On the one hand, I can't believe that the JSON API spec doesn't prescribe how to support that. On the other hand, I'm not at all surprised, because that is why you wrote JSON API Partial Success.md of course.

It's increasingly looking like we really need to start interacting with the JSON API spec writers to get these significant/critical gaps in the spec addressed.


Two related observations I made while I was looking into this:
  1. we put the errors under {…, meta: { errors: […] }}, which violates http://jsonapi.org/format/#error-objects, that says it should be under {…, errors: […]}! Looks like we need an issue to fix that? Never mind, apparently this would violate the JSON API spec — see https://gist.github.com/e0ipso/732712c3e573a6af1d83b25b9f0269c8#gistcomm...
    I sure hope they'll change this, because right now that makes JSON API braindead wrt collection handling.
  2. part of the ambiguity is that it's not clear per se that we're referring to a resource that would have been part of the retrieved collection, and what the position would've been: why aren't we including resource identifier objects for inaccessible resources in a resource collection? That would not divulge any sensitive information. But it would allow for use cases where you want to list all resources, even inaccessible ones, perhaps with a "log in to see the details of this resource" placeholder for the inaccessible ones.

I agree, we should not lose that functionality, because otherwise it's not clear which resource the error is for. I see three possible solutions:

  1. links.resource: a link pointing to the inaccessible resource's individual JSON API URL. Under a resource key perhaps?
  2. meta: we could then put type and id in there, so meta.type and meta.id
  3. source.pointer: if we would be including a resource identifier object for inaccessible resources, then we could use the pointer under source to its full potential: we could then specify not /data to indicate an inaccessible resource in a collection (which honestly is just completely wrong), but /data/2, assuming that the resource identifier object is at position 2 (third) in the collection.

I much prefer that third possible solution. It feels most in line with JSON API's intent.

gabesullice’s picture

Wow @Wim Leers, I really like #13.3 and I'm jealous I never thought of it lol

This would makes so much more sense for things like pagination too.

wim leers’s picture

Glad you like it Gabe! :D It's actually in a big part thanks to you — you talked a fair bit about pagination lately, and wrote that handbook page that you asked me to review. :)

I just noticed something wasn't clear in my comment:

It feels most in line with JSON API's intent.

The intent I'm referring to is mostly this http://jsonapi.org/format/#error-objects:

pointer: a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute].

The examples they give are for single resource URLs. But if you apply the same reasoning to resource collection URLs, you end up with /data/2 to point out that the third resource is not accessible, or perhaps /data/4/attributes/title to indicate that the fifth POSTed resource (when creating multiple resources at the same time) was not created because its title was invalid.

IOW: use the power of JSON Pointers :)

e0ipso’s picture

All of those options seem interesting. I'd like 3️⃣ slightly better, but all of them will work.

JSON Pointers are cool. JSON Path is great! :-P However in this case JSON pointer is more appropriate.

wim leers’s picture

Alright, sounds like we have a plan! :) /me high-fives Mateu & Gabe!

gabesullice’s picture

Title: Spec Compliance: when error object 'id' key exists, it contains the individual resource URI, instead of "a unique identifier for this particular occurrence of the problem" » [PP-1] Spec Compliance: when error object 'id' key exists, it contains the individual resource URI, instead of "a unique identifier for this particular occurrence of the problem"
Status: Needs work » Postponed

I've created a dependent issue to start including resource identifiers instead of simply omitting items when access is denied: #2944348: Provide a resource identifier instead of omitting the resource when access is denied

That will allow us to include a source.pointer in error objects as suggested by #13.3

wim leers’s picture

🎉

e0ipso’s picture

Is there extra work that needs to happen here when #2944348: Provide a resource identifier instead of omitting the resource when access is denied lands? If not we can simply mark this one as fixed instead of postponed.

gabesullice’s picture

Status: Postponed » Needs review
StatusFileSize
new5.98 KB
new13.69 KB

@e0ipso, the other issue was meant to only add resource identifiers instead of omitting them from the response. After that was finished, I planned on using this issue for changing the pointers.

However, after actually working on this, these two things (removing resources and generating errors) are pretty inextricably linked, so doing it in two issues doesn't make sense any longer. I'm going to close #2944348 in just a second.

The attached patch is the smallest possible set of changes that I felt I could introduce to make this work, but it does feel a bit hacked in. That said, doing anything else would probably mean a much larger patch and our efforts can probably be better used elsewhere.

Without further ado, "it works on my machine" so let's see what the testbot says :P

gabesullice’s picture

Title: [PP-1] Spec Compliance: when error object 'id' key exists, it contains the individual resource URI, instead of "a unique identifier for this particular occurrence of the problem" » Spec Compliance: when error object 'id' key exists, it contains the individual resource URI, instead of "a unique identifier for this particular occurrence of the problem"

The last submitted patch, 21: 2943176-21.tests_only.patch, failed testing. View results

Status: Needs review » Needs work

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

gabesullice’s picture

Status: Needs work » Needs review
StatusFileSize
new9 KB
new16.72 KB
new3.03 KB

The last submitted patch, 25: 2943176-25.tests_only.patch, failed testing. View results

wim leers’s picture

Reviewing the test-only patch:

  1. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -157,15 +156,14 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
    -    $single_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
    +    $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
    

    This was indeed incorrectly named.

  2. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -157,15 +156,14 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
    -    $this->assertEquals(1, count($single_output['data']));
    ...
    +    $this->assertEquals(2, count($collection_output['data']));
    

    This is the real change: inaccessible resources still get resource identifier objects.

  3. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -157,15 +156,14 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
    -    $this->assertEquals('/node--article/' . $this->nodes[1]->uuid(), $single_output['meta']['errors'][0]['id']);
    -    $this->assertFalse(empty($single_output['meta']['errors'][0]['source']['pointer']));
    ...
    +    $this->assertEquals('/data/1', $collection_output['meta']['errors'][0]['source']['pointer']);
    

    And this is the other real change: error objects now point to the inaccessible resource identifier objects.

  4. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -320,9 +318,7 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
    -    $output_nids = array_map(function ($result) {
    -      return $result['attributes']['nid'];
    -    }, $output['data']);
    +    $output_nids = array_reduce(['attributes', 'nid'], 'array_column', $output['data']);
    
    @@ -333,9 +329,7 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
    -    $output_nids = array_map(function ($result) {
    -      return $result['attributes']['nid'];
    -    }, $output['data']);
    +    $output_nids = array_reduce(['attributes', 'nid'], 'array_column', $output['data']);
    

    This is just refactoring.

  5. +++ b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php
    @@ -262,11 +262,14 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase {
    +    $this->assertSame('/included/0', $normalized['meta']['errors'][0]['source']['pointer']);
    +    $this->assertArrayNotHasKey('attributes', $normalized['included'][0]);
    +    $this->assertArrayNotHasKey('relationships', $normalized['included'][0]);
    

    This is verifying there's a resource identifier object as the inaccessible included resource.

  6. +++ b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php
    @@ -262,11 +262,14 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase {
    -    $this->assertEquals($this->term1->uuid(), $normalized['included'][0]['id']);
    -    $this->assertEquals('taxonomy_term--tags', $normalized['included'][0]['type']);
    -    $this->assertEquals($this->term1->label(), $normalized['included'][0]['attributes']['name']);
    -    $this->assertTrue(!isset($normalized['included'][0]['attributes']['created']));
    +    $this->assertEquals($this->term1->uuid(), $normalized['included'][1]['id']);
    +    $this->assertEquals('taxonomy_term--tags', $normalized['included'][1]['type']);
    +    $this->assertEquals($this->term1->label(), $normalized['included'][1]['attributes']['name']);
    +    $this->assertTrue(!isset($normalized['included'][1]['attributes']['created']));
    

    And the one that is accessible then naturally needed to shift around.

All those comments are just voicing my understanding. Looks great! 👌

wim leers’s picture

Status: Needs review » Needs work

Reviewing everything else in the "main" patch:

  1. +++ b/src/Normalizer/EntityAccessDeniedHttpExceptionNormalizer.php
    @@ -23,6 +26,23 @@ class EntityAccessDeniedHttpExceptionNormalizer extends HttpExceptionNormalizer
    +    return new EntityAccessDeniedHttpExceptionNormalizerValue(
    +      $errors,
    +      FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
    +      $object->getResourceIdentifier()
    
    +++ b/src/Normalizer/Value/EntityAccessDeniedHttpExceptionNormalizerValue.php
    @@ -0,0 +1,46 @@
    +class EntityAccessDeniedHttpExceptionNormalizerValue extends FieldNormalizerValue {
    

    Rather than adding this new value object class, I'd modify the existing one. "access denied" (403) isn't the only reason a JSON API document's error object should contain a pointer.

    Is there a reason you chose this approach instead?

  2. +++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
    @@ -168,9 +171,7 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
    -      $unique_key = $rasterized_include['data'] === FALSE ?
    -        $rasterized_include['meta']['errors'][0]['detail'] :
    -        $rasterized_include['data']['type'] . ':' . $rasterized_include['data']['id'];
    +      $unique_key = $rasterized_include['data']['type'] . ':' . $rasterized_include['data']['id'];
    

    This is a simplification we can now make because inaccessible includes now also always have a resource identifier object, right?

gabesullice’s picture

One clarification: #27.4 is not just a refactoring. It previously would have failed because the attributes key wouldn't be defined on the resource identifier array. Since array_column just casts out arrays that don't have the column key, it avoids all the isset() checking one would otherwise have to do :)

You can make a really neat (albeit naive) nested value extractor with that idea:

$path = 'some.deep.path.to.value';
$deep_values = array_reduce(explode('.', $path), 'array_column', $nested_array);

kinda nifty, no?

e0ipso’s picture

Your comment made me think of lodash. I was terrified to find out about https://lodash-php.com.

gabesullice’s picture

Status: Needs work » Needs review

#28.1

This change actually has little to do with pointers. This change needed to happen because entity access exceptions replace entities in an entity collection when they are loaded and checked for access. Because they replace the entities, we have to preserve identity information on the exception itself so that we can later normalize a resource identifier in the collection (previously, the exceptions were just filtered out). There's no other case where an exception needs to generate a resource identifier.

#28.2

That's correct.

gabesullice’s picture

+++ b/src/Normalizer/Value/JsonApiDocumentTopLevelNormalizerValue.php
@@ -114,11 +114,14 @@ class JsonApiDocumentTopLevelNormalizerValue implements ValueExtractorInterface,
+      if ($normalizer_value instanceof EntityAccessDeniedHttpExceptionNormalizerValue) {
...
-        $rasterized['meta']['errors'] = array_merge($previous_errors, $normalizer_value->rasterizeValue());
+        $rasterized_value = $normalizer_value->rasterizeValue();
...
+        $rasterized['meta']['errors'] = array_merge($previous_errors, $rasterized_value);
+        $rasterized['data'][] = $normalizer_value->rasterizeResourceIdentifier();

Re: 28.1, you can see here that we previously didn't add a value to data when we encountered an exception.

Now, we need to rasterize the exception into a resource identifier, in addition to rasterizing the error.

wim leers’s picture

This change actually has little to do with pointers. […] There's no other case where an exception needs to generate a resource identifier.

You're right that this is not per se related to pointers. But what matters here is that some exceptions involve existing resources, and the corresponding error objects may want to refer to those existing resources, even if we don't do that yet today. I'd say we shouldn't complicate today's patches for tomorrow's features (that may or may not happen), but in this case we're adding infrastructure and complexity that AFAICT would not be necessary if we did take tomorrow into account.

For example: a 409 when POSTing a resource to a collection that has some uniqueness constraint that is being violated, for example creating a User with the same name, or a Feed with the same URL — we'd want to be able to point not to the resource it's conflicting with. That would require a resource identifier (not in data, but in the error object).

TL;DR: the complexity of adding a EntityAccessDeniedHttpExceptionNormalizer and a EntityAccessDeniedHttpExceptionNormalizerValue seems higher than modifying HttpExceptionNormalizerValue?

(I'm not 100% convinced by neither your reasoning nor mine though — I don't feel very strongly about this, but I think it's our duty as reviewers to question architectural decisions.)

gabesullice’s picture

Status: Needs review » Active

Soo... I was initially very gung-ho about this in #14. However, after working on this and writing extensive pagination and filtering docs, I'm no longer as excited about this as I once was. I am no longer am convinced that we should include a resource identifier for inaccessible resources.

Why? It relies on meta.errors to indicate that a resource is not accessible by the current user. meta.errors is a non-standard feature of our implementation. We've tried to formalize it somewhat, but the fact remains that it is not part of the base spec. By including a resource identifier, there is no Drupal-agnostic way for a client to differentiate between a resource which was "forbidden" and a resource which simply doesn't have attributes/relationships.

I was also excited about this making pagination simpler, because page[limit] would then effectively operate as page[size] (meaning it would never have fewer than the limit number of resources when additional pages exist. I thought this would simplify client implementations, but I no longer thing that's true. While this would make it easier to reason about pagination, it wouldn't simplify client implementations because we're pushing the burden of removing resources from the UI into the client's domain... and their only way to do that will be to inspect meta.errors (which would mean they have to be "Drupal-aware") or do some heuristic based on the presence/absence of fields.

Finally, while I'm not sure this is technically a BC break, I think it will certainly cause unexpected behavior for many clients which have been relying on the backend to remove inaccessible resources from the data member of the document. This will be likely to cause JS errors wherever clients have made reasonable (yet optimistic) assumptions, like "every resource under data will have a attributes.title key".


So, what next?

I think we need to reevaluate adding individual errors for inaccessible resources altogether. If we're concerned about DX, then what we should be most concerned with is reducing the signal to noise ratio and providing actionable information. Individual errors do not satisfy that need. Instead, I propose that we provide a single error when any resource is omitted from the response:

{
  "meta": {
    "errors": {
      "title": "Insufficient Privileges",
      "detail": "One or more resources have been omitted from the primary data of this document due to insufficient privileges.",
      "links": {
        "about": "https://www.drupal.org/docs/8/modules/json-api/filtering#filters-access-control"
      },
      "source": {
        "pointer": "/data"
      }
    }
  }
}

I think this (1) reduces the signal to noise ratio, (2) is actionable, (3) is useful even when inactionable (e.g. providing a UI indication that items have been removed, even when they can't be filtered out).

I'm completely open to changing the langauge/contents of the error object. I'll point out that I chose "Insufficient Privileges" to distinguish it from "403 Access Denied" and I would be (reluctantly) open to including a meta object in the error object itself with links to the omitted resources. Like so:

{
  "title": "Insufficient Privileges",
  "...",
  "meta": {
    links: {
      "/user--user/550e8400-e29b-41d4-a716-446655440000": "http://example.com/jsonapi/user--user/550e8400-e29b-41d4-a716-446655440000",
    }
  }
}

Why not include those links in the parent error object? Because the spec isn't clear about whether we can or can't add additional members to the error object's link object."

However, I think it really doesn't serve a very useful purpose. I'm open to being convinced otherwise though.

wim leers’s picture

By including a resource identifier, there is no Drupal-agnostic way for a client to differentiate between a resource which was "forbidden" and a resource which simply doesn't have attributes/relationships.

I appreciate that, but the JSON API spec version 1.0 does not deal with partial success at all. The JSON API module for Drupal already violates this principle out of sheer necessity: without it, there's no way to make a sensible API. (This is the #1 gap/oversight in the JSON API spec IMHO.)

, I think it will certainly cause unexpected behavior for many clients which have been relying on the backend to remove inaccessible resources

This is an excellent point — you're right that it almost certainly has BC implications.

I propose that we provide a single error when any resource is omitted from the response

Hm … not sure yet. This is where I feel obligated to point to the JSON API spec again: this is surely such a common need except for the most trivial of applications, yet it's completely unmentioned. Error handling/handling of inaccessible resources seems like one of the areas where JSON API libraries can make the biggest DX difference to client developers! But alas …

I'm not sure what to do. This is where JSON API disappoints, sadly :(

e0ipso’s picture

My strong feelings regarding the (brilliant) argumentation @gabesullice made are:

  1. the spec may not have an opnion / contract on this matter, but Partial Success does. All consumers interacting with the Drupal-flavored JSON API servers have to be aware of its extensions, and respect them as they respect the base spec. With that, we have successfully extended the spec to cover this gap that @Wim Leers has pointed. The only missing part is that we should be forcing our clients to use Accept: application/vnd.api+json; extensions="fancy-filters,partial-success", instead of Accept: application/vnd.api+json, to formalize that awareness.
  2. I don't believe that we should hide available (useful) information from the consumer's developer. The digested error message doesn't work for me. I would really rather revert to the wonky, but somewhat clear behavior we had before. If "pointer" doesn't work for us, we can find a way to add a JSON Pointer to the appropriate entity. Maybe: meta.externalPointer -> https://example.org/jsonapi/node/article/1234-abcd#/data/attributes/title. Whatever we do, we add it to the Partial Success extension and it becomes as formal as if it was in the base spec.

If we want to have the digested error added on top of that, and then detailed errors hidden from the the average user via a permission; I don't feel strongly about that.

e0ipso’s picture

StatusFileSize
new153.39 KB

To re-iterate, this is the only wording preventing us from having a pointer to an external JSON document in the source.pointer:


I wouldn't oppose being practical and having a JSON pointer that is not restricted to the requested document. Even if that is not 100% in line with the base spec.

gabesullice’s picture

I think I see where the mental leap is that I failed to state explicitly. I think the single greatest distinction in my mind vs. your minds is now this:

An entity removed from a collection for access reasons is not an error, nor is it exceptional. It is expected behavior.

Everything else follows from that.

How do we know this is true? Let me show it by example:

If a node is not present in a query result because of node grants, Drupal doesn't throw an exception, it silently removes the result. It's not an error. It's expected behavior.

In the PHP API you may override that behavior, but it's still the default. Since this is a public API, we cannot override it, but that doesn't change the principle.

I appreciate that, but the JSON API spec version 1.0 does not deal with partial success at all.

If you join me in my thinking above, then "partial success" is not relevant because nothing failed. There's no need to even concern ourselves with extensions and other whatnot in that regard.

I don't believe that we should hide available (useful) information from the consumer's developer.

Neither do I! That is why I explicitly said:

"I would be (reluctantly) open to including a meta object in the [digested] error object itself with links to the omitted resources."

And I even included an example of how we might do that :) If clients wish to know the reason that access was denied, they may follow the link, where they'll receive a status, reason, and detailed information.

wim leers’s picture

The only missing part is that we should be forcing our clients to use Accept: application/vnd.api+json; extensions="fancy-filters,partial-success"

This is a really good point. We should mention this in jsonapi.api.php. But at the same time, we need to recognize the fact that we're not going to be able to generate responses that don't also adhere to those extensions. So perhaps even more importantly than documenting that this should be the Accept request header, we should update our response headers. But interestingly, http://jsonapi.org/extensions/ says an "extension system is under development" yet points to https://github.com/json-api/json-api/blob/9c7a03dbc37f80f6ca81b16d444c96..., which used to be the extension system. We really need to get this right.

wim leers’s picture

#38: I'm interested to hear Mateu's thoughts on this, my thinking on this hasn't fully crystallized yet.

wim leers’s picture

Assigned: Unassigned » e0ipso
Priority: Normal » Major

I re-read this issue, because it's been around for a while.

That of course ended with reading the last comment on this issue not written by me: @gabesullice's comment in #38. 2.5 months later, I still find myself going "hmmm, that's right" and then nodding when reading

If you join me in my thinking above, then "partial success" is not relevant because nothing failed. There's no need to even concern ourselves with extensions and other whatnot in that regard.

This issue is absolutely hard-blocked on @e0ipso. And I think it's very important for the future of JSON API. Bumping to Major priority and assigning to @e0ipso.

wim leers’s picture

Also: we reached consensus at #2853066-13: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata a few days ago! I think that could help get us to consensus on this too?

wim leers’s picture

e0ipso’s picture

An entity removed from a collection for access reasons is not an error, nor is it exceptional. It is expected behavior.

Everything else follows from that.

We don't treat entities differently depending on if they are in a collection or individual resources. This suggests that we do here. If an individual request to an entity fails with an error, then I think that the same operation as part of a collection should also be seen as one. I believe (by the accumulation of reports in that sense) that many assume the same, and it would be very confusing to treat it as success or error depending if it's a collection or not.

I do believe that things have partially failed if you request a collection where some entities fail.

e0ipso’s picture

Also, I remember when researching this in the past that all the questions and approaches in this matter (in the forums) acknowledged the existence of an error of an error.

gabesullice’s picture

We don't treat entities differently depending on if they are in a collection or individual resources... If an individual request to an entity fails with an error, then I think that the same operation as part of a collection should also be seen as one.

We respond with a 403 error for an inaccessible individual resource. 400-level errors are client errors. The error is accessing the individual resource (resource as in route, not as in entity) with insufficient authorization. IOW, the entity did not fail, the authorization did.

In #2853066-13: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata, we agreed that even a collection with 0 results (for access reasons) is a 200. Access to the "collection resource" is a success (200).

In a collection with omitted entities, I think it's correct to return a single "Insufficient Privileges" error that mirrors the client error at the individual resource level, with extra metadata about which resources would be viewable if those additional privileges were attached to the request. That's consistent with the consensus we reached (reflected in the IS update at #2853066-18) for the 2.x branch. This complements the idea that you might want to "correct" your request by adding a ?filter[status]=1 filter to your query, since it was a client error, not an entity error.


Given that #2853066, deals with a superset of the issue here and that we're trying to freeze the 1.x branch and release 2.x very soon, I propose we simply close this issue as "works as designed" for the 1.x branch, ignoring any spec violation WRT to the error ID in 1.x. It'll be a non-issue in 2.x.

e0ipso’s picture

Status: Active » Closed (works as designed)
rpayanm’s picture

Project: JSON:API » Drupal core
Version: 8.x-1.x-dev » 8.9.x-dev
Component: Code » jsonapi.module

Moving to Drupal core's issue queue.

I'm working on https://www.drupal.org/project/drupal/issues/3122113

bbrala’s picture

Hmm, if this works as designed, we might need to move along and try and execute: /Users/bjorn/projects/drupal/core/modules/jsonapi/tests/src/Functional/NodeTest.php:341. Which is a todo to remove some code when this issue is closed. Opening a child issue.

bbrala’s picture