Skip to content

Profile extensions #957

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 23 commits into from
Closed

Profile extensions #957

wants to merge 23 commits into from

Conversation

ethanresnick
Copy link
Member

Finally, draft text for a profile extension system! Take a look :)

A few notes/points for discussion, continuing threads from #915:

  • The mappings object. In this PR, each extension is assigned an alias in a separate "mappings" object, rather than through an alias key (or similar) in its link object. While the latter design would be simpler right now, the argument in favor of the "mappings" approach is that it would allow us to define other string to URI-or-link-relation mappings in the same object/with a unified system in the future (e.g. to support extended relations/CURIEs in link objects; see the ideas in Extensions next steps #915 (comment)). I'm not sure how important that is, though it might be reasonably important if we end up supporting actions through extended relations or generally mapping better to RFC 5988. Also in favor of mappings: I'm not a huge fan of there being a link object key (alias) that's only allowed in profile links... the links object already seems to have more than enough cases/formats imo.

  • Ignore the naming scheme. As discussed in detail previously, we need some way to carve out a space of keys for use as extension aliases, so that these aliases won't conflict with future base spec keys we want to add. There are a ton of options for how to do this and, for this PR, I specified "must contain a non-[a-z] character" as the pattern for discriminating between static keys and extension aliases. Think of this rule just as a placeholder, though. I know that it's not perfect, as @bintoro said in Extensions next steps #915, and it's not even my favorite approach. Its primary downside, I think, is that it makes multi-word key names impossible (unless the words have no separator), and that seems like too big a price to pay. But I've specified it in this PR anyway because it was easy/consistent (its already what we're using for query parameters), and because I want to talk with the other editors first before bothering to spec out another approach, since a lot of this naming stuff is subjective/aesthetic. So we'll talk it out at our next meeting.

  • The error object changes. See the commit messages about adding the type link and removing the code member; they provide some context on why this is needed and how it's acceptable from a compatibility POV. There are other options here, but the ones I've thought of aren't as good as this approach. Re the missing member: I went with a string in the extension, parameter, and key members, supplemented by an alternatives member, when clearly the same functionality could've been achieved by dropping alternatives and simply allowing extension, parameter, and key to take an array. I.e., I could have done:

    {
      "errors": [{ 
        //...
        "missing": { 
          "extension": ["http://jsonapi.org/ext1", "http://jsonapi.org/ext2", "http://jsonapi.org/ext3"]
        }
      }
    }

    I didn't do that, though, because I thought people would think that all the extensions in that array were missing, when the meaning is really that (at least) one of those extensions must be provided. Maybe there are some key names that make this unambiguous, and allow us to get rid of the extra alternatives key? (All the unambiguous key names I thought of required multiple words.)

  • De-emphasizing meta. Once we have profile extensions in the spec, does the role of meta change? I think it should, but I'm curious about y'alls take on exactly how.

  • Extension registration. Long-term I'm pretty sure we want some sort of web app to allow people to browse extensions and submit a registration through a nice form. But the workflow described on the extensions page here was just meant to be the MVP: fill out a template and post it on discuss/Github.

This 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.

This is the start of what I suggested in #683.
This doesn’t go into the details of how to use the profile parameter
(and handle errors that result from sending it to 1.0 servers)—those
details will be given in the extension section. But it updates the
basic rules that all clients + servers must follow on all requests.
This commit does two things:

1. It mentions, in the first sentence, that the restrictions apply to
query parameter _names_ (not their values, or name–value pairs).

2. It splits the text into two paragraphs (the requirements and the
recommendations) which makes it easier to read, since both those chunks
have a lot of bold all caps text.
This key will be used, for now, to assign a simple/pretty name to each
used extension (identified by its longer URI).

The key is called “mappings”, rather than “aliases” because, in the
future, I suspect that not every key in this object will be an alias
for (i.e. directly substitutable with) its value. Instead, I can
imagine this object also holding, e.g., a mapping between relationship
names and URIs that can be used as an extended link relation to capture
each relationship.
This commit removes the `code` member, which is better served by the
new `type` link. Type allows a superset of code's capabilities. 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 in the next
commit) 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 note 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.
Clarifies a number of references and fixes a broken link in the errors
section.
Make the `code` inherit its weight by default, so it can be used inline
with bold text, but set its weight to 600  in particular when its
within an h5 (since h5’s weight of 500 happens to be normal weight
still for common monospace fonts, which only have two weights).

Remove the `.highlight pre` font-family declaration, since it was never
getting applied (the `.highlight pre` always contains a `code`, and
that’s the font that was being used).
Note: the new MUST isn’t actually an incompatible change; it was
already implicit in 1.0, because a JSON Pointer that doesn’t point to a
value is an invalid pointer according to the RFC. I’m just calling this
case out more clearly to prevent confusion, as arose when we were
generating the error object examples for the examples page.
@ethanresnick ethanresnick modified the milestone: JSON-API 1.1-beta Dec 29, 2015
Each member of a links object is a "link". A link **MUST** be represented as
either:
<a href="#document-links-link" id="document-links-link"></a>
Within this object, a link **MUST** be represented as either:
Copy link
Contributor

Choose a reason for hiding this comment

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

The prior language equated each member with a single link. The removal of the phrase creates the same issue I'm trying to resolve for relationships in #946. Another similarity to #946 is that the names of the links are not mentioned at all.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree that there's an analogy between the text here and #946.

I faced a couple problems writing this section:

  1. With the profile key now holding multiple links, we ca no longer say "each value is a link".
  2. Because we haven't yet defined the relationship between links and RFC 5988, I didn't want to introduces a term for the key (i.e. I didn't want to say whether it's the links name or a relation).

So, given the above constraints, the wording in the PR was what I came up with.

Let's get this PR merged, and then we can figure out how to clarify things in #958. I'll link this thread there for reference.

@bintoro
Copy link
Contributor

bintoro commented Dec 29, 2015

Looking pretty good!

The one thing I'm uncomfortable with is the idiosyncratic usage of the term "mapping".

Usually a mapping (or a map) means a system that maps each element in a source set to an element in a target set. In JSON, an object would be a mapping. In mathematics, a function would be a mapping.

Thus, the object that is now named mappings is really just a single mapping. But rather than calling it mapping (which would be a bit like naming the profile array array), perhaps a more descriptive name like aliases would be in order.

A key inside mappings is already called an "alias", which makes sense and would seem to support naming the container aliases as well.

A key–value pair inside mappings is currently called a "mapping", but this entity doesn't seem to require a name of its own. The definition is there, but it's never used apart from a single non-normative note. If there's a need to call it something, call it an "alias definition".

@cziegenberg
Copy link
Contributor

Great job Ethan. It's a good start to integrate extensions. I'm a bit busy at the moment, but I want to give you a short feedback.

Some notes:

  • The mixed used of mapping and alias could be a bit confusing, so I agree to @bintoro.
  • Also you use the terms "profile extension", "profile" (in links) and "extension" (in error objects). Perhaps this should be clarified and mentioned, that other types of extensions can be defined later.
  • Perhaps it should be noted, that the "non a-z" rule still means that the rules for member names must still be met. Also it wasn't clear for me that this was meant case-sensitive when I read it the first time. (I know this is not final...)

Regarding the missing extensions error:
I previously suggested to return a group (array) of extensions, if one extension of the group is required. This way you avoid the additional member:

{
 "errors": [{
  "missing": {
   "extension": [
    "http://jsonapi.org/ext1",
    ["http://jsonapi.org/ext2", "http://jsonapi.org/ext3"]
   ]
  }
 }
}

@bintoro
Copy link
Contributor

bintoro commented Dec 31, 2015

A lot of the discussion over at #915 revolved around the naming system, which should, ideally, have the following properties:

  • Profile aliases should look like first-class members (no prefixing).
  • It should be possible to interleave data from two extensions without causing key collisions.

One obvious approach that I don't think was ever brought up is to prefix a global name whenever it appears inside an extension object:

{
  "aliases": {
    "attribute-validation": "http://jsonapi.org/ext/attribute-validation",
    "common-validators": "http://jsonapi.org/ext/validators",
  },
  "data": {
    "type": "articles", 
    "id": "1",
    "attributes": {"title": "Lorem ipsum"}
  },
  "attribute-validation": { // Appears in global scope -> no prefix
    "articles": {
      "title": {
        "$common-validators": {"string": true} // Nested alias -> prefix "$"
      }
    }
  }
}

This wouldn't treat extension aliases any differently from officially defined members because those, too, would have to be designated somehow if they were to appear within extension data.

@ethanresnick
Copy link
Member Author

Other responses...

Thus, the object that is now named mappings is really just a single mapping. But rather than calling it mapping (which would be a bit like naming the profile array array), perhaps a more descriptive name like aliases would be in order.

First, a heads up that I'm still not sure whether mappings should exist at all—that depends on how far JSON API wants to go down the path of supporting hypermedia. I.e. if we want extended rels and CURIEs, then mappings makes sense, but what if we don't? How much hypermedia we should support natively, vs. how much we should just hand off to JSON-LD (for devs who want to opt into that) is something I've been thinking about a lot lately. It's a bigger question, and I'll open an issue about it soon.

So, since mappings might go altogether, I don't think it makes sense to spend time debating the name now. For context, though... I had named it aliases before, but changed it to mappings because I was imagining some types of key–value mappings (for hypermedia purposes) that wouldn't really make sense to call "aliases". Per the above, though, we may decide that those kind of mappings aren't within the scope of JSON API, so let's figure that out first.

Perhaps this should be clarified and mentioned, that other types of extensions can be defined later.

@ziege Yes, I think that's fair. I'll update the PR accordingly.

Perhaps it should be noted, that the "non a-z" rule still means that the rules for member names must still be met. Also it wasn't clear for me that this was meant case-sensitive when I read it the first time. (I know this is not final...)

Good points. Let's revisit this if we end up sticking with the non a-z rule.

One obvious approach that I don't think was ever brought up is to prefix a global name whenever it appears inside an extension object:

Thanks! I'll add this to the list of possible approaches :) Hopefully I'll have a chance to talk with the other editors soon and we can figure out the priorities (functional and aesthetic) & narrow down the naming options.

I previously suggested to return a group (array) of extensions, if one extension of the group is required.

Right. In my proposal, though, each missing extension get's its own error (which is important because the extensions could be missing at different pointers). So there'd never be a value like:

"extension": ["http://jsonapi.org/ext1", ["http://jsonapi.org/ext2", "http://jsonapi.org/ext3"]]

Instead, the missing ext1 would have its own error object, and the other value would be:

"extension": ["http://jsonapi.org/ext2", "http://jsonapi.org/ext3"]

And that's where I think it's unclear to readers... if there is only one missing extension (i.e. if ext1 isn't missing/necessary, so there's just a choice between adding ext2 or ext3), and the above value is all the user sees, it's a little unclear whether both ext2 and ext3 are missing, or just one of them. That's what I was hoping we could find a better key name/structure for. Not a huge deal, but something to think about...

@bintoro
Copy link
Contributor

bintoro commented Jan 4, 2016

Quoting myself here...

Profile aliases should look like first-class members (no prefixing).

I'm not so sure about this requirement anymore.

Its origins were in the idea that very common extensions might eventually become kind of de facto standards and, hence, should also look like standard members.

However, at the same time, there's the "once an extension, always an extension" principle that specifically aims to keep (even universally adopted) extensions distinct from base spec additions.

These two ideas seem a bit contradictory. If maintaining an extension-introduced feature is permanently delegated to that extension, is there anything wrong with also permanently denoting the corresponding JSON member as such? Maybe not.

The aversion to "X-prefixing" that resulted in RFC 6648 is about cases where authors unilaterally introduce experimental names that later may become standardized. Neither applies to the situation here, as long as we have a registration requirement and a commitment not to absorb extensions into the base spec.

Given that a global prefixing scheme would immediately resolve the namespace-partitioning predicament, the option shouldn't be dismissed out of hand — except to leave the door open for assimilating extensions as true first-class members of the spec.

Also, I'm thinking it would probably be a good idea to preserve some form of the registered key scheme originally envisaged. Something like:

  • A suggested/default key would be a required part of the registration info.
  • APIs SHOULD use the suggested key.

Strongly encouraging all APIs to use the same key for the same extension would benefit everyone with no real downsides, so we should absolutely take the opportunity to do so. This is especially crucial in the absence of a global prefixing system. It seems pointless, even counterproductive, to make extension keys look like first-class members if their meaning can vary from server to server.

@ethanresnick
Copy link
Member Author

ethanresnick commented Feb 17, 2016

The core team talked about this PR at today's meeting, so I wanted to give y'all some updates and a chance to weigh in on the new ideas. (These aren't yet reflected in the PRs text, which I'll update once there's consensus on the details.)

  1. Naming scheme. Rather than partitioning the key space to prevent naming conflicts, we're currently leaning toward putting extension keys inside meta, where they can be named whatever the user likes without preventing future base spec additions. This is analogous to the approach we took with the attributes and relationships: basically, creating new container objects to avoid prefixing and other schemes. So, an extension (with a nested extension) would look like this:

    {
      "mappings": {
        "attribute-validation": "http://jsonapi.org/ext/attribute-validation",
        "validators": "http://jsonapi.org/ext/validators",
      },
      "data": {
        "type": "articles", 
        "id": "1",
        "meta": {
          "attribute-validation": {
             "title": {
               "meta": {
                 "validators": {"string": true}
               }
             }
          }
        },
        "attributes": {"title": "Lorem ipsum"}
      }
    }

    Is it more nesting? Undoubtedly. But it should only be one extra layer of nesting, since I expect extensions used inside of extensions to be a relatively rare case. And it's more consistent with the rest of the spec.

    Ideally, I wish the key were named something more descriptive than "meta"... but meta is something we already have, and we felt that adding a second key alongside it (e.g. an "extensions" key as @ziege proposed long ago) would unnecessarily complicate the spec and implementations.

    We also realizes that this mixes truly opaque custom data (i.e. that not associated with an extension URI) with extension-described data, but we see this as an advantage, as it allows existing APIs to associate the keys that they currently have in meta with an extension they can create now. Going forward, we'd hope that only extension-associated keys get put in meta, but the ability to add an opaque value there with no upfront ceremony (of figuring out, and perhaps defining, an extension) could be nice for prototyping or fast-moving teams.

    As a consequence of the change above, all the rules about non a-z names would go away.

  2. Registration. We're also leaning toward removing the normative text requiring extension registration, in part to allow companies to create extensions that they can use internally without registering publicly. However, because extension discoverability and reuse is hugely important, there would still be prominent non-normative notes encouraging registration, and (as in the current PR) we might grant extensions that register a jsonapi.org URI, as a small incentive.

  3. The mappings object. We're still trying to figure out the appropriate contours of JSON API hypermedia support, which will factor into the final name for mappings, as I mentioned. For now, though, we haven't had a chance to talk much about this.

  4. Open question: can mappings support relative URIs?

@dgeb
Copy link
Member

dgeb commented Feb 17, 2016

@ethanresnick Thanks for updating as per our discussion.

I think we decided to use a top-level extensions object instead of mappings (obviously still open for discussion).

We didn't discuss using extensions within extensions specifically, but I don't see the need to use meta within meta as you've done in your validators example.

I imagine that the canonical home page example with a couple extensions might look like this:

{
  "extensions": {
    "pagination": "http://jsonapi.org/extensions/pagination",
    "revision": "http://jsonapi.org/extensions/revisions"
  },
  "links": {
    "self": "http://example.com/articles",
    "next": "http://example.com/articles?page[offset]=2",
    "last": "http://example.com/articles?page[offset]=10"
  },
  "meta": {
    "pagination": {
      "offset": "1",
      "count": "10"
    }
  }
  "data": [{
    "type": "articles",
    "id": "1",
    "meta": {
      "revision": "123456"
    }, 
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "http://example.com/articles/1/relationships/author",
          "related": "http://example.com/articles/1/author"
        },
        "data": { "type": "people", "id": "9" }
      },
      "comments": {
        "links": {
          "self": "http://example.com/articles/1/relationships/comments",
          "related": "http://example.com/articles/1/comments"
        },
        "data": [
          { "type": "comments", "id": "5" },
          { "type": "comments", "id": "12" }
        ]
      }
    },
    "links": {
      "self": "http://example.com/articles/1"
    }
  }]
}

@ethanresnick
Copy link
Member Author

I think we decided to use a top-level extensions object instead of mappings (obviously still open for discussion).

Yeah, I remember you suggested that, I just wasn't sure whether we'd settled on it. My concern with calling the key extensions was that we may want to add other types of mappings later, and I'd rather not have to add yet more top-level keys to handle those. Tyler's (great) observation that we could express future mappings as extension data largely assuaged this concern, but it's still worth noting that the JSON is messier if we do it this way.

For instance, if we want to support CURIE-like strings as links object keys (to express extended link relations), we end up with:

{
  "extensions": {
    "pagination": "http://jsonapi.org/extensions/pagination",
    "revision": "http://jsonapi.org/extensions/revisions",
    "rel-prefixes": "http://jsonapi.org/extensions/extended-rels"
  },
  "meta": {
    "rel-prefixes": {
      "API": "http://mydomain.com/custom-rels/"
    }
  },
  "links": {
    "API/home": "http://mydomain.com/api/v2"
  }
  // ...
}

(Above, the rel-prefixes extension tells clients how to map links keys to full URIs.)

By contrast, if we called the extensions key aliases in 1.1 (the name I gave it before switching to the even more generic mappings), we'd end up with:

{
  "aliases": {
    "pagination": "http://jsonapi.org/extensions/pagination",
    "revision": "http://jsonapi.org/extensions/revisions",
    "API": "http://mydomain.com/custom-rels/"
  },
  "links": {
    "API/home": "http://mydomain.com/api/v2"
  }
  // ...
}

That's why I've seen the mappings naming decision as somewhat dependent on our hypermedia plans. Tyler's suggestion convinced me that the name mappings is too broad, but something like aliases (or bindings?) still seems sensible if we think we want to support extended relations.
 

We didn't discuss using extensions within extensions specifically, but I don't see the need to use meta within meta as you've done in your validators example.

Just to make sure we're talking about the same model for nesting extensions within extensions, check out this comment, if you haven't already, to see what I have in mind. Do you agree with that basic approach?

(Btw, here's the part of the PR that attempts to codify that model... it could maybe use some clarification.)

So, how would this work without the meta inside meta? I.e., in my example, how would the client know that validators is itself an extension-associated key, and not just a key defined by the attribute-validation extension?

@dgeb
Copy link
Member

dgeb commented Feb 18, 2016

Yeah, I remember you suggested that, I just wasn't sure whether we'd settled on it.

Fair enough. It does seem unsettled after all.

That's why I've seen the mappings naming decision as somewhat dependent on our hypermedia plans. Tyler's suggestion convinced me that the name mappings is too broad, but something like aliases (or bindings?) still seems sensible if we think we want to support extended relations.

We should consider using the top-level jsonapi object for declaring everything about an implementation, including extensions. This seems to fit its described purpose and would eliminate top-level noise.

Just to make sure we're talking about the same model for nesting extensions within extensions, check out this comment, if you haven't already, to see what I have in mind. Do you agree with that basic approach?

I think we're going down the rabbit hole of complexity here. An extension must be defined by the URL associated with it, and its value can be opaque to the base json:api spec. I'm not convinced that we need rules for extension composition in the base spec. If a particular extension wants to yield to other extensions at a particular key, then that's its prerogative. The extension can write the rules of what's allowed for any key under its control.

@tkellen
Copy link
Member

tkellen commented Feb 18, 2016

Big 👍 for using the jsonapi object for extending our hypermedia stuff further!

@ethanresnick
Copy link
Member Author

Using jsonapi to hold extension and/or other hypermedia info sounds fine to me, but it just seems to push the naming problem back a level, right? (I.e. now what do we call the key that holds the users mappings within the jsonapi bucket?)

Re how much control we need to exert over extension composition... I'm not sure... let's talk about it next week?

@tkellen
Copy link
Member

tkellen commented Feb 24, 2016

@ethanresnick can you pull out your @ prefix change to a separate PR so we can land it?

This will be used for a server to indicate which profile extension is
missing from a client’s document, in the event that a server requires
an extension be used (e.g. for authentication).

Note: if the markdown formatting looks weird…it is, but that’s the only
formatting that would put the “This object…” paragraph under the
`missing` bullet.
@remmeier
Copy link
Contributor

remmeier commented Aug 16, 2016

any updates on this and a general 1.1 roadmap? its is 11 months past the milestone in https://github.com/json-api/json-api/milestone/2.

@ethanresnick
Copy link
Member Author

Superseded by #1195 and #1196.

eq19 pushed a commit to WeakPositive/WeakPositive.github.io that referenced this pull request May 5, 2025
Add docs for at_least and at_most filters
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.

6 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