http://jsonapi.org/format/#crud

Problem/Motivation

While working on #2931785: The path for JSON API to core, I think we overlooked one very important thing wrt spec compliance. The fact that the JSON API module for Drupal incorporates two spec extensions:

  1. Fancy filters: https://gist.github.com/e0ipso/efcc4e96ca2aed58e32948e4f70c2460
  2. Partial success: https://gist.github.com/e0ipso/732712c3e573a6af1d83b25b9f0269c8

… yet we support Accept: application/vnd.api+json and Content-Type: application/vnd.api+json. Which signifies we're implementing the base spec. Additionally, we're not even mentioning in jsonapi.api.php that this module implements more than just the base spec!

http://jsonapi.org/format/#content-negotiation says:

Content Negotiation

Client Responsibilities

Clients MUST send all JSON API data in request documents with the header
Content-Type: application/vnd.api+json without any media type parameters.

Clients that include the JSON API media type in their Accept header MUST
specify the media type there at least once without any media type parameters.

Clients MUST ignore any parameters for the application/vnd.api+json
media type received in the Content-Type header of response documents.

Server Responsibilities

Servers MUST send all JSON API data in response documents with the header
Content-Type: application/vnd.api+json without any media type parameters.

Servers MUST respond with a 415 Unsupported Media Type status code if
a request specifies the header Content-Type: application/vnd.api+json
with any media type parameters.

Servers MUST respond with a 406 Not Acceptable status code if a
request’s Accept header contains the JSON API media type and all instances
of that media type are modified with media type parameters.

Note: The content negotiation requirements exist to allow future versions
of this specification to use media type parameters for extension negotiation
and versioning.

… and sadly, JSON API's extension system was reverted. http://jsonapi.org/extensions/ currently says An extension system is currently under development […] … on May 21, 2015.

In https://github.com/json-api/json-api/issues/614, Roy Fielding (of REST fame) is quoted to have written that media type negotiation like vnd.api+json is meaningless. He clarified his statement at https://github.com/json-api/json-api/issues/614#issuecomment-101741478. That issue spawned https://github.com/json-api/json-api/pull/650, which tried to improve things, but it was considered "too late before the imminent 1.0 release". Then, ironically, it is https://github.com/json-api/json-api/pull/660, which is mentioned nor linked in neither of the two issues above (614 an 650), and it simply removed the concept of/support for extensibility altogether. This was May 21, 2015.

It was worked on a bit in November 2015, in https://github.com/json-api/json-api/issues/915. Again in December 2015, in https://github.com/json-api/json-api/pull/957. And then again in https://github.com/json-api/json-api/pull/1195, in July 2017. And finally, https://github.com/json-api/json-api/issues/1207 in August 2017.

https://github.com/json-api/json-api/issues/1207 is the latest and greatest, confirmed by spec author dgeb at https://github.com/orbitjs/orbit/issues/479#issuecomment-358654119 on Jan 18, 2018. but it still hasn't actually gone anywhere — there's still nothing spec implementers can actually do …

Finally, note that http://jsonapi.org/format/#content-negotiation (quoted in full above) says the server must send an error response if any media type parameters are specified!

Proposed resolution

  1. Update jsonapi.api.php to state that the JSON API module implements the base spec + 2 extensions
  2. Figure out how to communicate in our HTTP responses that it's the base spec + 2 extensions … even though the JSON API spec literally forbids it, and there's a whole string of GitHub issues: 614, 650, 660, 915, 957, 1207.

Remaining tasks

Discuss!

User interface changes

TBD

API changes

TBD

Data model changes

TBD

Comments

Wim Leers created an issue. See original summary.

effulgentsia’s picture

Do we need Partial Success in jsonapi in Drupal core? What's the use case for it? Could it be moved to jsonapi_extras or jsonapi_partial_success? That doesn't entirely address this issue, but I'm just trying to wrap my head around the scope of what's needed in core.

e0ipso’s picture

@effulgentsia the case for partial success is for improved DX. Before the extension we had bug reports opened regularly about missing entities from collections (because they had failed somehow due to access denied or whatever), and missing fields that failed. Having an error in one of the items in the collection should not imply failing the whole request, but we still want comprehensive errors explaining what went wrong in the entities (or fields) that should have been there.

After the extension, and its documentation, have been available no more issues about that have appeared which I take as a good sign.

gabesullice’s picture

Do we need Partial Success in jsonapi in Drupal core?

From my perspective, partial success seems like something we can do conventionally without negotiating it as an extension because we're free to put information in the meta member at will.

@e0ipso, I completely agree that is has been a boon for DX reasons, but I'm not sure that we want to say that it's a necessary extension.

What's the use case for it?

I believe the use case is primarily because of how we handle access control on collection routes. We check $entity->access('view') on every resource. In many cases, users were requesting /jsonapi/node/foo specifying a page[limit] of 50, but then receiving < 50 resources. Their most common "error" was not filtering by status === 1. To address this, whenever access is denied to a resource, we make a note of it by putting an error object under meta.errors.

Could it be moved to jsonapi_extras or jsonapi_partial_success?

I don't think this would be possible. It would be really hard to pass off this information to another module in all the places where it comes up.

That said, I'm generally concerned that treating every omitted entity for access reasons as an "error" is the wrong approach from a technical perspective. Omitting inaccessible resources should be considered "normal" behavior not an error because including them would be "unsuccessful" on our part. We would have failed at providing access control appropriately.

From that perspective, the DX win is simply communicating that "entities were removed for access reasons," not in the extension itself.

We could communicate that in a number of different ways.

Saying that partial success is in extension makes me think it's negotiable, i.e., that by not including the extension, errors would not be serialized in the response. This would be akin to "development vs. production mode."

e0ipso’s picture

To be clear, I'm using extension not in the sense of negotiable plugins but in the sense that we are going further than the base spec and describing more of the API's behavior. Hence extending the specification document with our own rules. Both "extensions" are not negotiable. This is the main reason why we are not requiring the extension key in the Accept request header, nor responding with the extensions in use in the Content-Type response header.

In summary, I firmly believe that both fancy filters and partial success should remain as part of the module, even after an eventual core inclusion. I'm open to call them something different to extension given that 1) the extension system is in the limbo in JSON API spec and 2) they are cornerstones of our implementation and are non-negotiable.

gabesullice’s picture

I firmly believe that both fancy filters and partial success should remain as part of the module, even after an eventual core inclusion.

+1 to keeping them for core inclusion.

we are going further than the base spec and describing more of the API's behavior.

This is why I want to be really careful about how we treat partial success. Let's not tie ourselves to a specification WRT to surfacing errors, I think we need to test and solidify that a bit more. I think partial success can be a "convention" for now.

I'm open to call them something different to extension given that 1) the extension system is in the limbo in JSON API spec and 2) they are cornerstones of our implementation and are non-negotiable.

I do think Fancy Filters are an extension that should be negotiated between client/server.


So, @e0ipso, I think we agree more than we think, as usual. I don't think we want to put JSON API in core and require that developers download a contrib module to do filtering or have good DX. That's non-negotiable :)

wim leers’s picture

This is the main reason why we are not requiring the extension key in the Accept request header, nor responding with the extensions in use in the Content-Type response header.

Well, the spec also does not allow us to specify extensions in the request/response headers anyway, because an extension mechanism has not yet been specified!

I'm open to call them something different to extension given that 1) the extension system is in the limbo in JSON API spec and 2) they are cornerstones of our implementation and are non-negotiable.

Why haven't I thought of this? :D 👌

wim leers’s picture

Oops, cross-posted with #6.

+1 to keeping them for core inclusion.

Agreed that they're important to keep for the wonderful JSON API DX.

Let's not tie ourselves to a specification WRT to surfacing error, I think we need to test and solidify that a bit more.

I won't say no to better test coverage for this either. What parts of it do you feel are not solid enough yet? My impression is that the majority of it is already stable?

I do think Fancy Filters are an extension that should be negotiated between client/server.

This is honestly the bit I'm torn on the most. On the one hand I agree, because Partial Success merely uses meta, which is specifically for application-specific implementations, whereas Fancy Filters is much more fundamental.

On the other hand, the JSON API spec — despite being quite great — is utter garbage when it comes to filtering. http://jsonapi.org/format/#fetching-filtering says this:

Filtering

The filter query parameter is reserved for filtering data. Servers and clients
SHOULD use this key for filtering operations.

Note: JSON API is agnostic about the strategies supported by a server. The
filter query parameter can be used as the basis for any number of filtering
strategies.

That's literally all the JSON API spec has to say about it. Obviously this makes it impossible to have multiple interoperable implementations!

This is where I'm torn: every JSON API implementation must come up with its own decisions/design/convention/"extension" for filtering, because otherwise it's impossible to do any filtering.


Given this, the best we can do AFAICT is:

  1. Document these two non-negotiable extensions explicitly in jsonapi.api.php and explain the rationale.
  2. Explicitly link them in every JSON API response:
    "jsonapi": {
      "version": "1.0",
      "meta": {
        "links": {
          "self": "http://jsonapi.org/format/1.0/",
          "partial_success": "https://gist.github.com/e0ipso/732712c3e573a6af1d83b25b9f0269c8",
          "fancy_filters": "https://gist.github.com/e0ipso/efcc4e96ca2aed58e32948e4f70c2460"
        } 
      }
    },
    

Thoughts?

gabesullice’s picture

What parts of it do you feel are not solid enough yet? My impression is that the majority of it is already stable?

Error handling is not handled consistently across routes. It's not clear to me that we have a policy for what keys we use in our error objects. Some errors are not surface-able at all. I linked to a comment where I catalogued issues related to errors, but I'll reproduce it here:

  1. #2943176: 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"
  2. #2934362: Specify a "code" for every exception that JSON API throws
  3. #2934149: [>=8.5] JSON API routes not specifying _content_type_format route requirement, resulting in bad DX
  4. #2929428: [>=8.5] Convert "throw new *HttpException" into "throw new Cacheable*HttpException" where possible
  5. #2956084: Impossible to raise an error when an `include` is requested for an inaccessible relationship field.

On the other hand, the JSON API spec — despite being quite great — is utter garbage when it comes to filtering.

Sorry, this is a harsh and very narrow perspective on filtering. Filtering strategies should never be part of the base spec and should always be extension-based. Full stop. There can be entirely different, yet completely valid filtering strategies for all kinds of different use-cases all with varying levels complexity and tradeoffs. Specifying a filtering strategy in the spec would be entirely overbearing and make it far too rigid to be useful.

Given this, the best we can do AFAICT is... Document these two non-negotiable extensions explicitly in jsonapi.api.php and explain the rationale.

As I was saying above, fancy filtering is entirely severable and negotiable. The filtering strategy is really application dependent.

We've implemented what I think is a great default, but it has its limitations and is a pretty opinionated approach. I think it's essential that we make fancy filters a negotiable piece.

Re: Partial Success, I tried to make a case in #6 that we do not want to document it as an extension, but a less formal convention (for now).

Explicitly link them in every JSON API response.

I've started to make some really promising progress in the last week with the JSON API editors about coming up with a negotiation strategy so that we will be forward-compatible with JSON API 1.1 in Github #1207.

Let's postpone a practical decision on how we should indicate the fancy filtering extension until that is resolved/stalled.

e0ipso’s picture

From #9:

As I was saying above, fancy filtering is entirely severable and negotiable. The filtering strategy is really application dependent.

We've implemented what I think is a great default, but it has its limitations and is a pretty opinionated approach. I think it's essential that we make fancy filters a negotiable piece.

I don't agree that we need to have a negotiation layer for this. I wonder why you think it's essential. What are the limitations you are thinking about?

We could look into refactoring the query builder part (again) to make it a swappable service, but I am convinced that this is not an easy task. Even if this was already done we'd face maintenance on a complex, unbounded and abstract URL to Entity Query layer. I don't like that idea.

As for:

The filtering strategy is really application dependent.

I completely agree. The spec is rightfully agnostic about the types of queries different apps can do. Where we start to drift apart is that our application is Drupal, and our queries are the ones supported by Entity Query. We can even extend fancy filters further, to add support aggregation (as we've talked in Slack several times). So I think that one filtering strategy is good. I'll go further and say that it's recommendable to avoid duplicate code paths for the same feature.


#8++ I like both suggestions. I'd like to have the extensions moved to drupal.org eventually, so they belong to the community and not to my gist collection.

wim leers’s picture

Discussion is still ongoing in https://github.com/json-api/json-api/issues/1207. Hopefully that will reach consensus soon, then we won't be forced to invent our own approach anymore.

e0ipso’s picture

@Wim Leers we've been about to reach consensus before in this regard and it got stale. But I agree that ideally we'd want to implement the extension system.

Do we want to set some sort of timeout to wait for consensus?


I still think that we must have fancy filters by default (even if no profile is negotiated), and then let it be replaced by whatever if it comes the time via the extension system. That means that we don't need to support profile negotiation for now. Note that partial success is also a non-negotiable extension that is always on.

wim leers’s picture

Do we want to set some sort of timeout to wait for consensus?

Good idea. Shall we say April 15, i.e. just after DrupalCon?

I still think that we must have fancy filters by default

From a DX/consumer developer POV I wholeheartedly agree. Fancy filters are a super powerful feature.

e0ipso’s picture

#13 great to both accounts. If no one disagrees we'll time out after April 15.

wim leers’s picture

e0ipso’s picture

wim leers’s picture

Assigned: Unassigned » wim leers

Earlier today, I posted a review of the actual proposal/PR: https://github.com/json-api/json-api/pull/1268#issuecomment-394334144. AFAICT details still need to be refined, but in broad strokes, I think the proposal A) works, B) can already be implemented.

Taking this on.

wim leers’s picture

Title: Spec Compliance: JSON API's extensions (Fancy Filters & Partial Success) » Spec Compliance: JSON API's profile (Fancy Filters) needs to be explicitly communicated

Per:

  • From my perspective, partial success seems like something we can do conventionally without negotiating it as an extension because we're free to put information in the meta member at will.
  • This is why I want to be really careful about how we treat partial success. Let's not tie ourselves to a specification WRT to surfacing errors, I think we need to test and solidify that a bit more. I think partial success can be a "convention" for now.

I am reducing the scope to only Fancy Filters.

wim leers’s picture

Status: Active » Needs review
Issue tags: +Needs tests
StatusFileSize
new4.92 KB

This was simpler than I thought.

This will still need tests to verify that if a request lists profiles that are not supported result in a 400.

Next:

  1. Update the response documents per https://github.com/json-api/json-api/pull/1268.
  2. Aforementioned test coverage.
wim leers’s picture

Title: Spec Compliance: JSON API's profile (Fancy Filters) needs to be explicitly communicated » Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination) needs to be explicitly communicated

Although, don't we also need to have profiles for our sorting and pagination? This is what @e0ipso raised in #2812923-2: [FEATURE] Implement extension system, 1.5 year ago.

But http://jsonapi.org/format/#fetching-sorting is actually sufficiently prescriptive for basic usage, and hence is actually usable. The same is true for http://jsonapi.org/format/#fetching-pagination. But http://jsonapi.org/format/#fetching-filtering is empty; it doesn't say anything except that ?filter should be used for sorting; that's it!

So … I'm a bit torn.

I think the right thing to do here is to indeed have a profile for sorting and pagination too.

wim leers’s picture

Category: Plan » Task
Related issues: +#2812923: [FEATURE] Implement extension system

Turning this Plan into a Task. This is where all the discussion happened. Closing #2812923: [FEATURE] Implement extension system as a duplicate.

Status: Needs review » Needs work

The last submitted patch, 19: 2955020-19.patch, failed testing. View results

gabesullice’s picture

Don't we also need to have profiles for our sorting and pagination? ...

But http://jsonapi.org/format/#fetching-sorting is actually sufficiently prescriptive for basic usage, and hence is actually usable.

The same is true for http://jsonapi.org/format/#fetching-pagination. ...

I think the right thing to do here is to indeed have a profile for sorting and pagination too.

I think I disagree about a profile for sort and I certainly disagree about a profile for page.

Why?

As you mentioned, the spec is already sufficient to describe our use of sort, the only place where we've extended beyond the normative language of the spec is to follow the recommendation of using full-stop delimited field names for relationships (this is the only thing that is extension-worthy).

As for pagination, I think the pagination language of the spec is very intentionally silent on filtering strategies and enforces the use of HATEOAS links for communicating pagination links. IOW, I think this that it's the server's prerogative to add/update/change the link templates for pagination links at will and a compliant client should use those links as provided. If we had documented this better from the start, I would have gone so far as to say that we should not even consider it a BC break to move to cursor-based pagination in 2.x without a BC-flag.

Our pagination docs enforce this pattern:

If you take only one thing away from this guide, it should be that you should not construct your own pagination URLs.

You can take notice that the only mention of "offset" in the docs is in the sample server response document.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new2.7 KB
new7.58 KB

#19 had some unexpected failures, because I didn't think to think certain tests locally. Fixing.

gabesullice’s picture

I'm not sure that this is the right application of the profile media type parameter in this context.

Fancy filtering changes nothing about the response document. So, if I'm reading the PR correctly, we should be advertising the fancy filter extension via the jsonapi.profiles document member and requiring a client to specify the profile in the query parameter when filtering, but not providing it in every response header.

The current approach would work for partial success because that dictates how the document should be interpreted, but not for fancy filters because it's about how a server should interpret a request's query.

Am I thinking about this correctly? Maybe I'm missing something.

wim leers’s picture

Issue tags: -Needs tests
StatusFileSize
new13.12 KB
new6.88 KB

Test coverage:

This will still need tests to verify that if a request lists profiles that are not supported result in a 400.

Note that this is a stricter reading of the spec. But that's fine. It makes things simpler for future maintenance/BC.


This turned out to be extraordinarily difficult because \Symfony\Component\HttpFoundation\AcceptHeader::fromString() is broken; it parses Accept: text/html;charset="UTF-8" as two separate media types… And the same for $acceptable_profiles = AcceptHeader::fromString('application/vnd.api+json;profile="https://www.drupal.org/jsonapi/profile/fancy_filters"'): this results in a application/vnd.api+json and a https://www.drupal.org/jsonapi/profile/fancy_filters item … 😭

https://github.com/symfony/symfony/commit/6b601bd9a639ca141ebfcc3b6b91c2... created the current logic, and its test coverage leaves much to be desired. It looks like Symfony recently improved this in Symfony 4: https://github.com/symfony/symfony/commit/b435e80cae24e85d6e0e923ae932b2... (for https://github.com/symfony/symfony/pull/24699).

So, either way, it looks very unlikely that this will get fixed in the Symfony version that Drupal depends upon. This sadly means we'll need our own parsing. Fortunately, we can keep this to a minimum :)

wim leers’s picture

#23:

I think I disagree about a profile for sort and I certainly disagree about a profile for page.

[…]

This is exactly the doubt I was having.

As you mentioned, the spec is already sufficient to describe our use of sort, the only place where we've extended beyond the normative language of the spec is to follow the recommendation of using full-stop delimited field names for relationships (this is the only thing that is extension-worthy).

That's what I thought, until I looked at https://www.drupal.org/docs/8/modules/json-api/collections-and-sorting … we apparently support not only a path/pointer to sort and a direction, but also a language (langcode, see \Drupal\jsonapi\Query\Sort::LANGUAGE_KEY).

As for pagination, I think the pagination language of the spec is very intentionally silent on filtering strategies and enforces the use of HATEOAS links for communicating pagination links.

Mostly agreed.
I disagree that it's only HATEOAS. Because our pagination strategy assigns a particular meaning to page[offset] and page[limit]. That's something a client may very well rely upon. Arguably, page[offset] is not an API, but page[limit] is. Even though page[offset] is only mentioned in the sample response document, that is still the only way a client can ensure they get 2 or 100 responses. Of course, if we say that the client is not allowed to specify a limit, aka that's not a supported API, then sure … but if that were the case, we should also have hardcoded page[limit].

wim leers’s picture

#25:

I'm not sure that this is the right application of the profile media type parameter in this context.

You may be correct in a purely theoretical perspective. But there's only one explicit mention of that in https://github.com/json-api/json-api/pull/1268's current PR:

+* `profile`: an array of [links][link], each giving the URI of a
+  [profile][profiles] in use in the document.
Fancy filtering changes nothing about the response document.
  1. Correct, because the structure of response documents stays identical.
  2. Incorrect, because the contents of response documents changes.

For example: /jsonapi/user/user vs /jsonapi/user/user?filter[name]=Wim have vastly different content.

So, if I'm reading the PR correctly, we should be advertising the fancy filter extension via the jsonapi.profiles document member and requiring a client to specify the profile in the query parameter when filtering, but not providing it in every response header.

This makes for fairly large variations in Content-Type response header. When for example requesting individual resources, there is indeed little point in listing the Fancy Filters profile in the media type's profile parameter. But there's also no harm. And listing it always is simpler. Both in our implementation and our test coverage. But perhaps most importantly, it's also simpler when reverse proxies are involved; rather than needing to know the many possible profiles, they can just hardcode a particular Content-Type, because the Drupal JSON API module will always respond in the same way.
And when thinking of the future, when the JSON API module gains support for additional profiles, old clients would continue to request the current set of profiles. Therefore caching the different variations becomes simpler.

Additionally, it's totally feasible for JSON API response documents to start to contain more links (cfr. HATEOAS); when Fancy Filters is supported, there are various links one could imagine (e.g. on a user--user resource, links to filtered collections of all other resources for which the current user is the owner, e.g. {"links": {"owned-articles": "https://example.com/node/article?filter[uid]=18a503f7-5900-457b-a40c-49d4a3136e09).

wim leers’s picture

Status: Needs review » Needs work
+++ b/src/StackMiddleware/FormatSetter.php
@@ -36,6 +42,30 @@ class FormatSetter implements HttpKernelInterface {
+        $unsupported_profiles = static::getUnsupportedProfiles($request);
+        if (!empty($unsupported_profiles)) {

@@ -62,11 +92,74 @@ class FormatSetter implements HttpKernelInterface {
+  protected static function getUnsupportedProfiles(Request $request) {

+++ b/tests/src/Functional/ResourceTestBase.php
@@ -879,6 +879,25 @@ abstract class ResourceTestBase extends BrowserTestBase {
+    // DX: 400 when requesting unsupported profiles.
+    $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json;profile="https://example.com http://foobar.com';

This is only testing the simple case of accepting only a media type with unsupported profiles. We should also test two other cases:

  1. some unsupported profiles
  2. two Accept values; one with unsupported profiles, one without profiles — which should then result in a 200
wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new13.46 KB
new3.13 KB

Addressed #29.

wim leers’s picture

Assigned: wim leers » Unassigned
StatusFileSize
new55.71 KB
new64.15 KB

And finally, the updates to the jsonapi top-level document member! Lots of test expectations had to be updated, hence the size of this interdiff.

Unfortunately there is something super weird going on with:

  • CommentTest
  • EntityTestTest
  • MediaTest
  • TermTest

For some reason, they get all the jsonapi.links.profile links twice! I have not been able to figure out how this happens. It'd be great if somebody else could take a stab at this.

Status: Needs review » Needs work

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

gabesullice’s picture

#27: count me convinced. Let's have extensions for sort and page :)

#28

1) Correct, because the structure of response documents stays identical.
2) Incorrect, because the contents of response documents changes.
For example: /jsonapi/user/user vs /jsonapi/user/user?filter[name]=Wim have vastly different content.

Given that the spec says:

A profile MAY assign meaning to particular elements of the document structure, such as resource attributes or members of meta objects, and even to query parameters whose behavior is left up to each implementation, such as filter and all those that contain a non a-z character.

I think (1) is the proper understanding of how a profile alters the document. A profile is about "assigning meaning" to the structure/syntax of the document and allowed query params, not about 0, 1 or 2 items being returned.

Consider this language:

Clients and servers MUST include the profile media type parameter in conjunction with the JSON API media type in a Content-Type header to indicate that they have applied one or more profiles to a JSON API document.

Likewise, clients and servers applying profiles to a JSON API document MUST include a top-level links object with a profile key, and that profile key MUST include a [link] to the URI of each profile that has been applied.

^ Emphasis mine. It's the "to indicate that the profile has been applied" language that makes me think that we just want to add that media type profile when ?profile=https://www.drupal.org/jsonapi/profile/fancy_filters is in the request URL.

But perhaps most importantly, it's also simpler when reverse proxies are involved; rather than needing to know the many possible profiles, they can just hardcode a particular Content-Type, because the Drupal JSON API module will always respond in the same way.

I'm not sure I fully understand this use case, can you expand on that point? (I'll admit I'm having a little bit of a pavlovian response to this line of reasoning, it's eerily similar to the ?_format reasoning that's been a big pain point for us. I'll try to overcome that! :P)

Finally, I don't see that we're handling: "servers applying profiles to a JSON API document MUST include a top-level links object with a profile key".

You fixed this already!

wim leers’s picture

Issue tags: +Needs change record

Discussed with @gabesullice, and we agreed this belongs in 2.x.

I'll continue working on this on Monday; this will be my top priority.

wim leers’s picture

Version: 8.x-1.x-dev » 8.x-2.x-dev

😅

wim leers’s picture

Title: Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination) needs to be explicitly communicated » Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated
Assigned: Unassigned » wim leers
wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.2 KB
new65.3 KB

This patch should fix the failures in tests other than the 4 mentioned in #31.

#33:

  1. I think (1) is the proper understanding of how a profile alters the document. A profile is about "assigning meaning" to the structure/syntax of the document and allowed query params, not about 0, 1 or 2 items being returned.

    I totally get where your'e coming from. But

    That's not quite true either, because https://github.com/json-api/json-api/pull/1268 says

    Profiles might describe how the filter or page params are used.

    and its commit says:

     +A profile **MAY** assign meaning to particular elements of the document
     +structure, such as resource attributes or members of meta objects, and even
     +to query parameters whose behavior is left up to each implementation, such 
     +as `filter` and all those that contain a non a-z character.
    

    So a profile can cover an aspect of a JSON API implementation that results in no alterations to JSON API documents whatsoever.

  2. Consider this language:
    […]
    It's the "to indicate that the profile has been applied" language that makes me think that we just want to add that media type profile when ?profile=https://www.drupal.org/jsonapi/profile/fancy_filters is in the request URL.

    A. I think it's good that you're reading it as strictly as possible.
    B. See above; some profiles don't have anything in the document, so following this logic they'd never appear in the Content-Type response hader!
    C. The spec says MUST in case the document is being altered compared to the base spec. The spec does not say MUST NOT when the document is not being altered. And this is IMHO consciously: it allows a JSON API implementation to KISS, by always sending the same Content-Type response header if it does not have any optional/negotiable JSON API profiles.
    D. Certainly having the profile query parameter there is not a hard requirement to list profiles in the Content-Type response header. That query parameter can be used to require a profile to be applied. But a profile can also be requested using Accept. It's even allowed to respond to an Accept: application/vnd.api+json request with a Content-Type: application/vnd.api+json; profile="https://www.drupal.org/jsonapi/profile/fancy_filters https://www.drupal.org/jsonapi/profile/page_limit https://www.drupal.org/jsonapi/profile/sort_language" response; it's not required that a JSON API server makes every profile optional.

    That's why I added the following to jsonapi.api.php:

     * @section profiles Profiles
     *
     * The JSON API module for Drupal implements 3 profiles that extend the base
     * spec, without altering the basic semantics. All 3 are required — this means
     * none of them are optional or negotionable. We may add negotionable profiles
     * in the future.
    

    All of this is supported by the spec:

     +Servers **MAY** add profiles to a JSON API document even if the client has not
     +requested them. The recipient of a document **MUST** ignore any profile extensions 
     +in that document that it does not understand. The only exception to this is profiles
     +whose support is required using the `profile` query parameter, as described below.
    

    The combination of these choices allows our implementation to remain simple and maintainable. If everything becomes negotiable, then that is an enormous burden on us maintainers. We can still choose to support negotiable profiles in the future.

  3. Fancy filtering changes nothing about the response document. So, if I'm reading the PR correctly, we should be advertising the fancy filter extension via the jsonapi.profiles document member and requiring a client to specify the profile in the query parameter when filtering, but not providing it in every response header.

    But perhaps most importantly, it's also simpler when reverse proxies are involved; rather than needing to know the many possible profiles, they can just hardcode a particular Content-Type, because the Drupal JSON API module will always respond in the same way.

    I'm not sure I fully understand this use case, can you expand on that point? (I'll admit I'm having a little bit of a pavlovian response to this line of reasoning, it's eerily similar to the ?_format reasoning that's been a big pain point for us. I'll try to overcome that! :P)

    I interpreted your original remark as also meaning that when ?profile=https%3A%2F%2Fwww.drupal.org%2Fjsonapi%2Fprofile%2Ffancy_filters is missing, we also should omit that profile from the Content-Type response header. And I thought you meant that this should be true for every profile. Add to that the fact that you can also request a profile to be applied using the Accept header, and then you reach the point where there can be several variations of responses to the same URL, each with different combinations of profiles applied. Not only is this more complex, it also makes caching in a reverse proxy super hard, because that requires reverse proxies to normalize the values of the Accept request header. To be able to normalize them, the reverse proxy needs to know about all the values that the application cares about. This is brittle and a nightmare to support.

    But now I suspect you were not at all suggesting to respect Accept header based JSON API profile negotiation. You're only suggestion profile query parameter based JSON API profile negotiation. Is that correct?

    And I evensuspect that you meant this should only be applied to the Fancy Filters profile, because that is what defines our interpretation of the filter query parameter, and if that profile is not specified, then theoretically speaking our JSON API implementation is not supposed to know how to interpret it. Is that correct? (To this I say: it's fine to not specify this in the query parameter; it's fine for clients to know that this is the default filtering mechanism. That'd also prevent a pretty significant BC nightmare when updating to 2.x.)

Status: Needs review » Needs work

The last submitted patch, 37: 2955020-37.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
new3.03 KB
new67.34 KB

The last one was the trickiest; JsonApiDocumentTopLevelNormalizer had to be modified in just the right spot.

wim leers’s picture

StatusFileSize
new2.23 KB
new67.4 KB

Fixed CS violations.

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

Status: Needs review » Needs work

The last submitted patch, 40: 2955020-40.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.34 KB
new68.43 KB
wim leers’s picture

Issue tags: +Needs tests
StatusFileSize
new71.71 KB
new7.1 KB

Still to be done:

  1. validating request document's link profiles (if any):
     +Except for the `profile` key, each key present in a links object **MUST** have
     +a single link as its value. The `profile` key, if present, **MUST** hold an
     +array of links.
    
  2. validating request document's profiles (if any):
     +* `profiles` - an object with profile URIs as keys and corresponding
     +  [profile descriptors](profile-descriptors) as values.
  3. validating the profile query parameter:
     +A client **MAY** use the `profile` query parameter to _require_ the server to
     +apply one or more profiles when processing the request. The value of the `profile` 
     +query parameter **MUST** equal a URI-encoded whitespace-separated list of profile URIs.
     +
     +If a server receives a request requiring the application of a profile or
     +combination of profiles that it can not apply, it **MUST** respond with a `400
     +Bad Request` status code.

Implemented all three, but test coverage is still necessary. I'd first like feedback on the general approach.

wim leers’s picture

I believe #44 implements 100% of https://github.com/json-api/json-api/pull/1268 😀 🎉 🤞

Looking forward to feedback!

Status: Needs review » Needs work

The last submitted patch, 44: 2955020-44.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.48 KB
new71.98 KB

Status: Needs review » Needs work

The last submitted patch, 47: 2955020-47.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new589 bytes
new72.26 KB
wim leers’s picture

Assigned: wim leers » Unassigned
Issue summary: View changes
Status: Needs review » Postponed
Issue tags: +Needs upstream feature
Parent issue: #2931785: The path for JSON API to core »
Related issues: +#2931785: The path for JSON API to core

@gabesullice and I discussed this in detail last week, together with core committers @xjm, @webchick, @effulgentsia and @lauriii.

Until https://github.com/json-api/json-api/pull/1268 lands and JSON API spec version 1.1 is released, the JSON API module for Drupal would not possibly be able to implement this according to the spec, since the spec hasn't been finalized yet. This might be okay for a contrib module, but imagine what the consequences are for when JSON API is added to Drupal core: we'd end up with potentially our own fork of the spec. This is too big a risk.

It's better for now to let JSON API to work as it does: its primary addition is "fancy filters", and the spec already gives us the leeway to implement this as we see fit: http://jsonapi.org/format/#fetching-filtering.

Therefore, postponing this. Also removing this from #2931785: The path for JSON API to core.

wim leers’s picture

Status: Postponed » Active

JSON API maintainers just committed profile support to v1.1
https://github.com/json-api/json-api/commit/55cd721e55d4facec6e3242f2724...! See it at http://jsonapi.org/format/upcoming/#profiles.

The 1.1 version of the JSON API spec has not yet been released though. But this does mean this is up for debate again.

What do people think? Should we do this in 2.0? I'm inclined to say "yes" — I think the probability of this still changing is very low. Of course, we can only be 100% certain if this actually ships in a new JSON API spec version.

wim leers’s picture

Status: Active » Postponed

Discussed with @gabesullice. He pointed out that we already postponed this to a 2.x minor release because it wouldn't break BC anyway. That's still true today.

Plus:

Until https://github.com/json-api/json-api/pull/1268 lands and JSON API spec version 1.1 is released, the JSON API module for Drupal would not possibly be able to implement this according to the spec, since the spec hasn't been finalized yet.

1.1 is still not released, so there's still a risk.

Hence, marking this postponed again, until there's an actual JSON API spec update released.

effulgentsia’s picture

Hm, this is a tough one. On the one hand, I can see the logic for not including this in a tagged release of 2.x until after the spec is finalized. On the other hand, I think us implementing the spec and having real apps/sites in the wild using the implementation and reporting problems, if any, would be an extremely valuable way of validating the spec, and perhaps even having an opportunity to fix the spec (if necessary) prior to its release.

As a parallel, we began the HTML5 initiative in Drupal 8 core in 2011, even though the spec wasn't finalized as a W3C Recommendation until late 2014. It just so happened that Drupal 8.0 wasn't released until 2015, but we didn't know that would be the case in 2011. If 8.0 was ready in early 2014, we would have released it then, ahead of the HTML 5.0 final spec release. I realize it's a different situation though, in that browsers had already implemented the HTML5 spec draft, so we had pretty good confidence it wouldn't change much, whereas for json:api 1.1, we might be among the first implementors, so it might still be somewhat unstable.

wim leers’s picture

As a parallel, we began the HTML5 initiative in Drupal 8 core in 2011, even though the spec wasn't finalized as a W3C Recommendation until late 2014.

The difference is that HTML is declarative, and browsers interpret it. It won't require code changes on specific projects. Or, if browsers change, then everybody has to.
With JSON API, clients would need to be modified.

That, plus, Drupal 8 was still very far from being released, so any changes that would still need to happen would not impact actual production sites!

gabesullice’s picture

I think the next step if for us to begin drafting our profile specifications.

That will help us validate the profile concept and ensure that all the things that we want to have profiles for are actually permissible within the boundaries of this profile specification.

For example, I'd like to test that the way we handle relationship arity is actually something we can express as profile—all of the given examples are simply about contextualizing keys/values in a document, like how to interpret a timestamp.


WRT to end-user facing API changes, I think it's fine to postpone any of that.

None of our profiles are negotiable and the spec says we can apply profiles by default (we're already doing this, in a way). Clients won't need to request that our profiles be applied, so clients won't need to be updated. IOW, there's very little that we're supposed to actually do except document our profiles.

AFAICT, the only requirements we will eventually be forced to respect are these:

Clients and servers MUST include the profile media type parameter in conjunction with the JSON API media type in a Content-Type header to indicate that they have applied one or more profiles to a JSON API document.

Likewise, clients and servers applying profiles to a JSON API document MUST include a top-level links object with a profile key, and that profile key MUST include a link to the URI of each profile that has been applied.

But of course, we can't do any of that until we actually have profiles written and publicized. And if I snapped my fingers and had that done, we could still postpone API changes because these requirements are additive changes that won't break BC.

As for publicizing our profiles, I've already reached out to the JSON API maintainers to figure out how we should expect to do that.

wim leers’s picture

Quoting https://github.com/json-api/json-api/issues/600#issuecomment-429380085:

@wimleers It's gonna happen very soon. I'm just working on a PR for profile registration, and I think #1304, #1302 and #1199 should get in too. The way I think of beta, though, is that it's like TC39's Stage 3. I.e., people should be trying to implement the new features once they get to beta stage (even if not shipping them yet), just to make sure that implementation isn't unexpectedly difficult before we finalize the designs for good.

effulgentsia’s picture

The way I think of beta, though, is that it's like TC39's Stage 3. I.e., people should be trying to implement the new features once they get to beta stage (even if not shipping them yet)

I agree with that, and it's similar to what I was suggesting at the beginning of #53. #55 goes into a bit more detail on what that might entail for us. I still think it would be valuable for patches to be posted here (even if they're not committed until the spec is out of beta) that implement as closely as we can to the 1.1 spec, just to identify in the process of writing and reviewing those patches if there are any last-minute alterations that we want to propose to the spec.

wim leers’s picture

Agreed. Now to find the time.

wim leers’s picture

Status: Postponed » Active

My upstream comment I linked to in #56 triggered a public announcement: https://github.com/json-api/json-api/issues/600#issuecomment-430763592 + https://twitter.com/jsonapi/status/1052647059982077953 :)

Time to work on this, to help validate the spec.

wim leers’s picture

I'd like to see us move this forward again. Let's get a patch here that is green and does add the necessary profile metadata to our responses.

gabesullice’s picture

Priority: Critical » Major

I too would like to see this move forward again. The JSON:API spec maintainers are getting a bit antsy because of the lack of adoption of v1.1. This would help.

However, I don't think this issue qualifies as a critical, moving it down to a major.

wim leers’s picture

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

Drupal will not adopt 1.1 until 1.1 is finalized; it's too big a risk for Drupal otherwise.

But we could and should have green patches proving that it's working; that should give sufficient confirmation to the spec maintainers?

On the other hand: since we're now very early in the Drupal 8.8 cycle, we could commit this and revert before 8.8.0 if version 1.1 of the spec is not finalized before the 8.8.0 beta (per https://www.drupal.org/core/roadmap, the week of October 14, 2019). Would that work better for the spec maintainers? Nonetheless, that would still mean no significant real-world use, so it wouldn't be very different from just having a green patch.

effulgentsia’s picture

I think getting a working patch here is a good place to start. If that patch turns out to be large or complex, then we may want to come up with ideas for how to best support people in testing and reviewing it, especially if those tests and reviews are needed for the spec maintainers to feel confident about finalizing the spec. If the patch turns out to be relatively small and simple, then perhaps it can just remain here as a patch until the spec is finalized.

wim leers’s picture

Sounds good! Thanks for chiming in @effulgentsia 🙏

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.

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.

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

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

As of Oct. 1 2020, spec v 1.1 is in RC3.

Also, worth noting that this will help unlock functionality in clients, e.g. Orbit.js.

bbrala’s picture

I want to add here that if we have a definition I can probably get the filter implementation into (the very good) laravel-json-api package, which would be nice :)

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

Title: Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated » [PP-1] Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated
Version: 9.4.x-dev » 9.5.x-dev
Status: Active » Postponed

Some progress upstream! Since this is effectively blocked on json:api 1.1, I'm marking this PP. However, the json:api lead maintainer has committed to getting 1.1 out in June. Let's help him out with that and then we can start on this and the related #3279320: Implement extension link relation type for resource collection links on entrypoint which would be an extension.

bradjones1’s picture

Issue tags: +json:api 1.1

Adding tag for tracking json:api 1.1 related issues.

bradjones1’s picture

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

Title: [PP-1] Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated » Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated
Status: Postponed » Active

JSON:API 1.1 is out. Removing the PP.

bbrala’s picture

We need to revisit this with the implemented profiles and extentions in mind:

Extensions

Extensions provide a means to “extend” the base specification by defining additional specification semantics.

Extensions cannot alter or remove specification semantics, nor can they specify implementation semantics.

Profiles

Profiles provide a means to share a particular usage of the specification among implementations.

Profiles can specify implementation semantics, but cannot alter, add to, or remove specification semantics.

And the difference is basically defined here:

Semantics

All document members, query parameters, and processing rules defined by this specification are collectively called “specification semantics”.

Certain document members, query parameters, and processing rules are reserved for implementors to define at their discretion. These are called “implementation semantics”.

All other semantics are reserved for potential future use by this specification.

bbrala’s picture

bbrala’s picture

Title: Spec Compliance: JSON API's profile (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated » Spec Compliance: JSON API's profile/extention (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated
bbrala’s picture

Fancy filters: This would be an refinement of the filter parameter, therfor a profile.
Partial success: This is harder, is this an extention? If so this data could be added (while scoped by extention key) to the document. You could also say its an profile, it just adds some new information to another 'loosely defined' part of the document, therefor doesn't really rewrite the spec.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.