_format is not an allowed, implementation-specific query parameter. How shall we handle this?

Personally, I'd like to (once again) try to eliminate the need/ability to use this query parameter.

Comments

gabesullice’s picture

gabesullice created an issue.

IOW, I think there's still room for improvement on this topic.

wim leers’s picture

Title: Spec Compliance: `_format` is a disallowed query parameter name » [PP-1] Spec Compliance: `_format` is a disallowed query parameter name
Related issues: +#2933062: [>=8.5.4] Spec Compliance: Return 400 for unrecognized/unsupported query parameters

Wow. Such a great find! And so painfully obvious now 😳

I propose that in the 2.x branch of JSON API, we simply forbid ?_format=api_json. Using it will result in a 400 response.

That's what http://jsonapi.org/format/#query-parameters requires. And in practice, most people using JSON API probably are already omitting it anyway.

Implementing this will be easy after #2933062: [>=8.5.4] Spec Compliance: Return 400 for unrecognized/unsupported query parameters lands, since #2933062-25: [>=8.5.4] Spec Compliance: Return 400 for unrecognized/unsupported query parameters added a @todo referring to this issue; we'll just need to delete that @todo and the line below it!

gabesullice’s picture

I propose that in the 2.x branch of JSON API, we simply forbid ?_format=api_json. Using it will result in a 400 response.

Yes, please. 😍🙏+💯

wim leers’s picture

Status: Active » Reviewed & tested by the community

👌

gabesullice’s picture

Title: [PP-1] Spec Compliance: `_format` is a disallowed query parameter name » Spec Compliance: `_format` is a disallowed query parameter name
gabesullice’s picture

Status: Reviewed & tested by the community » Active

Err, there's not a patch. Don't know why this was RTBC lol.

gabesullice’s picture

StatusFileSize
new685 bytes

I'm just going to make the call and commit this if tests pass. That's what major version number bumps are for, right?

wim leers’s picture

Don't know why this was RTBC lol.

LOL! 😅

gabesullice’s picture

Status: Active » Needs review
StatusFileSize
new2.64 KB
new12.04 KB

Wow, so this has a super obscure interaction with #2948666: Remove JSON API's use of $context['cacheable_metadata'].

Surprisingly, we can't actually return a JsonApiTopLevelDocument with an errors object; only one with data.

Right now, all error responses arise from thrown exceptions. So why not just throw an exception? Well, we can't actually throw an exception that results in an equivalent error object because we don't have an exception type that can carry customized links (!). The customized link the currrent patch has is: http://jsonapi.org/format/#query-parameters.

JsonApiDocumentTopLevel even has the following class comment:

 * @todo Add the missing required members: 'error' and 'meta' or document why not.
 * @todo Add support for the missing optional members: 'jsonapi', 'links' and 'included' or document why not.
 */
class JsonApiDocumentTopLevel {

Sooo, I'm not too pleased with how this interdiff looks. Not sure if we want to fix it like this for now and just have follow up to do it "right" later or if we want to make it easy to do "right" first.

wim leers’s picture

StatusFileSize
new2.83 KB

Re-applied #9's interdiff on top of #7.

Status: Needs review » Needs work

The last submitted patch, 10: 2977600-10.patch, failed testing. View results

e0ipso’s picture

Are we forbidding any parameter unmanaged by the spec? Or are we targeting only _format?

There are many reasons to need arbitrary qs params. I want to make sure that is still a possibility.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new3.74 KB
new4.05 KB

Sooo, I'm not too pleased with how this interdiff looks. Not sure if we want to fix it like this for now and just have follow up to do it "right" later or if we want to make it easy to do "right" first.

Wasn't sure about this either, but I think I managed to find another solution without spending a lot of time on it. It:

  • Changes \Drupal\jsonapi\EventSubscriber\JsonApiRequestValidator::validateQueryParams() to not return a ResourceResponse (this was the only place creating a ResourceResponse with array as its data, probably wrongly so — my bad!), and instead return a HTTP exception.
  • HTTP exceptions are already handled by \Drupal\jsonapi\EventSubscriber\DefaultExceptionSubscriber::onException(), so we're basically done! There is only one caveat: that means we lose the errors.links.info = http://jsonapi.org/format/#query-parameters piece of information. But even that can be solved pretty elegantly.

This is even a net code reduction, and frankly is how JsonApiRequestValidator.php should've worked in the first place. It was only added yesterday (in #2933062: [>=8.5.4] Spec Compliance: Return 400 for unrecognized/unsupported query parameters). So it's definitely fine to refine it.

wim leers’s picture

Status: Needs review » Needs work

The last submitted patch, 13: 2977600-12.patch, failed testing. View results

e0ipso’s picture

I saw that and realized that it was forbidding all qs params. That's unfortunate. I'd like to review that.

wim leers’s picture

D'oh #13 should have been posted on #2982478: JsonApiRequestValidator creates ResourceResponse object with array instead of JsonApiDocumentTopLevelNormalizerValue 😅 Did that, and got it committed. HEAD is now passing tests again!

wim leers’s picture

I saw that and realized that it was forbidding all qs params. That's unfortunate. I'd like to review that.

It doesn't! It forbids all query string parameters that the JSON API spec reserves. Something like foo=bar is forbidden, but something like foo_foo=bar is allowed. This is just the JSON API spec, nothing more or less. See http://jsonapi.org/format/#query-parameters

According to those rules, _format=api_json is also forbidden. But since likely many clients are using URL?_format=api_json, that parameter was special-cased to be allowed by #2933062: [>=8.5.4] Spec Compliance: Return 400 for unrecognized/unsupported query parameters. This issue aims to change that, for better spec compliance.

wim leers’s picture

Re-testing #7, which should still apply cleanly, and now should no longer be affected by failing HEAD tests.

gabesullice’s picture

Status: Needs work » Needs review
Related issues: +#2984044: Ignore some query parameters which violate the JSON API specification

Most of the changes from #13 have already been integrated in #2982478: JsonApiRequestValidator creates ResourceResponse object with array instead of JsonApiDocumentTopLevelNormalizerValue.

That leaves the original changes. IMHO, I think this can be committed as is.

There's a related followup that deals with the larger topic in #16 here: #2984044: Ignore some query parameters which violate the JSON API specification.

gabesullice’s picture

StatusFileSize
new685 bytes

D'oh! Patch attached.

wim leers’s picture

If we commit this, then accessing /jsonapi/node/article via a browser will result in Dynamic Page Cache entries that are have the html rather than api_json format. This is because of the logic in FormatSetter only automatically setting the api_json format only if the correct Accept header is specified.

I think we want to change that, so that the api_json format is always set for every JSON API route?

This may seem out of scope here, but the change here is effectively forbidding the use of /jsonapi/node/article?_format=api_json, and hence this issue is forcing the need for that change.

gabesullice’s picture

Issue summary: View changes
StatusFileSize
new208.44 KB

If we commit this, then accessing /jsonapi/node/article via a browser will result in Dynamic Page Cache entries that are have the html rather than api_json format. This is because of the logic in FormatSetter only automatically setting the api_json format only if the correct Accept header is specified.

Are you sure?

I think we want to change that, so that the api_json format is always set for every JSON API route?

That's already how it works. FormatSetter calls $this->isJsonApiRoute() which is:

  protected function isJsonApiRequest(Request $request) {
    $is_jsonapi_route = $request->attributes->get(Routes::JSON_API_ROUTE_FLAG_KEY);
    // Check if the path indicates that the request intended to target a JSON
    // API route (but may not have because of an incorrect parameter or minor
    // typo).
    $jsonapi_route_intended = strpos($request->getPathInfo(), "{$this->jsonApiBasePath}/") !== FALSE;
    // Check if the 'Accept' header includes the JSON API MIME type.
    $request_has_jsonapi_media_type = count(array_filter($request->getAcceptableContentTypes(), function ($accept) {
      return strpos($accept, 'application/vnd.api+json') === 0;
    }));
    return $is_jsonapi_route || ($jsonapi_route_intended && $request_has_jsonapi_media_type);
  }

So any matched JSON API route already works like you said. It's just when the route isn't matched and there is no Accept header that it "fails". In that case, it will send an HTML 404. When is a route not matched? When a resource UUID doesn't exist or a URL has a typo in it.

I think I'm fine with that for the browser. I could be convinced otherwise.

Status: Needs review » Needs work

The last submitted patch, 21: 2977600-20.patch, failed testing. View results

gabesullice’s picture

Status: Needs work » Needs review

This was kicked back because HEAD broke on the latest version of core.

wim leers’s picture

Status: Needs review » Needs work
StatusFileSize
new430.37 KB

I should have been more specific. This is what I meant:

(That's with only those 2 requests in the browsers on the right hand side having occurred.)

wim leers’s picture

Status: Needs work » Needs review
Related issues: +#2987205: FormatSetter doesn't set the format to `api_json` when accessing just `/jsonapi`
StatusFileSize
new1.17 KB
new1.36 KB

If we commit this, then accessing /jsonapi/node/article via a browser will result in Dynamic Page Cache entries that are have the html rather than api_json format.

Actually, this is already the case in HEAD. I should've just opened a separate issue for that. Did that now: #2987205: FormatSetter doesn't set the format to `api_json` when accessing just `/jsonapi`.

Sorry for distracting this issue.

IMHO that leaves one thing left to do: ensure that the upgrade DX is good.

{
  "errors": [
    {
      "title": "Bad Request",
      "status": 400,
      "detail": "The following query parameters violate the JSON API spec: '_format'.",
      "links": {
        "info": "http://jsonapi.org/format/#query-parameters"
      }
    }
  }
}

Is not very helpful for those that were doing it before. Let's be more helpful.

{
  "errors": [
    {
      "title": "Bad Request",
      "status": 400,
      "detail": "JSON API does not need that ugly '_format' query string! 🤘 Use the URL provided in 'links' 🙏",
      "links": {
        "info": "http://d8/jsonapi/taxonomy_term/tags/3fadcc4a-6bb2-45dc-a8fe-87f3f6884ba9/parent"
      }
    }
  }
}

That's nicer, isn't it?

Status: Needs review » Needs work

The last submitted patch, 27: 2977600-27.patch, failed testing. View results

wim leers’s picture

I'm sure some would argue "why even bother restricting this if we can just whitelist this one parameter and not annoy anybody with it?". I understand that.

?_format got so much hate after Drupal 8 was released. So much. I'd need all my fingers and toes multiple times to count the number of times I've had to link to this CR: https://www.drupal.org/node/2501221. Many comments on that CR too, all complaining.

The reasons for having to use ?_format don't affect JSON API, thanks to it being served from its own routes, and hence there not being any need for content negotiation

But if tutorials/documentation keeps referring to ?_format, then that keeps harming us/Drupal.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.14 KB
new2.45 KB
gabesullice’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/src/EventSubscriber/JsonApiRequestValidator.php
@@ -55,9 +55,15 @@ class JsonApiRequestValidator implements EventSubscriberInterface {
+      $exception->setHeaders(['Link' => $uri_without_query_string]);

Let's make a followup so that this link is not under the `info` key.

wim leers’s picture

Assigned: Unassigned » e0ipso
gabesullice’s picture

This has been RTBC for a couple weeks, I think we're waiting for @e0ipso to commit it?

wim leers’s picture

Correct!

e0ipso’s picture

Status: Reviewed & tested by the community » Fixed

But if tutorials/documentation keeps referring to ?_format, then that keeps harming us/Drupal.

I'm not sure about this point. I think the Drupal hate comes from somewhere else, but I'll concede to

The reasons for having to use ?_format don't affect JSON API, thanks to it being served from its own routes, and hence there not being any need for content negotiation


Merging.

e0ipso’s picture

Assigned: e0ipso » Unassigned
Status: Fixed » Needs review
StatusFileSize
new2.29 KB

  • gabesullice committed 661b37e on 8.x-2.x
    Issue #2977600 by Wim Leers, gabesullice, e0ipso: Spec Compliance: `...
gabesullice’s picture

Committed because this was RTBC before and blocked on @e0ipso. In #35 @e0ipso attempted to merge that patch but got a merge conflict. Now, this LGTM and we can get this in!

gabesullice’s picture

Status: Needs review » Fixed
wim leers’s picture

🍻

Status: Fixed » Closed (fixed)

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