Skip to content

Latest commit

 

History

History
463 lines (354 loc) · 17.4 KB

README.md

File metadata and controls

463 lines (354 loc) · 17.4 KB

This specification is a DRAFT.

HAP - Hypermedia Application Protocol

Rationale

The Hypermedia Application Protocol (HAP) is a domain generic, self-documenting, hypermedia type. HAP builds on top of Transit which is a data exchange format superseding JSON. HAP brings semantics for linking, manipulation and embedding of resources.

HAP is intended to be language independent. Currently Transit is already available for the following languages:

  • Clojure
  • ClojureScript
  • Java
  • JS
  • Python
  • Ruby
  • C#
  • Dart
  • Erlang
  • OCaml

The following libraries and applications are available for HAP:

By Example

A walkthrough explaining HAP by using HAP ToDo as example is available here.

Spec

Representations

Hypermedia Application Protocol representations are defined in terms of data structures like maps, lists, primitive types and extension types.

HAP defines the following six keys in the top-level map:

  • :data - application-specific data
  • :links - HAP links
  • :queries - HAP queries
  • :forms - HAP forms
  • :embedded - embedded representations
  • :ops - HAP operations

In this specification, representations are shown in there JSON-Verbose encoding to provide the maximum human readability.

The minimal HAP representation is a map with a self link:

{"~:links": {"~:self": {"~:href": "~rhttp://..."}}}

It is not necessary to include any application-specific data.

Transport

HAP representations are transferred through HTTP. APIs conforming to HAP are RESTful APIs. The HTTP version used in HAP is 1.1 specified in RFC 7230 and following. HAP clients and servers MUST conform to HTTP 1.1.

Keywords

HAP uses Transit keywords for map keys. Keywords are special strings which can have namespaces and are cached in Transit. They are typically used as map keys, enums or other controlled vocabulary. In this representation, keywords are written with a leading colon like this: :keyword. In Transit itself keywords are encoded as strings like so: "~:keyword".

Keywords can have namespaces. Some user defined keywords like link relations have to use such namespaces to avoid clashes with other keywords. A keyword with namespace consists of a namespace and a name and is encodes like this: :namespace/name. Namespaces can have several components which are separated by dots like: :com.domain.subdomain/name.

Links

Links are used to make your API discoverable. They link to other resources yielding new HAP representations. Every HAP representation has one top-level link map keyed under the :links key. The link map itself keys all links under there link relation. A HAP representation with a self link looks like this:

{"~:links": {"~:self": {"~:href": "~rhttp://..."}}}

Every link is a map with the following keys:

  • :href - the URI of the link.
  • :label - a human readable label of the resource of the link target (optional)

URIs in HAP are always specified using the Transit semantic type uri. Relative URIs are allowed and have to be resolved against the effective request URI.

Link Relation Types

Link relation types follow the RFC 5988. They are expressed as keywords.

Registered link relation types are keywords without a namespace where the name of the keyword is character-by-character equal to the token of the link relation type.

The following IANA registered link relation types are used in HAP according there established semantics:

  • :self - MUST be a URI of the resource which produced the HAP representation. The URI SHOULD be canonical. It acts as base URI of relative URIs.

  • :up - Representations SHOULD have an up link. The up link refers to a parent document in a hierarchy of documents.

  • :next - Next links are most often used in list resources which offer paging. There the next link points to the next page. Other usages are also imaginable.

  • :prev - Prev links are most often used in list resources which offer paging. There the prev link points to the previous page. Other usages are also imaginable.

  • :profile - Point to a profile of the representation. In HAP a profile contains a schema of the representation.

Extension link relation types are encoded as keywords with a namespace.

Specifying more than one Link per Link Relation

It is possible to specify more than one link per link relation. A common use case it to a link to a collection of items like line items in an order. More than one link can be specified by using an array of links like so:

{"~:links":
 {"~:line-items":
  [{"~:href": "~rhttp://..."},
   {"~:href": "~rhttp://..."}]}}

Application-Specific Data

Application-specific data can be any value under the :data key. Most often data is a map itself. The representation of a ToDo item looks like this:

{"~:data": {
   "~:label": "a",
   "~:state": "~:active"},
 "~:links": {
    "~:self": {
      "~:href": "~r/items/16069bcc-2bb2-4660-a07d-7d5b4934aa19"}}}

Queries

Queries are used to describe which query params a resource accepts. Queries are similar to HTML forms which use HTTP GET. One example of a query is simple filtering of the items in a list-like resource. Resources providing filtering will contain a query like this:

{"~:queries": 
  {"~:filter": 
    {"~:href": "~rhttp://...",
     "~:params": {"~:filter": {"~:type": "~SStr"}}}}}

Each Query is a map with the following keys:

  • :href - a URI which is encoded according the Transit spec and points to the resource handling the query
  • :params - a map of required and optional parameters
  • :label - a human readable label/title of the query (optional)
  • :desc - a human readable description of the query (optional)

Executing Queries

Queries are executed by issuing an HTTP GET request against a URI which is build the following way. First the URI from :href has to be resolved against the effective request URI of the representation containing the query. Second each parameter is encoded as query param of the URI were parameter names are encoded as strings and the values itself are encoded using non-verbose Transit JSON encoding.

Resources accepting queries have to return regular representations either directly or through redirection.

Params

Params are used by Queries and Forms to specify parameters a resources accepts. Each param is a map itself with the following keys:

  • :type - the schema describing the param
  • :optional - a boolean value which defaults to false (optional)
  • :label - a human readable label/title of the param (optional)
  • :desc - a human readable description of the param (optional)

Types are currently specified in form of Prismatic Schema expressions, but this might change in the future because Prismatic Schema is only implemented in Clojure and ClojureScript right now. Apart from that, validation of form parameters is fully optional. HAP representations are not typed at all and so are form param values. A server might use the schema specified for an form param to validate its value but this is not a requirement. At the end following Postel's Law is preferred over overly strict validation of inputs.

Transfer Schemas

Schemas are serialized using the following semantic types:

Semantic Type Tag Rep Tag Rep String Rep MessagePack JSON JSON-Verbose
leaf schema S s "leaf" "~Sleaf" "~Sleaf" "~Sleaf"
record schema record name map map suitable to create the record ["#record name", {":prop-a": "val-a"}] ["#record name", {":prop-a": "val-a"}] {"#record name": {":prop-a": "val-a"}}

The following leaf schemas are specified:

  • Any
  • Bool
  • Keyword
  • Inst
  • Int
  • Num
  • Regex
  • Str
  • Symbol
  • Uuid

The following record schemas are specified:

  • schema.core.EqSchema
  • schema.core.EnumSchema
  • schema.core.Predicate
  • schema.core.Maybe
  • schema.core.NamedSchema
  • schema.core.Either
  • schema.core.Both
  • schema.core.RequiredKey
  • schema.core.OptionalKey
  • schema.core.MapEntry
  • schema.core.One
  • schema.core.FnSchema
  • schema.core.Isa

Forms

In difference to links and queries, forms describe how new resources can be created. In that sense, forms are equivalent to HTML forms using HTTP POST. Forms are important in order to make HAP representations self describing. With forms users of an API can identify possible resource manipulations without the need of any out-of-band representationation.

A HAP representation with a form looks like this:

{"~:forms": {"~:name": {"~:href": "~rhttp://...",
                        "~:label": "..."}}}

The :href key carries a URI which is encoded according the Transit spec and points to the resource handling the form. Relative URIs are allowed and have to be resolved against the effective request URI.

The :label key specifies a human readable label/title which is optional. The :desc key specifies a human readable description which is optional.

Form have params which can convey values of arbitrary semantic types including composite types. Required and optional parameters are specified in a :params map:

{"~:href": "~rhttp://...",
 "~:params": {"~:content": {"~:type": "~SStr"},
              "~:due": {"~:type": "~SInst"}}}

Submitting Forms

Forms are exclusively used to create new resources. Clients have to use HTTP POST to issue a form request. The request payload is a Transit encoded map of parameter names to the values supplied. The resource has to respond with status code 201 and a location header containing the URI of the created resource. Client can than fetch the newly created resource if they like.

Example Form

This example is a form which lets you create new todo items. There are two prams specified: :content and :due where :content has the type Str and :due the type Inst.

{"~:href": "~r/todos",
 "~:label": "Create new ToDo Item",
 "~:params": {"~:content": {"~:type": "~SStr"},
              "~:due": {"~:type": "~SInst"}}}

The representation to post looks like this:

{"~:content": "Buy milk", 
 "~:due": "~t2016-04-12T23:20:50.52Z"} 

Embeddable Representations

Representations of resources can be embedded instead of only linked. Embedding representations is a performance optimisation and equivalent to link to there resource. One common example are line items of an order.

{"~:embedded":
  {"~:line-items":
    [{"~:data": {"~:amount": 1},
      "~:links": {"~:self": {"~:href": "~rhttp://..."},
                  "~:product": {"~:href": "~rhttp://..."}}},
     {"~:data": {"~:amount": 2},
      "~:links": {"~:self": {"~:href": "~rhttp://..."},
                  "~:product": {"~:href": "~rhttp://..."}}}]}}

The :embedded map is shaped like the :links map with the difference that it holds whole representations instead of links. The map keys are link relations too. The values are often vectors but can also be single representations.

Embedded representations MUST contain a :self link. So there has to be always a resource providing the embedded representation. Embedded representations MUST NOT be used for simple composite values which are always possible.

It is not necessary that an embedded representation equals the representation conveyed by its resource. A common use case is to provide a subset of the data as embedded representation were the full representation can be obtained by following the :self link.

Generic Operations

Apart from application specific queries and forms described earlier, HAP provides semantics for the following generic operations.

Full Resource Updates

A full resource update is done by putting a representation which should represent the new state of the resource to the URI of the resource. A conditional request SHOULD be used to issue the PUT. Successful responses to update requests have the status code 204 No Content and contain no body.

Partial updates are not possible right now. They may be added later.

Representations of resources were full updates are used should not contain embeddable representations. When embeddable representations are used, the ETag has to change whenever one of the embedded representations change. Otherwise caching of such representations would not work as intended. On the other hand the ETag is used to detect state changes of the resource itself to prevent clients from overwriting previous changes. However if the ETag changes only because an embedded representation changed, the update request of a client will be refused without a good reason. So ETags of updateable resources should only reflect the resource state and so embeddable representations are not suitable.

Full resource updates have several advantages. One advantage is the possibility to use a conditional request which prevents one from the "lost update" problem. Another advantage is cache invalidation. HTTP caches understand PUT operations on resources and invalidate the cached representation accordingly.

Resource Deletion

A resource can be deleted by issuing a HTTP DELETE request to the resource URI. Successful responses to delete requests have the status code 204 No Content and contain no body.

Operation Announcement

HAP representations contain a set under the :ops key. This set can hold up to two keywords :update and :delete depending whether the operation is implemented on the resource delivering the representation.

An example representation of a resource allowing updates looks like this:

{"~:ops": {"~#set": ["~:update"]}} 

Profiles

A profile is a representation which contains a schema of the data part of another representation. Profiles are used to generate update forms.

Related Work

HTML

HTML - Hypertext Markup Language is a media type for presenting textual representations to humans. A browser is used to drive the interaction between the human and the server providing HTML representations. The most important point is that the browser is fully generic, which means it does not depend on the server or say the application with provides the HTML representations. It's the strength of HTML which allows a rich interaction between a human and a server using a generic browser.

HAP builds on the foundations layed out by HTML. HAP takes the good stuff from HTML - the links and forms - and removes just the presentation artifacts, concentrating on pure data.

HAL

HAL - Hypertext Application Language is similar to HAP. HAP supersedes HAL in three fields: resource manipulation, encoding of application-specific data and extensible data types.

HAL lacks forms and operations, which HAP provides. In HAP it is possible to document and support resource manipulation were it is not possible in HAL.

In HAL, application-specific data is directly encoded in the top-level map which also holds the metadata. HAL reserves the map keys "_links" and "_embedded" for its metadata. Application-specific data can not use these keys and more severe, HAL can not extend its metadata part by reserving new keys, because that could break existing applications.

HAL uses JSON were HAP uses Transit. Transit offers extensible data types and has many data types already build-in. In this aspect, HAP is superior only by using Transit instead of JSON.