From d1a897419c7fea6fae94a0cb0eeaf0e5f3dba3d2 Mon Sep 17 00:00:00 2001 From: Ethan Resnick Date: Sun, 7 Oct 2018 13:56:31 -0400 Subject: [PATCH 1/3] fix(format): remove "extensions" reference from example url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we’re calling them ‘profiles’ now. --- _format/1.1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_format/1.1/index.md b/_format/1.1/index.md index bcb5268c3..814bbcf52 100644 --- a/_format/1.1/index.md +++ b/_format/1.1/index.md @@ -1893,10 +1893,10 @@ request is received, a server **SHOULD** attempt to apply the requested profiles to its response. For example, in the following request, the client asks that the server apply the -`http://jsonapi.org/extensions/last-modified` profile if it is able to. +`http://example.com/last-modified` profile if it is able to. ```http -Accept: application/vnd.api+json;profile="http://example.com/extensions/last-modified", application/vnd.api+json +Accept: application/vnd.api+json;profile="http://example.com/last-modified", application/vnd.api+json ``` > Note: The second instance of the JSON API media type in the example above is From 170b579c1d9b7c36547b0fed9ae7c582ce3db65f Mon Sep 17 00:00:00 2001 From: Ethan Resnick Date: Mon, 22 Oct 2018 17:23:52 -0400 Subject: [PATCH 2/3] feat(ux): add one extra level of outline headings This makes the sidebar table of contents more useful imo (for us and, soon, in user-defined profiles). --- _format/1.1/index.md | 8 ++++---- javascripts/all.js | 13 ++++++++----- stylesheets/all.css | 10 +++++----- stylesheets/all.sass | 6 +++--- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/_format/1.1/index.md b/_format/1.1/index.md index 9e1a3136b..b28eecd26 100644 --- a/_format/1.1/index.md +++ b/_format/1.1/index.md @@ -234,7 +234,7 @@ other and with `type` and `id`. In other words, a resource can not have an attribute and relationship with the same name, nor can it have an attribute or relationship named `type` or `id`. -#### Attributes +##### Attributes The value of the `attributes` key **MUST** be an object (an "attributes object"). Members of the attributes object ("attributes") represent information @@ -253,7 +253,7 @@ alongside other information to be represented in a resource object, these keys > Note: See [fields] and [member names] for more restrictions on this container. -#### Relationships +##### Relationships The value of the `relationships` key **MUST** be an object (a "relationships object"). Members of the relationships object ("relationships") represent @@ -285,7 +285,7 @@ data, not the related resources. > Note: See [fields] and [member names] for more restrictions on this container. -#### Related Resource Links +##### Related Resource Links A "related resource link" provides access to [resource objects][resource objects] [linked][links] in a [relationship][relationships]. When fetched, the related resource object(s) @@ -300,7 +300,7 @@ relationship isn't currently associated with any target resources. Additionally, a related resource link **MUST NOT** change because its relationship's content changes. -#### Resource Linkage +##### Resource Linkage Resource linkage in a [compound document] allows a client to link together all of the included [resource objects] without having to `GET` any URLs via [links]. diff --git a/javascripts/all.js b/javascripts/all.js index 4a4ca3277..44fe7f6a4 100644 --- a/javascripts/all.js +++ b/javascripts/all.js @@ -12,7 +12,6 @@ $(document).ready(function() { // Sidebar scroll affix fixElement($(".sidebar"), $("footer"), 52); - activateVersionPicker(); }); @@ -89,6 +88,14 @@ function createOutlineFromElement(element) { children: [] }; + $(this).nextUntil('h3', 'h4').each(function() { + childItem.children.push({ + title: $(this).not('a').text(), + href: $(this).find('a').attr('href') || "#", + children: [] + }); + }); + item.children.push(childItem); }); @@ -98,10 +105,6 @@ function createOutlineFromElement(element) { return outline; } -/** - * Creates a nested list from an array in the form returned by `createOutlineFromElement`. - */ - /** * Creates a nested list from an array in the form returned by `createOutlineFromElement`. * diff --git a/stylesheets/all.css b/stylesheets/all.css index 833eb002d..de0017289 100644 --- a/stylesheets/all.css +++ b/stylesheets/all.css @@ -304,21 +304,21 @@ footer { margin-bottom: 50px; } .sidebar { - width: 220px; + width: 235px; margin-top: 8px; float: left; } .sidebar h1 { font-size: 15px; background-color: white; z-index: 1; - width: 220px; + width: 235px; color: inherit; font-weight: 300; } .sidebar nav > ol { - width: 220px; + width: 235px; overflow-y: auto; } .sidebar #version-picker-wrapper { - width: 220px; + width: 200px; margin-bottom: 20px; display: flex; flex-direction: row; } @@ -343,7 +343,7 @@ footer { @media screen and (min-width: 886px) { .sidebar + .content { - margin-left: 235px; + margin-left: 245px; padding-left: calc(100vw - 886px); } } @media screen and (min-width: 901px) { .sidebar + .content { diff --git a/stylesheets/all.sass b/stylesheets/all.sass index 1007217c0..54a46a2f5 100644 --- a/stylesheets/all.sass +++ b/stylesheets/all.sass @@ -6,7 +6,7 @@ /** Config */ $page-width: 955px -$sidebar-width: 220px +$sidebar-width: 235px $page-padding: 20px $mobile-breakpoint: 800px @@ -307,7 +307,7 @@ footer overflow-y: auto #version-picker-wrapper - width: $sidebar-width + width: 200px margin-bottom: 20px display: flex flex-direction: row @@ -347,7 +347,7 @@ footer // just jump to its final size at the next media query. @media screen and (min-width: $page-width + ($page-padding * 2) - 109px) .sidebar + .content - margin-left: $sidebar-width + 15px + margin-left: $sidebar-width + 10px padding-left: calc(100vw - #{$page-width + ($page-padding * 2) - 109px}) @media screen and (min-width: $page-width + ($page-padding * 2) - 94px) From 6f620fa14a78ad2303936db1aee5268568f3aac8 Mon Sep 17 00:00:00 2001 From: Ethan Resnick Date: Mon, 22 Oct 2018 17:29:59 -0400 Subject: [PATCH 3/3] docs: add profile registration template, site pages, example profile --- _config.yml | 32 +- _format/1.1/index.md | 5 + _includes/global_head_assets.html | 32 + _includes/global_html_footer.html | 23 + _includes/header_offset_2.md | 33 + _includes/profile_url.md | 5 + _includes/site_navigation.html | 13 + _layouts/page.html | 83 +-- _layouts/profile.html | 71 ++ _layouts/profile_error.html | 28 + .../ethanresnick/cursor-pagination/index.md | 610 ++++++++++++++++++ .../cursor-pagination/max-size-exceeded.md | 3 + .../range-pagination-not-supported.md | 3 + .../cursor-pagination/unsupported-sort.md | 3 + extensions/index.md | 114 +++- index.md | 7 +- javascripts/all.js | 11 + profile_template | 54 ++ stylesheets/all.css | 42 +- stylesheets/all.css.map | 2 +- stylesheets/all.sass | 51 +- 21 files changed, 1127 insertions(+), 98 deletions(-) create mode 100644 _includes/global_head_assets.html create mode 100644 _includes/global_html_footer.html create mode 100644 _includes/header_offset_2.md create mode 100644 _includes/profile_url.md create mode 100644 _includes/site_navigation.html create mode 100644 _layouts/profile.html create mode 100644 _layouts/profile_error.html create mode 100644 _profiles/ethanresnick/cursor-pagination/index.md create mode 100644 _profiles/ethanresnick/cursor-pagination/max-size-exceeded.md create mode 100644 _profiles/ethanresnick/cursor-pagination/range-pagination-not-supported.md create mode 100644 _profiles/ethanresnick/cursor-pagination/unsupported-sort.md create mode 100644 profile_template diff --git a/_config.yml b/_config.yml index 795ecc305..771a57edc 100644 --- a/_config.yml +++ b/_config.yml @@ -13,11 +13,11 @@ whitelist: - jekyll-sitemap - jekyll-feed - jekyll-seo-tag - - jekyll-gist - jemoji incremental: false gist: noscript: false + # customize github pages quiet: false markdown: kramdown @@ -25,7 +25,9 @@ highlighter: rouge kramdown: input: GFM hard_wrap: false - auto_ids: false + auto_ids: true + auto_id_prefix: auto-id- + auto_id_stripping: true template: '' # cannot customize math_engine: mathjax # cannot customize syntax_highligher: rouge # cannot customize @@ -48,6 +50,9 @@ port: 9876 collections: format: output: true + profiles: + output: true + permalink: /:collection/:path defaults: - scope: @@ -57,6 +62,20 @@ defaults: layout: page show_sidebar: true is_spec_page: true + - scope: + path: "" + type: "profiles" + values: + layout: profile_error + is_spec_page: false + show_sidebar: false + - scope: + path: "*/*/*/index.md" + type: "profiles" + values: + layout: profile + is_spec_page: false + show_sidebar: true latest_version: 1.0 excerpt_separator: "" @@ -84,3 +103,12 @@ quicklinks: url: /format/ - title: Contribute on GitHub url: https://github.com/json-api/json-api + +profile_categories: + - Pagination + # these are some other potential categories. + # Uncomment them if you're adding a profile in one of these categories. + # - Filtering + # - Actions/Hypermedia + # - Data Modeling + # - Deep Querying diff --git a/_format/1.1/index.md b/_format/1.1/index.md index b28eecd26..f5c10b9c7 100644 --- a/_format/1.1/index.md +++ b/_format/1.1/index.md @@ -2222,6 +2222,11 @@ supported as well. 3. alter the JSON structure of any concept defined in this specification, including to allow a superset of JSON structures. + +> If you create your own profile, you are **strongly encouraged to [register](/extensions/#profile-registration) +> it** with the JSON API [profile registry](/extensions/), so that others can +> find and reuse it. + #### Revising a Profile Profiles **MAY** be revised over time, e.g., to add new capabilities. However, diff --git a/_includes/global_head_assets.html b/_includes/global_head_assets.html new file mode 100644 index 000000000..22551095d --- /dev/null +++ b/_includes/global_head_assets.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_includes/global_html_footer.html b/_includes/global_html_footer.html new file mode 100644 index 000000000..78938024e --- /dev/null +++ b/_includes/global_html_footer.html @@ -0,0 +1,23 @@ + + diff --git a/_includes/header_offset_2.md b/_includes/header_offset_2.md new file mode 100644 index 000000000..221a039f5 --- /dev/null +++ b/_includes/header_offset_2.md @@ -0,0 +1,33 @@ +{% comment %} + When we embed the markdown from a user-provided profile specification + inside the profile layout, we need to increment each heading level by 2. + Kramdown, our markdown parser, provides a `header_offset` option, but + Jekyll only allows us to set that globally -- and setting it globally + to two would break our other pages. So, this include lets us take arbitrary + HTML (from rendered markdown) and does some liquid string replacements + to offset its headings. This is pretty janky (even more so because liquid + only allows us to do literal string replacement, not regex replacement), + but I think it *should* work robustly, thanks to the fact that angle + brackets (i.e., `<` and `>`) aren't supposed to appear in HTML unencoded, + and I imagine Kramdown respects that. +{% endcomment %} +{{ include.content + | replace: "", "" + | replace: "", "" + | replace: "", "" + | replace: "", "" + | replace: "", "" + | replace: "", "" + | replace: "", "" + | replace: "", "" + | replace: "", "" +}} diff --git a/_includes/profile_url.md b/_includes/profile_url.md new file mode 100644 index 000000000..7c66cde28 --- /dev/null +++ b/_includes/profile_url.md @@ -0,0 +1,5 @@ +{% comment %} + Takes the page object for a JSON:API profile spec and returns its url. + This file can't have whitespace outside this comment block or else the + output will be corrupted. +{% endcomment %}{{ include.page.url | absolute_url | split: "/" | where_exp: "item", "item != 'index'" | join: "/" }}/ \ No newline at end of file diff --git a/_includes/site_navigation.html b/_includes/site_navigation.html new file mode 100644 index 000000000..6ef5c513b --- /dev/null +++ b/_includes/site_navigation.html @@ -0,0 +1,13 @@ + diff --git a/_layouts/page.html b/_layouts/page.html index 063c5503c..b70fbb076 100644 --- a/_layouts/page.html +++ b/_layouts/page.html @@ -1,9 +1,7 @@ - - - + {% include global_head_assets.html %} {% comment %} Below, we're either dealing with a generic page or a page from the @@ -20,57 +18,15 @@ {% else %} {% assign page_title = page.title %} {% endif %} - Codestin Search App - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Codestin Search App - + {% include site_navigation.html %} {% if page.show_masthead %}
-

JSON API

+

JSON API

A specification for building APIs in JSON

{% endif %} -
+
{% unless page.show_masthead %}

{{ page_title }} @@ -136,31 +93,9 @@

{% endif %} {{ content }} -

+
- - + {% include global_html_footer.html %} diff --git a/_layouts/profile.html b/_layouts/profile.html new file mode 100644 index 000000000..8ab61bb8a --- /dev/null +++ b/_layouts/profile.html @@ -0,0 +1,71 @@ + + + + {% include global_head_assets.html %} + {% capture page_title %}“{{ page.name | smartify }}” Profile{% endcapture %} + Codestin Search App + + + + {% include site_navigation.html %} +
+ +
+

+ {{ page_title }} +

+
+

Introduction

+

This is the specification of a profile for the JSON:API specification.

+

The url for this profile is {% include profile_url.md page=page %}.

+ + {% capture extended_description_markdown %} + {{ page.extended_description | markdownify }} + {% endcapture %} + {% include header_offset_2.md content=extended_description_markdown %} +
+ {% if page.minimum_jsonapi_version > 1.0 %} +
+

+ + Minimum JSON:API Version +

+ +

This profile requires at least JSON:API version {{ page.minimum_jsonapi_version }}.

+ {% capture minimum_version_markdown %} + {{ page.minimum_jsonapi_version_explanation | markdownify }} + {% endcapture %} + {% include header_offset_2.md content=minimum_version_markdown %} +
+ {% endif %} +
+

Specification

+ {% include header_offset_2.md content=content %} +
+
+

Contact the Author

+ {% if page.discussion_url %} +

To discuss or ask questions about this extension, visit + {{ page.discussion_url }}.

+

You can also contact the author directly:

+ {% endif %} +
+ {{ page.author_name }}
+ {{ page.author_email }}
+ {% if page.author_website %} + {{ page.author_website }}
+ {% endif %} + {% if page.author_phone %} + {{ page.author_phone }}
+ {% endif %} +
+
+
+
+ {% include global_html_footer.html %} + + diff --git a/_layouts/profile_error.html b/_layouts/profile_error.html new file mode 100644 index 000000000..cbe0ea42d --- /dev/null +++ b/_layouts/profile_error.html @@ -0,0 +1,28 @@ + + + + {% include global_head_assets.html %} + Codestin Search App + + + + {% include site_navigation.html %} +
+ {% if page.show_sidebar %} + + {% endif %} +
+

{{ page.title }}

+
+ {{ content }} +
+
+
+ {% include global_html_footer.html %} + + + diff --git a/_profiles/ethanresnick/cursor-pagination/index.md b/_profiles/ethanresnick/cursor-pagination/index.md new file mode 100644 index 000000000..e11aa68bb --- /dev/null +++ b/_profiles/ethanresnick/cursor-pagination/index.md @@ -0,0 +1,610 @@ +--- +name: Cursor Pagination +short_description: | + Enables pagination forward and backwards from a client-provided cursor, with + a client-controlled page size. + +extended_description: | + Cursor-based pagination (aka keyset pagination) is a [common](https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12) + [pagination strategy](https://www.citusdata.com/blog/2016/03/30/five-ways-to-paginate/) + that avoids many of the pitfalls of "offset–limit" pagination. + + For example, with offset–limit pagination, if an item from a prior page is + deleted while the client is paginating, all subsequent results will be shifted + forward by one. Therefore, when the client requests the next page, there's one + result that it will skip over and never see. Conversely, if a result is added + to the list of results as the client is paginating, the client may see the + same result multiple times, on different pages. Cursor-based pagination + can prevent both of these possibilities. + + Cursor-based pagination also performs better for large data sets under most + implementations. + + To support cursor-based pagination, this specification defines three query + parameters — `page[size]`, `page[before]`, and `page[after]` —  + and a method for providing clients with pagination links and cursors in response + bodies. + + For example, this request would get the next 100 people after the cursor `abcde`: + + ``` + GET /people?page[size]=100&page[after]=abcde + ``` + + Replacing `page[after]` with `page[before]` would allow the client to paginate + backwards. + + Alternatively, to find all people between cursors `abcde` and `fghij` + (exclusive), the client could request: + + ``` + GET /people?page[after]=abcde&page[before]=fghij + ``` + + Other combinations are possible, and these parameters are described more below. + +# If your profile defines values that can only be used in document members that +# were introduced after JSON:API v1.0, change the minimum_jsonapi_version field +# and fill in the minimum_jsonapi_version_explanation field with an explanation +# of the features you're relying on from the JSON:API version you've indicated. +minimum_jsonapi_version: 1.0 +minimum_jsonapi_version_explanation: + +# Url of a Github repo or some other place where people can +# ask questions/start discussions about your extension. +discussion_url: http://tets.com + +author_name: Ethan Resnick +author_email: ethan.resnick@gmail.com +# Optional fields +author_website: https://ethanresnick.com/ +author_phone: +13104398032 # use `tel` url format. + +# Valid categories are listed under the profile_categories section +# in https://github.com/json-api/json-api/blob/gh-pages/_config.yml +categories: + - Pagination +--- +# Concepts + +## Sorting Requirement + +**Pagination only applies to an ordered list of results.** This order must not +change between requests unless the underlying data changes, to ensure that +results don't arbitrarily move between pages. + +If the client's paginated request includes a `?sort` query parameter that +only partially orders the results, the server **MUST** apply additional +sorting constraints — consistent with the client-requested ones — to produce +a unique ordering, if it wishes to support pagination of that data. + +For example, suppose a client requests `GET /people?sort=age&page[size]=10`. +If multiple people have the same age, their relative ordering is not defined +(and could vary between requests), making pagination impossible. Therefore, +to fulfill the request, the server must treat all paginated requests with +`?sort=age` as though the client had instead asked to sort by age followed +by some unique field or combination of fields (e.g., `?sort=age,id`). + +Similarly, when the collection being paginated has no natural or +client-requested order (like the set of resource identifier objects in +a relationship), the server **MUST** assign an order if it wishes to support +pagination. + +The server **MAY** reject pagination requests if the client has requested that +the results be sorted in a way that server cannot efficiently paginate. In that +case, the server **MUST** reject the request according to the rules for the +[unsupported sort error]. + +## Cursors +A "cursor" is a string, created by the server using whatever method it likes, +that divides the list of results into those that fall before the cursor, those +that fall after the cursor, and, optionally, one result that falls "on" the +cursor. + +For example, imagine that the list of results being paginated is as follows: + +``` +[ + { "type": "examples", "id": "1" }, + { "type": "examples", "id": "5" }, + { "type": "examples", "id": "7" }, + { "type": "examples", "id": "8" }, + { "type": "examples", "id": "9" } +] +``` + +For this list, the server might produce the cursor string `abcde` as its way of +encoding "id = 5". With that cursor, then, the first result would fall before +the cursor, the second result would fall on the cursor, and the other results +would fall after it. + +The list of results **MAY** change between the client's pagination requests. +For example, the result with `"id": "5"` might be removed from the result set if +the resource it refers to is deleted. In that case, the cursor `abcde` would no +longer fall on any one result, but the same results would still come before it +and after it. + +In rare cases, a server may find it unacceptable for the results list to change +between a client's pagination requests. In those cases, the server **MAY** +encode into the cursor information uniquely identifying the client or its session, +and use that identifier to return consistent results from a single "snapshot" in +time. + +# Query Parameters + +## `page[size]` + +The ```page[size]``` parameter indicates the number of results that the client +would like to see in the response. + +If `page[size]` is provided, it **MUST** be a positive integer.[1](#fn-1) +If this requirement isn't met — e.g. if `page[size]` is negative — the server +**MUST** respond according to the rules for the [invalid query parameter error]. + +For each endpoint on which it supports pagintation, a server **MAY** define a +maximum number of results that it will send in response to a paginated request +to that endpoint. This is called the "max page size". If the server does not +choose a max page size for a given endpoint, it is implicitly infinity. + +If `page[size]` exceeds the server-defined max page size, the server **MUST** +respond according to the rules for the [max page size exceeded error]. + +If `page[size]` is omitted, the server **MUST** choose a "default page size". +This default size **MUST** be an integer between 1 and the max page size, +inclusive. + +The `page[size]` value, or the default page +size if `page[size]` is omitted, is called the "used page size". + +On any valid paginated request, the number of [pagination items][pagination item] +returned **MUST** equal the used page size — provided there are at +least that many items in the results list, and which satisfy the constraints of +the `page[after]` and/or `page[before]` parameters (if any). + +## `page[after]` and `page[before]` + +The `page[after]` and `page[before]` parameters are both optional and both, if +provided, take a cursor as their value. If their value is not a valid cursor, +the server **MUST** respond according to the rules for the +[invalid query parameter error]. + +The `page[after]` parameter is typically sent by the client to get the next page, +while `page[before]` is used to get the prior page. + +More formally, when `page[after]` is provided, the returned [paginated data] +**MUST** have as its first item the item that is *immediately after* the cursor +in the results list. (An exception is that, if there are no items in the results +list that fall after the cursor, the returned paginated data **MUST** be an +empty array.) + +When `page[before]` is provided, the *last* item returned in the [paginated data] +**MUST** be the item that is closest to, but still before, the cursor in the +unpaginated results list. (Analogous to the above, if there are no items in the +results list that fall before the cursor, the returned paginated data **MUST** +be an empty array.) + +For example, imagine that the list of results being paginated is again: + +``` +[ + { "type": "examples", "id": "1" }, + { "type": "examples", "id": "5" }, + { "type": "examples", "id": "7" }, + { "type": "examples", "id": "8" }, + { "type": "examples", "id": "9" } +] +``` + +Further, imagine that the cursor `xxx` falls on the entry with `"id": "9"`, +while the cursor `abcde` still falls on the entry with `"id": "5"`. + +Then, for example, if the request was: + +``` +GET /example-data?page[after]=abcde&page[size]=2 +``` + +The response would contain: + +``` +{ + "links": { + "prev": "/example-data?page[before]=yyy&page[size]=2", + "next": "/example-data?page[after]=zzz&page[size]=2" + }, + "data": [ + // the pagination item metadata is optional below. + { "type": "examples", "id": "7", "meta": { "page": { "cursor": "yyy" } } }, + { "type": "examples", "id": "8", "meta": { "page": { "cursor": "zzz" } } } + ] +} +``` + +Alternatively, if the request was: + +``` +GET /example-data?page[before]=xxx&page[size]=3 +``` + +The response would contain: + +``` +{ + "links": { + "prev": "/example-data?page[before]=abcde&page[size]=3" + "next": "/example-data?page[after]=zzz&page[size]=3" + }, + "data": [ + // again, optional pagination item metadata is allowed for each item here. + { "type": "examples", "id": "5" }, + { "type": "examples", "id": "7" }, + { "type": "examples", "id": "8" } + ] +} +``` + +Notice that the `page[before]=xxx` is causing the last item in the response's +[paginated data] to be the entry with `"id": "8"`, while the number of items in +the paginated data above that item is controlled by the used page size. + +### Omitting Both `page[after]` and `page[before]` +If the client's paginated request includes neither the `page[after]` nor the +`page[before]` parameters, the returned [paginated data] **MUST** start with +the first item from the results list. (If the results list is empty, the +paginated data **MUST** be an empty array.) + +### Combining `page[after]` and `page[before]` +Clients **MAY** use the `page[after]` and `page[before]` parameters together on +the same request. These are called "range pagination requests", as the client is +asking for all the results starting from immediately after the `page[after]` +cursor and continuing up until the `page[before]` cursor. + +Servers are not required to support such requests. If the server chooses not to +support these requests, it **MUST** respond according to the rules for the +[range pagination not supported error]. + +On range pagination requests, the server **MUST** use its max page size for that +endpoint as the default page size. In other words, the [used page size] will +either by the value of the `page[size]` parameter or the max page size. + +If the number of results that satisfy both the `page[after]` and `page[before]` +constraints exceeds the used page size, the server **MUST** respond with the +same [paginated data] that it would have if the `page[before]` parameter had not +been provided. However, in this case the server **MUST** also add +`"rangeTruncated": true` to the [pagination metadata] to indicate to the client +that the paginated data does not contain all the results it requested. + +For example, given our example data and cursors above, imagine the client +requests: + +``` +GET /example-data?page[after]=abcde&page[before]=xxx +``` + +Then, assuming our server's max page size was greater than 1, the response +would contain: + +``` +{ + "links": { + "prev": "/example-data?page[before]=yyy", + "next": "/example-data?page[after]=zzz" + }, + "data": [ + { "type": "examples", "id": "7" }, + { "type": "examples", "id": "8" } + ] +} +``` + +However, if our server's max page size was 1, or the client included +`page[size]=1` in its request, the response would contain: + +``` +{ + "meta": { + "page": { "rangeTruncated": true } + }, + "links": { + "prev": "/example-data?page[before]=yyy&page[size]=1", + "next": "/example-data?page[after]=yyy&page[size]=1" + }, + "data": [ + { "type": "examples", "id": "7" } + ] +} +``` + +# Document Structure + +## Terms +This profile uses the following terms to refer to different document elements: + +1. paginated data: an array in a JSON:API + response document that holds the results that were extracted from a full list + of results being paginated. It is always the value of a `data` key. When the + [primary data](https://jsonapi.org/format/#document-top-level) is being + paginated, the value of the document's top-level `data` key is paginated data. + When the resource identifier objects in a relationship are being paginated, + the value of the `data` key in the relationship object is paginated data. + +2. pagination links: the `links` object + that's a sibling of the paginated data. + +3. pagination metadata: the [`page` + member] of the `meta` object that's a sibling of the paginated data (and + pagination links). + +4. pagination item: an entry in the + paginated data array. + +5. pagination item metadata: the + [`page` member] of the `meta` object that's at the top-level of a + paginated data item. + +To demonstrate these terms, various elements are labeled in the following +example: + +``` +GET /people?page[size]=1 +``` +``` +{ + // "pagination links" (for the top-level `data`) + "links": { }, + "meta": { + // "pagination metadata" + "page": {} + }, + // "paginated data" + "data": [ + // a "pagination item" + { + "type": "people", + "id": "1", + // "pagination item metadata" in `page`. + "meta": { "page": {} }, + "attributes": {}, + "relationships": { + "friends": { + // "pagination links". + // would be non-empty in practice to indicate that the server + // has chosen to paginate this relationship, even though the + // client hasn't explicitly asked, which is allowed. + "links": {}, + // another instance of "paginated data" + "data": [ + // a "pagination item", with (empty) "pagination item metadata". + { "type": "people", "id": "3", "meta": { "page": { } } } + ] + } + } + } + ] +} +``` + +## `page` Meta Object Members +This profile reserves a `page` member in every JSON:API-defined `meta` object. +(Each of these `page` members constitutes an element defined by this profile, +so they can be [aliased](https://jsonapi.org/format/1.1/#profile-keywords-and-aliases).) + +The `page` member, when present, **MUST** hold an object as its value; any other +values are [unrecognized](https://jsonapi.org/format/1.1/#profiles-processing). +The recognized keys/values in these various `page` objects are defined throughout +this specification. + + +## Item Cursors +The server **MAY** choose to send some or all pagination items back to the client +with a `cursor` member in the [pagination item's metadata][pagination item metadata]. +If present, this member **MUST** hold a cursor that (at the time of the response) +["falls on"][cursor] the item it is returned with. Clients can use this cursor +to paginate from this item. + +For example, a response might contain: + +``` +{ + // top-level links, meta, etc. omitted. + // more people would likely be in the response as well. + "data": [{ + "type": "people", + "id": "3", + "meta": { + "page": { "cursor": "someOpaqueString" } + } + //... + }] +} +``` + +With this response, clients could use `page[before]=someOpaqueString` or +`page[after]=someOpaqueString` to paginate from person 3 in either direction. + +## Links +JSON:API allows four types of pagination links: [`prev`, `next`, `first`, and +`last`](https://jsonapi.org/format/#fetching-pagination). + +It is **RECOMMENDED** that servers include `first` and `last` links when these +are inexpensive to compute. + +However, servers **MUST** include a `prev` and a `next` link for each instance +of paginated data in a response. + +If a request does not contain the `page[before]` parameter, the server **MUST** +determine whether a next page exists, and return `null` as the `next` link if not. + +If a request does not contain the `page[after]` parameter, the server **MUST** +determine whether a previous page exists, and return `null` as the `prev` link +if not. + +In all other cases, a server **SHOULD** set these links to `null` when it can +inexpensively determine that the current response is for the first or last page +respectively. + +However, if the server can't easily determine whether there are prior results +(when computing the `prev` link) or subsequent results (when computing the +`next` link), it **MAY** use a URI in these links that returns an empty array +as its paginated data. + +For example, imagine a request for: + +``` +GET /example-data?page[before]=xyz +``` + +To fulfill this request, the server will likely issue a query that filters the +full `example-data` results list to find only records that are before the cursor. +From those query results, the server would not know whether there are additional +results that come *after* the cursor, and it may not have a cheap way to find out. + +So, in this case, the server can simply return a `next` URI where the `page[after]` +parameter is set to the [item cursor][item cursors] for the last item in the +response's paginated data. + +If the client goes to fetch this link, it will either receive an empty array +as the paginated data, in which case it knows it's reached the end, or it will +get the subsequent results. + +> Note: in general, servers are likely to find it more expensive to determine +whether a previous page exists when `page[after]` is in use, and whether a +subsequent page exists when `page[before]` is in use. Luckily, when `page[after]` +is in use, clients usually don't care about the previous page (only the next +one), and vice-versa when `page[before]` is in use. So, by allowing the server +to return a link that might end up corresponding to an empty page, the server +can often skip a query that will never be needed. + +## Collection Sizes +The pagination metadata **MAY** contain a `total` member containing an integer +indicating the total number of items in the list of results that's being +paginated. + +For example, a response to `GET /people?page[size]=2` might include: + +``` +{ + "meta": { + "page": { "total": 200 } + }, + // links omitted + "data": [ + { + "type": "people", + // ... + }, + { + "type": "people", + // ... + } + ] +} +``` + +The pagination metadata **MAY** also contain an `estimatedTotal` member. +If present, the value of this member **MUST** be an object. That object +**MAY** have one key, `bestGuess`. If present, `bestGuess` **MUST** contain +an integer indicating the server's best estimate of the size of the full results +list. + +Server's might choose to use `estimatedTotal` instead of `total` when computing +the exact total is costly. + +# Error Cases +## Unsupported Sort Error +The server **MUST** respond to this error by sending a `400 Bad Request`. The +response document **MUST** contain an error object that identifies the `sort` +parameter as the [error's `source`](https://jsonapi.org/format/#error-objects), +and has a `type` link of: + +``` +https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort +``` + + +## Max Page Size Exceeded Error +The server **MUST** respond to this error by sending a `400 Bad Request`. The +response document **MUST** contain an error object that: + +- identifies `page[size]` as the error's `source`; +- provides the maximum page size as an integer in `maxSize` member of the `page` + element in the error object's `meta` object; and +- includes a `type` link of: + + ``` + https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded + ``` + +If this profile's `page` element has not been [aliased](https://jsonapi.org/format/1.1/#profile-keywords-and-aliases), +the error object might look like: + +``` +{ + "status": "400", + "meta": { + "page": { "maxSize": 100 } + }, + "title": "Page size requested is too large.", + "detail": "You requested a size of 200, but 100 is the maximum.", + "source": { + "parameter": "page[size]" + }, + "links": { + "type": ["https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded"] + } +} +``` + +## Invalid Parameter Value Error +The server **MUST** respond to this error by sending a `400 Bad Request` with an +error object in the response document that identifies the problematic parameter +in the error object's `source` member. + +For example, the server might send: + +``` +{ + "errors": [{ + "title": "Invalid Parameter.", + "detail": "page[size] must be a positive integer; got 0", + "source": { "parameter": "page[size]" }, + "status": "400" + }] +} +``` + +## Range Pagination Not Supported Error +The server **MUST** respond to this error by sending a `400 Bad Request`. The +response document **MUST** contain an error object that has a `type` link of: + +``` +https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported +``` + +# Notes + +1. Technically, a URI is a series of characters, so the value + of a query parameter -- including `page[size]` -- is always a string. When + the text says the value "MUST be a positive integer", it means more precisely + that the value **MUST** be a sequence of characters matching the regular + expression `^[0-9]+$`, which **MUST** then be interpreted as a base-10 integer. + + +[sorting requirement]: #concepts-sorting +[cursor]: #concepts-cursors + +[`page` member]: #document-page-meta +[item cursors]: #document-item-cursors + +[range pagination]: #query-range-pagination + +[used page size]: #terms-used-page-size +[paginated data]: #terms-paginated-data +[pagination metadata]: #terms-pagination-metadata +[pagination item]: #terms-pagination-item +[pagination item metadata]: #terms-pagination-item-metadata + +[unsupported sort error]: #errors-unsupported-sort +[max page size exceeded error]: #errors-max-page-size-exceeded +[invalid query parameter error]: #errors-invalid-query-parameter +[range pagination not supported error]: #errors-range-pagination-not-supported diff --git a/_profiles/ethanresnick/cursor-pagination/max-size-exceeded.md b/_profiles/ethanresnick/cursor-pagination/max-size-exceeded.md new file mode 100644 index 000000000..b7a9f9267 --- /dev/null +++ b/_profiles/ethanresnick/cursor-pagination/max-size-exceeded.md @@ -0,0 +1,3 @@ +--- +redirect_to: /profiles/ethanresnick/cursor-pagination/#auto-id-pagesize +--- diff --git a/_profiles/ethanresnick/cursor-pagination/range-pagination-not-supported.md b/_profiles/ethanresnick/cursor-pagination/range-pagination-not-supported.md new file mode 100644 index 000000000..21d10b38e --- /dev/null +++ b/_profiles/ethanresnick/cursor-pagination/range-pagination-not-supported.md @@ -0,0 +1,3 @@ +--- +redirect_to: /profiles/ethanresnick/cursor-pagination/#query-range-pagination +--- diff --git a/_profiles/ethanresnick/cursor-pagination/unsupported-sort.md b/_profiles/ethanresnick/cursor-pagination/unsupported-sort.md new file mode 100644 index 000000000..1507442a1 --- /dev/null +++ b/_profiles/ethanresnick/cursor-pagination/unsupported-sort.md @@ -0,0 +1,3 @@ +--- +redirect_to: /profiles/ethanresnick/cursor-pagination/#concepts-sorting +--- diff --git a/extensions/index.md b/extensions/index.md index 198060ca6..b7b8b771c 100644 --- a/extensions/index.md +++ b/extensions/index.md @@ -2,22 +2,118 @@ layout: page title: Extensions show_sidebar: true +redirect_from: /profiles --- -## Status +JSON:API can be extended with profiles. These profiles enable an API to +provide clients with information or functionality beyond that described +in the base JSON:API specification. -**An extension system is currently under development,** and you can view the -latest work [here](https://github.com/json-api/json-api/tree/profile-extensions). -There is no official support for extensions in the base JSON API specification. +Anyone can author a profile, and a single profile can be reused by multiple APIs. +Popular profiles may be implemented by off-the-shelf tools so that developers +can seamlessly take advantage of the features these profiles provide. + +## Existing Profiles + +{% for category in site.profile_categories %} +

+ {{ category }} +

+
+ {% for profile in site.profiles %} + {% if profile.categories contains category %} +
{{ profile.name }}
+
{{ profile.short_description }}
+ {% endif %} + {% endfor %} +
+{% endfor %} + +## Creating a New Profile + +### Before Creating a Profile + +Please **check whether an existing profile fits your needs or could be amended +to fit your needs** before developing a new one. + +- If a suitable profile already exists, consider using it. Having fewer, more +widely-deployed profiles makes it easier to create shared tooling. + +- If there's an existing profile that could be amended to fit your needs, +consider asking the profile's author if they would be willing to modify it as +needed. Contact them through the information in their profile's registration +and give them some time to reply. + +### Authoring Your Profile + +To author your profile, [download and fill out the template](/profile_template.md). + +### Register & Use Your Profile + +Once you've authored your profile, submit it to the JSON:API profile registry. +By registering your profile: + +1. it will be listed above for others to find and reuse. + +2. it will be given an official url on jsonapi.org. This will be the URL you and + others use to apply/identify the profile. + +3. one of the JSON API's editors will review your submission to check that it + follows the [profile extension requirements](/format/1.1/#profiles-authoring). + These requirements can be a bit tricky, so getting an expert review ensures + that your profile is legal for use with JSON:API. + +To register your profile: + +1. Choose a namespace, which is a name that uniquely identifies you or your + organization. (All profiles authored by the same person or organization are + registered under the same namespace, to prevent naming conflicts.) This + namespace should be your Github username, or the name of a Github organization + to which you belong. In unusual circumstances, you can request to use a + different name as the namespace in your PR (see below). + +2. Create a PR to [the json-api repository](https://github.com/json-api/json-api). + In the PR, make a directory at `_profiles/{NAMESPACE}/{PROFILE_NAME}` (where + `PROFILE_NAME` is the name of your profile, dasherized), and put your filled + out template as the `index.md` file in that directory folder. (See [an example](https://github.com/json-api/json-api/tree/1.1/_profiles/ethanresnick/cursor-pagination).) + +Once submitted, one of JSON:API's editors will review your profile to check that +it: 1) follows the template above; 2) complies with JSON:API's [requirements for profiles](/format/1.1/#profiles-authoring); +and 3) wouldn't cause any problems were it to become widely adopted. If your +profile meets these three criteria, it will generally be **approved within a week**. + +In limited cases (e.g., if your profile defines a new, fundamental mechanism for +doing something "architectural" that other profiles may need to do too), it might +take longer for the reviewer to adequately check that the profile wouldn't have +problematic ramifications if it became widely adopted. + +As part of this review, the editors or the community might also give design +feedback on your submission. You can take as much time as you'd like to act on/ +respond to this feedback, and the editors will wait for you to say that your +submission is finalized before merging your PR. You are encouraged, but not +required, to act on any design feedback. + +If you do change your submission after it's been reviewed, it will be re-reviewed +to make sure it still complies with the requirements for approval given above. + +JSON:API's editors may occasionally reassign responsibility for a registered +profile. The most common case of this will be to enable changes to be made to +profiles where the author of the registration has died, moved out of contact or +is otherwise unable to make changes that are important to the community. + +Even though profile registration is strongly encouraged, it is not mandatory. +If you choose not to register your profile, you can create your own URL, on +a domain you control, and use that to identify your profile. You should +self-host the filled out template at this URL. ## Prior Extensions JSON API previously offered experimental support for a different extension -negotiation system than the one now being discussed, and it provided a number of -extensions for use with that old negotiation system. However, this system was -always experimental and has now been deprecated. +negotiation system than the one now in the specification, and it provided a +number of extensions for use with that old negotiation system. However, this +system was always experimental and has now been deprecated. -New APIs should not use the old system or any extensions designed for it. APIs -that already use these old extensions should direct clients to an +New APIs should not use the old system or any extensions designed for it. +APIs that already use these old extensions should direct clients to an [earlier version of this page](https://github.com/json-api/json-api/blob/9c7a03dbc37f80f6ca81b16d444c960e96dd7a57/extensions/index.md) as documentation. diff --git a/index.md b/index.md index 786f81553..620ccf163 100644 --- a/index.md +++ b/index.md @@ -116,10 +116,11 @@ specification](/format). ## Extensions -JSON API has [experimental support for extensions](/extensions). +The JSON API community has created a collection of extensions that APIs can use +to provide clients with information or functionality beyond that described in the base JSON API specification. These extensions are called profiles. -Official extensions are being developed for [Bulk](/extensions/bulk/) and -[JSON Patch](/extensions/jsonpatch/) operations. +You can [browse existing profiles](/extensions/#existing-profiles) or +[create a new one](/extensions/#profile-creation). ## Update history diff --git a/javascripts/all.js b/javascripts/all.js index 44fe7f6a4..d030f35af 100644 --- a/javascripts/all.js +++ b/javascripts/all.js @@ -1,6 +1,17 @@ //= require_tree . $(document).ready(function() { + // Add links within headings from user-provided profiles markup for + // easy jumping and so outline generation works. + $('h2, h3, h4, h5', document.querySelector('#profile-spec-container')).each(function() { + var $this = $(this); + + if($this.find("a.headerlink").length === 0 && $this.attr('id')) { + $this.prepend(''); + } + }); + + // Build navigation list var documentOutlineElement = $("#document-outline"); diff --git a/profile_template b/profile_template new file mode 100644 index 000000000..610688975 --- /dev/null +++ b/profile_template @@ -0,0 +1,54 @@ +--- +permalink: /profile_template.md +--- +--- +name: Your Profile's Name +short_description: | + A short descriptions (1-3 sentences) about your profile's purpose and + features, to help others determine if it will work for them. + +extended_description: | + A description used in the introduction section on the profile's dedicated + page. This can be as long as you want, and can contain markdown. + +# If your profile defines values that can only be used in document members that +# were introduced after JSON:API v1.0, change the minimum_jsonapi_version field +# and fill in the minimum_jsonapi_version_explanation field with an explanation +# of the features you're relying on from the JSON:API version you've indicated. +minimum_jsonapi_version: 1.0 +minimum_jsonapi_version_explanation: + +# Url of a Github repo or some other place where people can +# ask questions/start discussions about your extension. +discussion_url: http://tets.com + +author_name: Ethan Resnick +author_email: ethan.resnick@gmail.com +# Optional fields +author_website: https://ethanresnick.com/ +author_phone: +13104398032 # use `tel` url format. + +# Valid categories are listed under the profile_categories section +# in https://github.com/json-api/json-api/blob/gh-pages/_config.yml +categories: + - Filtering +--- + +Here, specify your profile. This description should be detailed enough to allow +for interoperable implementations, but it otherwise doesn't need to be formal. +At the very least, describe the elements (document data or query parameters) +your profile defines and the allowed values for each. + +Make sure you __follow the authoring guidelines__, which describe what profiles +are and are not allowed to do, and how they should be designed to evolve over time. + +Those authoring guidelines are at: + http://jsonapi.org/format/1.1/#profiles-authoring + +Finally, it is **strongly encouraged** that your profile reuse objects, +patterns, and key names from the main JSON API specification where appropriate. + +For instance, if your profile defines a field that points to another JSON API +resource, that field should hold a resource identifier object. Or, if your +extension defines a "type" key, it should use that key as JSON API does +(i.e. to hold a string indicating a resource's type). diff --git a/stylesheets/all.css b/stylesheets/all.css index de0017289..fab5a3766 100644 --- a/stylesheets/all.css +++ b/stylesheets/all.css @@ -93,7 +93,7 @@ footer .site-wrapper, footer .social-links { text-transform: uppercase; letter-spacing: 0.2px; } -header h1, footer .social-links span { +header h1#json-api, footer .social-links span { text-indent: -119988px; overflow: hidden; text-align: left; @@ -134,7 +134,7 @@ h1 { color: #0b4e22; } h2 { - margin: 50px 0 15px 0; + margin: 2em 0 15px 0; color: #5a5a5a; font-size: 22px; line-height: 1.3; } @@ -165,13 +165,19 @@ th { a { color: #5a5a5a; } -blockquote { +blockquote, .note { margin: 20px 0; - padding: 10px 15px 0 15px; + padding: 10px 15px 10px 15px; border-radius: 4px; color: gray; background: rgba(90, 90, 90, 0.04); border: 1px solid #cccccc; } + blockquote code, .note code { + background: inherit !important; } + blockquote p, .note p { + margin-bottom: 6px; } + blockquote p:last-child, .note p:last-child { + margin-bottom: 0; } p > code, li > code { padding: 1px 4px; @@ -181,6 +187,9 @@ p > code, li > code { code { font-weight: inherit; } +address { + font-style: normal; } + /** Navigation */ .site-nav { font-weight: 500; @@ -226,7 +235,7 @@ header .content { padding: 29px 20px 41px; background: #ebebeb; border-bottom: 1px solid #cccccc; } -header h1 { +header h1#json-api { text-align: center; height: 130px; margin: 0; @@ -380,4 +389,27 @@ pre.highlight { margin: 2em 0; font-family: Menlo, monospace; } +/** Extensions page */ +.profiles-list dt, .profiles-list dd { + display: inline; + margin-left: 0; } +.profiles-list dd::after { + display: block; + content: " "; + height: 0.8em; } +.profiles-list dt::before { + content: "•"; + font-size: 1.56em; + margin-right: 1em; + position: absolute; + margin-left: -0.66em; + line-height: 1.1em; } + +/** Individual profile page */ +.profile-page h2 { + margin-top: 2em; } + +.profile-page h1 + section h2 { + margin-top: 1em; } + /*# sourceMappingURL=all.css.map */ diff --git a/stylesheets/all.css.map b/stylesheets/all.css.map index 07354ac35..b414463a8 100644 --- a/stylesheets/all.css.map +++ b/stylesheets/all.css.map @@ -1,6 +1,6 @@ { "version": 3, -"mappings": "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", +"mappings": "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", "sources": ["highlight.scss","fontello.scss","all.sass","../../../../../../../usr/local/lib/ruby/gems/2.4.0/gems/compass-core-1.0.3/stylesheets/compass/typography/text/_replacement.scss","../../../../../../../usr/local/lib/ruby/gems/2.4.0/gems/compass-core-1.0.3/stylesheets/compass/css3/_flexbox.scss","../../../../../../../usr/local/lib/ruby/gems/2.4.0/gems/compass-core-1.0.3/stylesheets/compass/_support.scss","../../../../../../../usr/local/lib/ruby/gems/2.4.0/gems/compass-core-1.0.3/stylesheets/compass/utilities/general/_clearfix.scss","../../../../../../../usr/local/lib/ruby/gems/2.4.0/gems/compass-core-1.0.3/stylesheets/compass/utilities/general/_hacks.scss"], "names": [], "file": "all.css" diff --git a/stylesheets/all.sass b/stylesheets/all.sass index 54a46a2f5..aa3b62642 100644 --- a/stylesheets/all.sass +++ b/stylesheets/all.sass @@ -19,6 +19,7 @@ $color-masthead: rgb(235, 235, 235) $fonts-body: "Helvetica Neue", "Helvetica", Arial, "Open Sans", sans-serif $border-radius-main: 4px +$h2-margin-top: 2em /** Mixins */ @@ -83,7 +84,7 @@ h1 color: $color-accent h2 - margin: 50px 0 15px 0 + margin: $h2-margin-top 0 15px 0 color: $color-main font-size: 22px line-height: 1.3 @@ -115,14 +116,23 @@ th a color: $color-main -blockquote +blockquote, .note margin: 20px 0 - padding: 10px 15px 0 15px + padding: 10px 15px 10px 15px border-radius: $border-radius-main color: lighten($color-main, 15%) background: rgba($color-main, 0.04) border: 1px solid $color-light-rule + code + background: inherit !important + + p + margin-bottom: 6px + + p:last-child + margin-bottom: 0 + p > code, li > code padding: 1px 4px background: rgba(0, 0, 0, 0.06) @@ -131,6 +141,9 @@ p > code, li > code code font-weight: inherit +address + font-style: normal + /** Navigation */ .site-nav @@ -190,7 +203,7 @@ header background: $color-masthead border-bottom: 1px solid $color-light-rule - h1 + h1#json-api @extend %hide-text text-align: center height: 130px @@ -392,3 +405,33 @@ pre.highlight line-height: 16px margin: 2em 0 font-family: Menlo, monospace + + +/** Extensions page */ +.profiles-list + dt, dd + display: inline + margin-left: 0 + + dd::after + display: block + content: " " + height: .8em + + dt::before + content: "\2022" + font-size: 1.56em + margin-right: 1em + position: absolute + margin-left: -.66em + line-height: 1.1em + +/** Individual profile page */ +// Our profile pages have more sophisticated markup than our +// main format pages, necessitating slightly different CSS. +// This is a bit of a mess.. but so is this whole file. +.profile-page h2 + margin-top: $h2-margin-top + +.profile-page h1 + section h2 + margin-top: 1em