Skip to content

Profile Extensions, Take 3: Extension Rules #1195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed

Conversation

ethanresnick
Copy link
Member

@ethanresnick ethanresnick commented Jul 23, 2017

Hey all!

Here is an updated PR for profile extensions. I’d really like to get this merged soon, or else get a concrete list of blockers if it’s not mergeable as is. We’ve been sitting on this for two years, and the design has gone through quite a lot of vetting and revision, so I think it’s time. Besides, 1.1 is still a draft, so we can still change things if this design proves unworkable.

History

For anyone reviewing this PR who hasn’t followed (or has forgotten) the prior discussions around JSON:API extensions, here are some links describing, broadly, why this proposal works the way it does:

  1. Issue Rethink extension negotiation #614 (starting here) lays out the broad design goals for JSON:API extensions and groups possible extensions into two different types. Specifically, it separates out “profile extensions”—which are limited in what they can do, in order to simplify processing, maximize interoperability, and compose seamlessly—from more powerful “comprehensive extensions”.

  2. Issue Extensions Overhaul #650 contains my first stab at specifying the extension system. This design tried to cover both profile extensions and comprehensive extensions, as well as an extension advertising/discovery mechanism.

  3. After Extensions Overhaul #650 proved to be too big to consider all at once, I decided to focus on profile extensions first, and I put forward a refined design for them in Extensions next steps #915. The first post in Extensions next steps #915 offers some background on why I chose to focus on profile extensions and also why I think extensions are so important generally. That post also offers some additional design goals unique to profile extensions.

  4. The discussion in Extensions next steps #915 was wide-ranging. It covered what restrictions should go on profile extensions, whether and how they should be registered, whether and how they should be versioned, and how extension-added data should be distinguished from data that might be defined by future versions of the base specification. We explored many different possibilities and considered models developed by, and lessons learned from, other standards efforts. I was particularly influenced by a W3C TAG–recommended model of compatibility (defined in detail here), which undergirds the approach to extension versioning used in this PR (namely, extensions are unversioned and must change in fully-compatible ways over time).

  5. Finally, Profile extensions #957 attempted to put the conclusions of Extensions next steps #915 into specification-ready text. This text was reviewed at an editors meeting and tweaked—most notably, to require that extension-defined data live under the meta object. This PR is based on Profile extensions #957.

New Changes

For easier review, this PR rebases the latest version of #957, and some changes from #957 have been split off into a separate PR. In particular, this PR defines profile extensions, while #1196 outlines the (optional but encouraged) registration process. Beyond splitting up #957, this latest revision contains some small changes:

  1. I removed the requirement that an array of profile links be added in the links object to indicate the extensions in use. The extensions in use are still conveyed in the aliases object in the document body, and in the Content-Type header.

  2. In the error object, I introduced type as a top-level key, rather than as a link. (For more on the error object changes, see the commit messages; they provide some context on why these changes are needed and why they’re acceptable from a compatibility POV.)

  3. I've decided to keep the aliases key at the top-level, rather than nest it under jsonapi, because it includes data that applies to the current document, not necessarily to the server as a whole (which is more what the jsonapi key is for iiuc).

Next Steps

Again, I think this is either ready to merge or very close, especially since we'd only be merging it into a draft spec. Still, I'm open to any feedback, although I'd rather not get bogged down bikeshedding names. I know it's a lot to review, so I've tried to break it up into coherent commits with detailed commit messages.

- State that a profile parameter is allowed, which will be used for extensions.

- Restructure section to add a "universal responsibilities" section that covers (existing) rules around the content-type header and parameter usage

- Clarify what a media type parameter is. Closes #762

- Add a section detailing how 1.0 servers should respond to clients that send extended documents with the profile parameter in Content-Type. (Note: we don't need rules the other way, as clients are supposed to just ignore the profile parameter in the Content-Type for the server's response.)
This leverages standard HTTP conneg.
Go over what they are and, equally importantly, what they arent allowed to do.
1. We add a type member to enables the client to distinguish between different error conditions that share the same status code, by matching on the `type`
URI.

This is needed for the extension system, where the client needs to distinguish between the `profile` parameter not being supported by server, vs. the client providing insufficient profile extensions, both of which trigger a 415.

However, its also a huge win for developer ergonomics, as having an error URL you can follow to get more infomration about a problem is convenient and common.

This is the start of what I suggested in #683.

2. We add the `missing` member as a way for the server to indicate that a required extension is missing. However, we also use this member to clean up the definition of the pointer and parameter fields (as it's not clear that they could previously be used for *missing* document values/parameters).

3. We deprecate the `code` member, because it's functionality is better served by the type field. In particular, application-specific values can still be used (just by
replacing `code` with a `type` URI that the server controls), but `type` also allows the reuse of well-known error types—both those defined in the JSON API spec (which will be necessary for extensions) and those used by, e.g., problem+json.

Removing members from the definition of an error object is NOT a breaking change. Old APIs that send these members will still work with old clients, and new clients won’t choke either, as they’ll simply
ignore the old (and no-longer-defined) members. And, because these values were never required, all clients had to be able to function without them anyway. [See also my inline not about allowing clients to
continue to support old members, if they want.] However, making it illegal to produce certain error object members going forward has the advantage of simplifying an error format that’s redundant right now and a bit underspecified.
Since the info in it can now be extension defined, rather than purely non-standard.
@ethanresnick ethanresnick mentioned this pull request Jul 23, 2017
@ethanresnick ethanresnick modified the milestone: JSON-API 1.1-beta Jul 23, 2017
## <a href="#extending" id="extending" class="headerlink"></a> Extending JSON API

An API **MAY** support one or more "profile extensions" to supplement the
capabalities of this specification.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

capabilities*

"templates": [{
"name": "publish",
"method": "PATCH",
"uri": "/articles/1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing comma here

Copy link
Contributor

@jamesplease jamesplease left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't read all of the issues that you've linked to from this issue, but your decision to focus on "profile extensions" seems good.

Others may be more familiar with all of the discussions around extensions, but from my outsider's perspective, this seems to be in a good place to merge and iterate on.

Thanks for doing this work, @ethanresnick !

@ethanresnick
Copy link
Member Author

Thanks for the review @jmeas, and the encouraging comments. Typos fixed!

@dgeb
Copy link
Member

dgeb commented Jul 24, 2017

Thanks for rebasing your work and reopening this discussion, Ethan.

I'd like to try to establish consensus on some fundamentals before engaging in a thorough detailed review.

You have defined profile extensions as follows:

An API MAY support one or more "profile extensions" to supplement the capabilities of this specification.

A profile extension is a small, separate specification that defines a set of values that can be added to a JSON API document, and the meaning of those values at the different places they can occur.

My preference would be to define profile extensions as separate specifications that define particular usage patterns that are wholly compatible with the base JSON API specification.

I believe that profile extensions can be much more useful if they are allowed to define usage expectations of any specific part of the spec including query params, attributes, relationships, meta, etc. The only requirement is that these extensions must fully conform with the base spec.

Here are a few of the use cases we have discussed in the past:

  • particular pagination patterns through specifying usage of the page param

  • particular filtering patterns through specifying usage of the filter param

  • inclusion of set data in top-level meta (e.g. pagination statistics in a partial set)

  • inclusion of timestamps as either attributes or meta info per resource

  • expectations about the nature of IDs - i.e. client or server generated, UUIDs, etc.

I am concerned that this PR does not allow for some of these use cases. Do you agree that they should be in scope? Do we have a fundamental disagreement on the purpose of profile extensions?

@ethanresnick
Copy link
Member Author

ethanresnick commented Jul 24, 2017

Hey Dan!

Good to be chatting with you again. And I'm glad we're backing up to look at the fundamentals before we get lost in the weeds.

I want to focus on 3 of your use cases, which I agree aren't neatly addressed by the current design:

  • particular pagination patterns through specifying usage of the page param

  • particular filtering patterns through specifying usage of the filter param

  • expectations about the nature of IDs - i.e. client or server generated, UUIDs, etc.

I see the value in an API being able to communicate all of these things to clients. In my head, though, this has been a slightly different feature than profile extensions. In particular, most of these usage patterns apply on an API-wide level, so my assumption is/has been that they should ultimately be described in some sort of "home" document living at the root of the API. That document might reference some separate specifications, but these would be a different a type of extension than profile extensions.

A key distinction, I think, is whether the extension adds meaning to contents in the request/response document vs. whether it specifies things that are irrelevant to interpreting the current document, but might be useful in other ways (like in helping clients constructing future requests). To me, profile extensions are only about the former, whereas the extensions you're describing would also do the latter.

I guess we could make one type of extension that does both, but there are some ways in which, to me, they really do feel like different concerns. For example, a client might want to request that the server apply a profile extension (in its current definition) to a document, in order to get extra info, but it really doesn't make sense to have a client requesting/negotiating based on extensions describing API usage patterns.

About your other use cases...

inclusion of timestamps as either attributes or meta info per resource

I agree that extension being able to give meaning to an attribute would be nice. For example, a resource object like:

{ 
  "type": "examples",
  "id": "1",
  "attributes": { 
    // let's imagine both of these attributes have extension-defined meanings.
    // maybe the color extension defines a standard set of colors.
    "last-modified": "2017-07-24T20:08:45.497Z",
    "color": "green"
  }
}

is obviously nicer JSON than

{ 
  "type": "examples",
  "id": "1",
  "attributes": {},
  "meta": {
    "last-modified": "2017-07-24T20:08:45.497Z",
    "color": "green"
  }
}

and being able to keep the data in the attributes object is a plus. So, I'm all for changing the current design to support assigning meaning to attributes. And I think doing that shouldn't be too hard (it seems to amount to, in one way or another, letting the aliases functionality reach into the attributes bag).

One thing I want to preserve, though, is the ability for someone reading a JSON:API document to know which extensions are adding meaning to which document members, just based on the contents of the document. In the current design, if I see { "meta": { "last-modified": "..." } }, I know that the meaning of the value at the "last-modified" key is defined according to whatever extension is associated with the "last-modified" alias. I can then easily look up its meaning in that extension's spec. By contrast, if extensions aren't explicitly associated with the data they describe, I have to be familiar with all the extensions in use on the document to know which of them, if any, are defining a "last-modified" attribute.

Also, if each attribute isn't explicitly associated with an extension, it's possible for two extensions to give the same attribute contradictory meanings—either when the extensions are first created or, importantly, as they're updated over time to add new features. Part of the design goals I set for profile extensions was to allow extensions to evolve independently and without versioning schemes, and this isn't possible if multiple extensions can be applying to the same piece of data.

define usage expectations of ... relationships

As far as extensions applying to relationships: I think this is almost certainly just asking for a way to map relationship names to what RFC5889 would call "extended relation names", which I totally agree we should have (and which we've discussed a bit going all the way back to #159), but I think that's also a separate feature from profile extensions.

@dgeb
Copy link
Member

dgeb commented Jul 25, 2017

@ethanresnick - Thanks as always for engaging so thoughtfully!

In order to establish the boundaries of the responsibilities of profile extensions, I would find it helpful to step back and try to carve up the responsibilities of the "neighbors" to profile extensions (i.e. other extensions, schemas, profiles, home documents, etc. that help to define and clarify an API). Otherwise, we risk introducing an unintentionally "gerrymandered" concept prematurely (sorry for the tortured analogy).

To be clear, I don't want to take away from your hard work here and all the discussions that have taken place. I believe much of this work is useful even if its boundaries shift a little bit after discussion.

I would find it useful to start a new discussion (very soon!) with a goal of providing a 10K foot view of the potential ways to define and standardize API structure and capabilities (i.e. profile extensions and all its neighbors). If we can agree on the high order bits, we can empower the community to explore some of these areas, and we can come back to review your PR here with confidence.

@ethanresnick
Copy link
Member Author

In order to establish the boundaries of the responsibilities of profile extensions, I would find it helpful to step back and try to carve up the responsibilities of the "neighbors" to profile extensions (i.e. other extensions, schemas, profiles, home documents, etc. that help to define and clarify an API). Otherwise, we risk introducing an unintentionally "gerrymandered" concept prematurely (sorry for the tortured analogy).

Hey Dan! I also don't want us to ship something that's narrower than it needs to be and/or ultimately makes JSON:API's design unnecessarily fragmented. To avoid that, I'm open to having the high-level discussion you're describing, and will try to go into it not totally attached to the existing design.

That said, I think there's also huge value in just shipping something soon, as long as it gets us, say, 80% of the way there. Right now, there are a number of use cases that JSON:API isn't addressing, that us editors haven't had the bandwidth to address, and we're starting to see non-compliant implementations and (I'd imagine) people just abandoning JSON:API as a result. While the current profile extension design is intentionally limited, I do believe it would immediately unblock the community to start addressing many of these issues in ways that we can learn from, without being bound to adopt their solutions in the base spec. I don't want the perfect to be the enemy of the good here.

So, in the name of making sure we ship something, do you have ideas for how we can constrain this 10k foot conversation so it doesn't totally derail shipping something? I'd really like to figure out the parameters for such a conversation before we open what could be a giant can of worms.

@dgeb
Copy link
Member

dgeb commented Jul 25, 2017

@ethanresnick I agree that we've got to build some momentum. I'm willing to say that we should at the very least time box such a potentially sprawling open-ended conversation. I think we could hash some rough parameters out in a couple weeks. I'm willing to get the discussion started this week and try to build consensus by August 11.

I think it's critical that we proceed cautiously and deliberately, but that we do in fact proceed.

If there are other ways you'd like to constrain this conversation before we start, please let me know.

@ethanresnick
Copy link
Member Author

@dgeb Cool. Timeboxing this to August 11th sounds like a good solution to me. I think we should also define the scope and desired output a bit more, but I'll let you do that in your OP, since I want to make sure we're including all the things you think are important to include.

@dgeb
Copy link
Member

dgeb commented Aug 3, 2017

@ethanresnick Please see #1207 for my attempt at a high-level discussion about extending and describing the spec. Sorry that it took me longer than expected.

Copy link
Contributor

@gabesullice gabesullice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! @ethanresnick great job! I'm incredibly excited for something like this to take shape.

We've spent the last 3 years developing a JSON API module for Drupal which will soon be included in Drupal itself and will enable every Drupal installation to provide its content using JSON API out of the box.

There are a number of things that we've come up against that this proposal will immediately solve for us.

Namely, communicating access/allowed operations and better error handling.

I'm really excited to see where this goes!

The one thing which I think it left a little too unspecified is how profiles can interact with query parameters. I've seen your comment here: #1207 (comment)

But I'm afraid the URL templating won't be sufficient to express very rich use cases. Specifically, our filter expressions could not be templated.

I wonder if we might be able to add a mechanism to support richer uses of the reserved query parameters in a more structured way.

defines a `profile` parameter. If present, the value of this parameter, **MUST**
be a space-separated (U+0020 SPACE, " ") list of profile extension URIs. This
list **MUST** be surrounded by quotation marks (U+0022 QUOTATION MARK, "\""), in
accordance with the HTTP specification.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an example would be useful here.

The top level of a JSON API document **MAY** include an `aliases` member.
If present, the value of this member **MUST** be an object (an "aliases object").

Each key–value pair in the aliases object is an "alias definition". The key,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is missing where in the document aliases should be actively "dereferenced". Do alias only apply to JSON keys or do they apply to values as well? Should the be dereferenced only within a certain member like jsonapi or meta, or could they also be dereferenced for keys under data.attributes? Perhaps it's globally?

I think that no matter the answer, it would be valuable to specify it :)

The recipient of a document **MUST** ignore any profile extensions in that
document that it does not understand.

Recipients **MAY** process in parallel the data from different profile extensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this permitting/restricting?

When one or more profile extensions are used in a JSON API document, the document
**MUST** define at least one [alias][aliases] for each extension's URI.

An alias for an extension's URI **MAY** be used as a key (an "extension-associated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is where aliases apply. Maybe the "where" part of this could be moved to the aliases section while the "how" past of how aliases should be interpreted in the context of an extension could be left here.

extensions:

```http
Content-Type: application/vnd.api+json; profile="http://jsonapi.org/ext/example-1/ http://jsonapi.org/ext/example-2/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! The URI is really powerful. Perhaps there should be an HTTP(S) caveat here?

there **MUST** be an [error object][error objects] for each required extension
that is missing, and this error object **MUST**:

- have a [`type`](#error-objects) who's URI is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have a `type` key with the value `http://jsonapi.org/errors/missing-profile-extension`.

places they can occur.

These values **MAY** be defined for use in any spec-defined [meta object][meta],
with the exception is that an extension **MUST NOT** define values for use in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/is that/that

The keys in any profile-extension-defined objects **MUST** meet this specification's
requirements for [member names].

> If you author an extension, you are **strongly encouraged to [register](/extensions/#extension-creation-registration)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension authors **SHOULD** register their extension with the JSON API extension registry if the extension is intended for use in a public-facing implementation. This is intended to reduce duplication of efforts and reduce extension collisions.

"templates": [{
"name": "publish",
"method": "PATCH",
"uri": "/articles/1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is not a URI, just a path

* `links`: a [links object][links] containing the following members:
* `about`: a [link][links] that leads to further details about this
particular occurrence of the problem.
* `status`: the HTTP status code applicable to this problem, expressed as a
string value.
* `code`: an application-specific error code, expressed as a string value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually 100% with you that code is less useful than type (we've had our struggles), but can this be removed during the 1.0 lifecycle?

Edit: after reading below, I see the answer to this. However, perhaps this should be kept here with an additional caveat like: "this member is no longer recommended for use, however some implementations may continue to send this key. Implementors SHOULD use the type key for new APIs.

@gabesullice
Copy link
Contributor

Just wanted to chime back in and say that I've since read your further comments in #1207 (comment) and am less convinced link templates could not handle our filters. Good explanations!

However, I'm still ever so slightly on the side of @dgeb that I would like profiles to have greater power to define the usage of reserved query parameters without adopting link templates.

@dgeb dgeb mentioned this pull request Apr 15, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy