diff --git a/sections/hypermedia.md b/sections/hypermedia.md index f3b6ed1..d46076a 100644 --- a/sections/hypermedia.md +++ b/sections/hypermedia.md @@ -13,231 +13,65 @@ The exclusion of Personally Identifiable Information (PII) in links provided bac References to such entities should be non-enumerable i.e. knowing one reference shouldn't allow the simple discovery of another (e.g. entities referenced with id=123 allow the enumeration of references id=122, id=124 etc). The use of UUIDs can be considered a good replacement for sequential references e.g. `6df54d5e-3df7-11ec-96ad-6f2d87ff1821` is non-enumerable. A reference may be also constructed using [pseudonymization techniques](https://en.wikipedia.org/wiki/Pseudonymization), in the case of OIDC "Subjects" the [Pairwise Identifier Algorithm](https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg) may be used i.e. the identifier is non-enumerable and persistent only for the authenticated caller, however will not be resolvable for a different caller under a different authentication context. In some circumstances producing transient, short-lived, non-persistent or one-time-use references for entities may be appropriate (if explicitly understood and handled by the service & caller. Confirmation / reset links may be an appropriate example of this). -Example: - -```javascript - -GET /namespace/v1/employees - -//200 OK - -{ - "total_records" : "1", - "_links": [{ - "href" : "/namespace/v1/employees", - "rel" : "self" - }], - "employees": [{ - "name" : "John Smith", - "_links": [{ - "href" : "/namespace/v1/employees/6df54d5e-3df7-11ec-96ad-6f2d87ff1821", - "rel" : "self" - }] - }] -} -``` - ## HATEOAS -`Hypermedia As The Engine Of Application State` is the concept of representing allowable actions as hyperlinks associated with resource. Similar to Hypermedia Linked Data concept the links defined in the response data represents state transitions that are available from that current state to adjacent states. - -```javascript -{ - "account_ref":"a2be47d4-3dfc-11ec-a765-db48afd2ce06", - "balance": 100.00, - "_links":[ - {"rel": " **deposit**", "href":"/accounts/a2be47d4-3dfc-11ec-a765-db48afd2ce06/deposit"}, - {"rel": " **withdraw**", "href":"/accounts/a2be47d4-3dfc-11ec-a765-db48afd2ce06/withdraw"}, - {"rel": " **transfer**", "href":"/accounts/a2be47d4-3dfc-11ec-a765-db48afd2ce06/transfer"} - ] -} - -``` - -But if the same account is overdrawn by 25 then the only allowed action is deposit: - -```javascript -{ - "account_ref":"a2be47d4-3dfc-11ec-a765-db48afd2ce06", - "balance": -25.00, - "_links":[{ - "rel": "deposit", - "href":"/accounts/a2be47d4-3dfc-11ec-a765-db48afd2ce06/deposit" - }] -} - -``` - -Deciding to implement HATEOAS designs in your API is dependent on the readiness of the clients to consume HATEOAS and the design and implementation effort. +Hypermedia As The Engine Of Application State (HATEOAS) SHOULD be implimented by enterprise APIs to provide a consistent notation to describe linked resources. -## Hypermedia Compliant API +HATEOAS is the concept of representing allowable actions as hyperlinks associated with resource. When provided, links defined in the response data: -A hypermedia compliant API provides consumers with an accessible a state machine of the transitions that are available for them to use. +**MUST** return a link element to self +**MUST** return links to sub-resource +**MUST** return links to adjacent members of a compound document (if any) +**SHOULD** only return links to operations which the requester is entitled +**MAY** return links to intrinsically related objects -In APIs, request methods such as `DELETE`, `PATCH`, `POST` and `PUT` initiate a transition in the state of a resource. A `GET` request **MUST** never change the state of the resource that is retrieved. +Where HATEOAS links are implemented, links MUST reflect the currently returned version. Care must be taken when linking to versioned resources outside of the current API specification. If it is a resource that maintains deprecated versions, a versioned URL may not represent the correct version for every client. If there is likely to be any doubt, consider returning an Id (or array of Ids) with an indicative name derived from the singular form of the related resource, in camelCase (remove hyphens), with a suffix of ‘Id’, e.g. 'resourceId', rather than a link. -In order to provide a better experience for API consumers, API designers **SHOULD** provide a list of state transitions that are available for each resource in the `_links` array. +### Link Notation -An example of an API that exposes a set of operations to manage a user account lifecycle and implements the HATEOAS interface constraint is as follows: +A straightforward “linkName”: “URIString” link notation MUST be used to implement HATEOAS, keyed against the sub-resource or related-resource name, without “rel” or “href” keys, and MUST be relative (as defined by rfc3986 - URI generic syntax), beginning with a leading slash character, and refering to the resource path WITHOUT the domain/hostname, as per . E.g. -A client starts their interaction with a service through the URI `/users`. This fixed URI supports both `GET` and `POST` operations. The client decides to do a `POST` operation to create a user in the system. +“link-name”: “/v1/resources/{resourceId}/sub-resources” -### Request -``` -POST https://gw.api.gov.au/dept-xyz/v1/users - -{ - "first_name": "John", - "last_name" : "smith", - ... -} -``` - -### Response - -The API creates a new user from the input and returns the following links to the client in the response. - -- A link to the created resource in the `Location` header (to comply with the 201 response spec) -- A link to retrieve the complete representation of the user (aka `self` link) (`GET`). -- A link to update the user (`PUT`). -- A link to partially update the user (`PATCH`). -- A link to delete the user (`DELETE`). - -```json -HTTP/1.1 201 CREATED -Content-Type: application/json -Location: https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179 -... +The following example represents the content of an “account” resource 12345. Available deposit, withdraw and transfer actions are represented as HATEOAS links: +```javascript { - "_links": [ - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "self", - "method" : "GET" - }, - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "delete", - "method": "DELETE" - }, - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "replace", - "method": "PUT" - }, - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "edit", - "method": "PATCH" - } - ] - + "data": { + "accountId":"e7c9ad70-3dff-11ec-9d87-6fc27f396179", + "clientId": 567890, + "balance": 100.00 + } + "links": { + "self": "/v1/accounts/e7c9ad70-3dff-11ec-9d87-6fc27f396179", + "deposits": "/v1/account/e7c9ad70-3dff-11ec-9d87-6fc27f396179/deposits", + "withdrawals": "/v1/account/e7c9ad70-3dff-11ec-9d87-6fc27f396179/withdrawals", + } } ``` -A client can store these links in its database for later use. - -A client may then want to display a set of users and their details before the admin decides to delete one of the users. So the client does a `GET` to the same fixed URI `/users`. - -### Request - -``` -GET https://gw.api.gov.au/dept-xyz/v1/users -``` - -The API returns all the users in the system with respective `self` links. - -### Response - -```json -HTTP/1.1 200 OK -Content-Type: application/json -... +But if the same account is overdrawn by 25 then the only allowed action is deposit: +```javascript { - "total_records": "2", - "users": [ - { - "first_name": "John", - "last_name": "Greenwood", - ... - "_links": [ - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "self" - } - ] - }, - { - "first_name": "David", - "last_name": "Brown", - ... - "_links": [ - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/81d13dea-3e03-11ec-8c06-bff381749c44", - "rel": "self" - } - ] - } - ] + "data": { + "accountId":"e7c9ad70-3dff-11ec-9d87-6fc27f396179", + "clientId": 567890, + "balance": 100.00 + } + "links": { + "self": "/v1/accounts/e7c9ad70-3dff-11ec-9d87-6fc27f396179", + "deposits": "/v1/account/e7c9ad70-3dff-11ec-9d87-6fc27f396179/deposits", + } } ``` -The client MAY follow the `self` link of each user and figure out all the possible operations that it can perform on the user resource. - -### Request - -``` -GET https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179 -``` - -### Response - -```json -HTTP/1.1 200 OK -Content-Type: application/json -... -{ - "first_name": "John", - "last_name": "Smith", - ... - "_links": [ - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "self", - "method" : "GET" - }, - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "delete", - "method": "DELETE" - }, - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "replace", - "method": "PUT" - }, - { - "href": "https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179", - "rel": "edit", - "method": "PATCH" - } - ] -} -``` - -To delete the user, the client retrieves the URI of the link relation type `delete` from its data store and performs a delete operation on the URI. - -### Request +A client can store these links in its database for later use, or use them to mask end-user options. -``` -DELETE https://gw.api.gov.au/dept-xyz/v1/users/e7c9ad70-3dff-11ec-9d87-6fc27f396179 - -``` +Deciding to implement HATEOAS designs in your API is dependent on the readiness of the clients to consume HATEOAS and the design and implementation effort. In summary: @@ -245,37 +79,9 @@ In summary: - The client does not need to build the logic of composing URIs to execute different requests or code any kind of business rule by looking into the response details that may be associated with the URIs and state changes. - The client acknowledges the fact that the process of creating URIs belongs to the server. - Client treats URIs as opaque identifiers. -- APIs using hypermedia in representations could be extended seamlessly. As new methods are introduced responses could be extended with relevant HATEOAS links. This way clients could take advantage of the functionality in incremental fashion. For example; if the API starts supporting a new `PATCH` operation then clients could use it to do partial updates. - -The mere presence of links does not decouple a client from having to learn the data required to make requests for a transition and all associated link semantics particularly for `POST`/`PUT`/`PATCH` operations. An API **MUST** provide documentation to clearly describe all the links, link relation types and request response formats for each of the URIs. - -## Link Description Object - -Links **MUST** be described using the [Link Description Object (LDO)](http://json-schema.org/latest/json-schema-hypermedia.html#anchor17) schema. An LDO describes a single link relation in the links array. Following is brief description for properties of Link Description Object. - -### href - -- A value for the `href` property **MUST** be provided. - -- The value of the `href` property **MUST** be a [URI template](https://tools.ietf.org/html/rfc6570) used to determine the target URI of the related resource. It **SHOULD** be resolved as a URI template per [RFC 6570](https://tools.ietf.org/html/rfc6570). +- APIs using hypermedia in representations could be extended seamlessly. As new related resources are introduced responses could be extended with relevant HATEOAS links. This way clients could take advantage of the functionality in incremental fashion. -- Use **ONLY** absolute URIs as a value for `href` property. Clients usually bookmark the absolute URI of a link relation type from the representation to make API requests later. Developers **MUST** use the URI Component Naming Conventions to construct absolute URIs. The value from the incoming `Host` header (e.g. gw.api.gov.au) MUST be used as the `host` field of the absolute URI. - -### rel - -- `rel` stands for relation as defined in Link Relation Type - -- The value of the `rel` property indicates the name of the relation to the target resource. - -- A value for the `rel` property **MUST** be provided. - -### method - -- The `method` property identifies the HTTP verb that **MUST** be used to make a request to the target of the link. The `method` property assumes a default value of `GET` if it is omitted. - -### title - -- The `title` property provides a title for the link and is a helpful documentation tool to facilitate understanding by the end clients. This property is **NOT REQUIRED**. +An API specificaion **MUST** clearly describe all the links, link relation types and request response formats for each of the URIs. ## Link Relation Type @@ -287,11 +93,8 @@ The table below describes some of the commonly used link relation types. It also |Link Relation Type | Description| |---------|------------| -|`self` | Conveys an identifier for the link's context. Usually a link pointing to the resource itself.| -|`create` | Refers to a link that can be used to create a new resource.| -|`edit` | Refers to editing (or partially updating) the representation identified by the link. Use this to represent a `PATCH` operation link.| -|`delete` | Refers to deleting a resource identified by the link. Use this `Extended link relation type` to represent a `DELETE` operation link.| -|`replace` | Refers to completely update (or replace) the representation identified by the link. Use this `Extended link relation type` to represent a `PUT` operation link.| + +|`self` | Conveys an identifier for the link's context. Usually a link pointing to the resource itself.| |`first` | Refers to the first page of the result list.| |`last` | Refers to the last page of the result list provided `total_required` is specified as a query parameter.| |`next` | Refers to the next page of the result list.| diff --git a/sections/pagination.md b/sections/pagination.md index 72bec80..3bcc991 100644 --- a/sections/pagination.md +++ b/sections/pagination.md @@ -16,70 +16,52 @@ When pagination is implemented, the following query parameters **MUST** be used: | Query Parameter | Description | Example | | --- | --- | --- | | `page` | The page that the user wants to return. | `page=1` (default: 1)| -| `limit` | The number of results per page the user wants to return. | `limit=10` (default: 10)| +| `page-size` | the number of records to return in each page. | `page-size=10` (default: 25)| This creates a URI structure as follows: -Page 1: `/customers?page=1&limit=10` +Page 1: `/customers?page=1&page-size=10` -Page 2: `/customers?page=2&limit=10` +Page 2: `/customers?page=2&page-size=10` ... -Page 50: `/customers?page=50&limit=10` +Page 50: `/customers?page=50&page-size=10` This structure is useful as it is easy to understand and can be directly included in a UI. -**Common Terms** +### Other Pagination Patterns -The following parameters are also common across REST APIs but are not to be used in this specification. - -- `offset` and `limit`. This is common in SQL databases and is a good option when you need stable permalinks to result sets however usability of the `offset` parameter can be difficult so `page` is preferred. - -- `since` and `limit`. Get everything "since" some ID or timestamp. These are useful when it is a priority to let clients efficiently stay "in sync" with data however they generally require the result set order to be very stable. +Other allowable pagination strategies include (but are not limited to): page-based, offset-based, and cursor-based. The page query parameter can be used as a basis for any of these strategies. For example, an offset-based strategy might use page-offset and page-limit e.g. ‘?page-offset=0&page-limit=10’, while a cursor-based strategy might use page-cursor. ## Pagination Response -When pagination is implemented, it is **RECOMMENDED** for the response body to include `_meta` and `_links` fields to provide the client with appropriate information about the result set, and for easy navigation within the collection. The fields can also remove the need for clients to unnecessarily parse query parameters to continue navigating the collection. +When pagination is implemented, it is **RECOMMENDED** for the response body to include `meta` and `links` fields to provide the client with appropriate information about the result set, and for easy navigation within the collection. The fields can also remove the need for clients to unnecessarily parse query parameters to continue navigating the collection. -- The `_links` field **SHOULD** include links for `self`, `first`, `last`, `next` and `prev`. +- The `links` field **SHOULD** include links for `self`, `first`, `last`, `next` and `prev`. - Each link **MUST** include any additional query string parameters provided in the original request. An example of this response body is as follows: ```javascript { - "_meta": { - "processing_time": "10 milliseconds", - "processing_time_ms": 10, - "total_records": 38, + "meta": { + "processingTime": "10 milliseconds", + "processingTimeMs": 10, + "totalPages": 8, + "totalRecords": 76, "page": 3, "limit": 10, "count": 8 }, - "_links": [ - { - "href": "/customers?page=3&limit=10", - "rel": "self" - }, - { - "href": "/customers?page=1&limit=10", - "rel": "first" - }, - { - "href": "/customers?page=5&limit=10", - "rel": "last" - }, - { - "href": "/customers?page=2&limit=10", - "rel": "prev" - }, - { - "href": "/customers?page=4&limit=10", - "rel": "next" - } - ], - "customers": ... + "links": { + "self": "/customers?page=3&limit=10", + "first": "/customers?page=1&limit=10", + "last": "/customers?page=8&limit=10", + "prev": "/customers?page=2&limit=10", + "next": "/customers?page=4&limit=10", + }, + "data": ... } ``` @@ -91,18 +73,22 @@ metaModel: readOnly: true type: object properties: - processing_time: + processingTime: type: string description: "The processing time for this request in human readable format." example: "10 milliseconds" - processing_time_ms: + processingTimeMs: type: integer description: "The processing time for this request in milliseconds." example: 10 - total_records: + totalRecords: type: integer description: "Total number of the results which meets the search criteria regardless of the page and limit." example: 38 + totalPages: + type: integer + description: "Total number of the pages in the result set." + example: 38 page: type: integer description: "The current page for this collection request." @@ -119,31 +105,23 @@ metaModel: ## Out of Range -If the client requests a page that this out of the valid range for the collection, for example if the valid page range is `1 to 15`, but the client requests `?page=0` or `?page=999999`, then the response body **SHOULD** contain an empty collection with HTTP Status code as `200`. +If the client requests a page that this out of the valid range for the collection, for example if the valid page range is `1 to 15`, but the client requests `?page=0` or `?page=1000`, then the response body **SHOULD** contain an empty collection with HTTP Status code as `200`. An example of the response is as follows: ```javascript { - "_meta": { + "meta": { "processing_time": "10 milliseconds", "processing_time_ms": 10, "total_records": 38 }, - "_links": [ - { - "href": "/customers?page=99999&limit=10", - "rel": "self" - }, - { - "href": "/customers?page=1&limit=10", - "rel": "first" - }, - { - "href": "/customers?page=15&limit=10", - "rel": "last" - } - ], - "customers": [] + "links": { + "self": "/customers?page=99999&limit=10", + "first": "/customers?page=1&limit=10", + "last": "/customers?page=15&limit=10", + }, + "data": [] } ``` +A maximum page size of 1000 records is assumed for all end points (unless otherwise stipulated in the end point definition). If a page size greater than this maximum is requested then a HTTP status of 422 Unprocessable Entity SHOULD be returned.