-
Notifications
You must be signed in to change notification settings - Fork 890
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
Conversation
- 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.
_format/1.1/index.md
Outdated
## <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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capabilities*
_format/1.1/index.md
Outdated
"templates": [{ | ||
"name": "publish", | ||
"method": "PATCH", | ||
"uri": "/articles/1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing comma here
There was a problem hiding this 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 !
Thanks for the review @jmeas, and the encouraging comments. Typos fixed! |
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:
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:
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? |
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:
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...
I agree that extension being able to give meaning to an attribute would be nice. For example, a resource object like:
is obviously nicer JSON than
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 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.
As far as extensions applying to |
@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. |
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. |
@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. |
@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. |
@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. |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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/" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
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. |
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:
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”.
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.
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.
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).
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:
I removed the requirement that an array of
profile
links be added in thelinks
object to indicate the extensions in use. The extensions in use are still conveyed in thealiases
object in the document body, and in the Content-Type header.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.)I've decided to keep the
aliases
key at the top-level, rather than nest it underjsonapi
, because it includes data that applies to the current document, not necessarily to the server as a whole (which is more what thejsonapi
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.