Status

Doc Status

Working Draft — Only experimental and ‘proof-of-concept’ apps should be built on this unstable draft.

Last Updated

2015-11-03

Proposed IANA Registrations

application/prs.hal-forms+json

Github Repo

https://github.com/mamund/hal-forms

Sample Implementation

https://github.com/apiacademy/ndcoslo2015/tree/hal-forms

Summary

This document describes a simple media type designed to add runtime FORM support for the HAL media type. This is an independent backward-compatible extension of the HAL media type and MUST NOT be treated as standard HAL or as part of the HAL specification.

Motivation

The HAL media type is a popular format for representing API responses. One of the possible reasons for this popularity is that HAL does not include detailed information about state transitions like filtered requests (e.g. search, sort, etc.) or write operations (e.g. create, edit, delete, etc.) as part of the specification. This feature keeps the complexity of HAL responses relatively low and easy to deal with. However, this also means one of the challenges of using HAL for APIs that support dynamic user inputs and/or varying work flow patterns is client applications must encode the detailed transition information (args, methods, work flow) directly in the client code. That means clients must be updated each time the input or work flow details change.

The HAL-FORMS media type is an attempt to meet this challenge by creating a format and processing pattern to support dynamic UI forms for HAL responses. This is a backward-compatible, non-standard extension. There is nothing in this format or process pattern that adds breaking changes to the HAL media type itself. Clients that implement this extension can still be valid HAL clients by degrading gracefully (e.g. not throwing errors) when HAL-FORMS documents are not returned by the server.

Note

The HAL-FORMS media type design follows many of the HAL media type conventions. For this reason, HAL-FORMS "looks like HAL." However, it is important to keep in mind that HAL-FORMS is not the same as HAL — the two should not be thought of as interchangeable in any way. Finally, HAL-FORMS documents SHOULD always be send over HTTP using the application/prs.hal-forms+json media type identifer.

Compliance

An implementation (client or server) of this specification is not compliant if it fails to satisfy one or more of the MUST or REQUIRED elements. An implementation that satisfies all the MUST and REQUIRED elements as well as all the SHOULD and RECOMMENDED elements is said to be "unconditionally compliant"; one that satisfies all the MUST and REQUIRED elements but not all the SHOULD and RECOMMENDED elements is said to be "conditionally compliant."

Note
RFC2119 Words

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119.

The HAL-FORMS Media Type

The HAL-FORMS media type is a simple JSON document that contains information on the HTTP method, message content-type, and parameters to use when making a templated request to a HAL server. This media type can be used to provide dynamic state transition descriptions at runtime.

Example HAL-FORMS document

Here is a simple HAL-FORMS document example:

Example HAL-FORMS document.
{
  "_links" : {
     "self" : {
      "href" : "http://api.example.org/rels/create"
    }
 },
  "_templates" : {
    "default" : {
      "title" : "Create",
      "method" : "post",
      "contentType" : "application/json",
      "properties" : [
        {"name" : "title", "required" : true, "value" : "", "prompt" : "Title", "regex" : "", "templated" : false},
        {"name" : "completed", "required" : false, "value" : "false", "prompt" : "Completed", "regex" : ""}
      ]
    }
  }
}

Properties of a HAL-FORMS Document

All HAL-FORMS documents MUST be valid JSON documents. A well-formed HAL-FORMS document has two top-level objects: _links and _templates. The following is a summary of the structure of the HAL-FORMS media type.

The _links element contains any links associated with the HAL-FORMS document. For this release, the only valid link object is the self link object.

_links

Contains the collection of valid link objects in the HAL-FORMS document. This is a RECOMMENDED element. If this element is missing or if the contents is unrecognized, it SHOULD be treated as if it contains a single link element with the key of self and the value of the URL used to request this HAL-FORMS document.

link

The link element consists of a key and a set of properties.

  • key : The unique identifier of the link object. This is a REQUIRED element for each _link+ object. For this release, the only valid value for the key element is "self". If this element is missing, is set to an unrecognized value, or cannot be parsed, the associated link object SHOULD be ignored.

  • href : The URL associated with the key. This is a REQUIRED element. If this is missing, set to empty or unparsable, the associated link object SHOULD be ignored.

The _templates Element

The _templates element describes the available state transition details including the HTTP method, message content-type, and arguments for the transition. This is a REQUIRED element. If the HAL-FORMS document does not contain this element or the contents are unrecognized or unparsable, the HAL-FORMS document SHOULD be ignored.

The _templates element contains a collection of template objects. Each template object contains the following possible properties:

contentType

The value of contentType is the media type the client SHOULD use when sending a request body to the server. This is an OPTIONAL element. Valid values for this release are "application/json" and "application/x-www-form-urlencoded". If the contentType property is missing, is set to empty, or contains an unrecognized value, the client SHOULD act is if the contentType is set to "application/json". See Encoding Request Bodies for details.

key

The unqiue identifier for this template object. This is a REQUIRED element. For this release, the only valid value for key is "default". If this element is missing, set to empty or is unparsable, this template object SHOULD be ignored.

method

The HTTP method the client SHOULD use when the service request. Any valid HTTP method is allowed. This is a REQUIRED element. If the value is empty or is not understood by the client, the value MUST be treated as an HTTP GET.

properties

An array of one or more anonymous property objects (see property) that each describe a parameter for the associated state transition. This is an OPTIONAL element. If the array is missing or empty, the properties collection MUST be treated as an empty set of parameters — meaning that the transition is meant to be executed without passing any parameters.

property

A JSON object that describes a state transition parameter. A property object has the following elements:

  • name : The parameter name. This is a valid JSON string. This is a REQUIRED element. If this attribute is missing or set to empty, the client SHOULD ignore this property object completely.

  • prompt : The human-readable prompt for the parameter. This is a valid JSON string. This is an OPTIONAL element. If this element is missing, clients MAY act as if the prompt value is set to the value in the name attribute.

  • readOnly : Indicates whether the parameter is read-only. This is a valid JSON boolean. This is an OPTIONAL element. If this element is missing, empty, or set to an unrecognized value, it SHOULD be treated as if the value of readOnly is set to ‘false’.

  • regex : A regular expression string to be applied to the value of the parameter. Rules for valid values are the same as the HTML5 pattern attribute. This is an OPTIONAL element. If this attribute missing, is set to empty, or is unparseable , it SHOULD be ignored.

  • required : Indicates whether the parameter is required. This is a valid JSON boolean. This is an OPTIONAL element. If this attribute is missing, set to blank or contains an unrecognized value, it SHOULD be treated as if the value of required is set to ‘false’.

  • templated : Indicate whether the value element contains a URITemplate string for the client to resolve. This is a valid JSON boolean. This is an OPTIONAL element. If this element is missing, set to empty, or contains unrecognized content, it SHOULD be treated as if the value of templated is set to ‘false’.

  • value : The parameter value. This is a valid JSON string. This string MAY contain a URITtemplate (see templated for details). This is an OPTIONAL element. If it does not exist, clients SHOULD act as if the value property is set to an empty string.

title

A human-readable string that can be used to identify this template. This is a valid JSON string. This is an OPTIONAL element. If it does not exist or is unparsable, consumers MAY use the key value of the template as the value for title.

Encoding Requests

Once the client application has used the HAL-FORMS document to render a UI for accepting inputs (and the user has supplied the inputs), that same document SHOULD be used to encode a request to send to the server to execute the state transition described by the HAL-FORMS document. There are two ways in which HAL-FORMS documents can be used to construct parameterized requests. The first is by encoding request URLs for HTTP GET, DELETE, and HEAD requests. The second is by encoding request bodies for HTTP PUT, POST, and PATCH requests.

Encoding Request URLs

When clients are instructed to send a request without a body (e.g. GET, HEAD, DELETE), clients SHOULD use the list of property object’s name and value attributes to construct a valid URL using the W3C Mutate Action URL Algorithm to produce a valid query string. Below is a simple HAL-FORMS document that uses the GET method and the resulting updated URL.

// HAL RESPONSE
{
  "_links": {
    "self": {
      "href": "http://api.example.org/task-list/",
      "title": "Reload",
      "templated": false
    },
    "http://api.example.org/rels/filter": {
      "href": "http://api.example.org/task-list/",
      "title": "Filter Tasks",
      "templated": false
    },
  }
}

// HAL FORMS DOCUMENT
{
  "_links" : {
    "self" : {
      "href" : "http://api.example.org/rels/filter"
    }
  },
  "_templates" : {
    "default" : {
      "title" : "Filter",
      "method":"get",
      "properties": [
        {"name":"title", "value":"", "prompt":"Title"},
        {"name":"completed", "value":"", "prompt":"Completed", "regex":"^(true|false)$"}
      ]
    }
  }
}

// RESULTING URL
http://api.example.org/task-list/?title=sample&completed=false
Note

The HAL media type already defines support for parameterized HTTP GET queries using URITemplates. However, the HAL-FORMS document can be treated as an alternate implementation for GET queries that include additional constraints such as prompts, regular expression validators, etc.

Encoding Request Bodies

When clients are instructed to send a request with a body (e.g. PUT, POST, PATCH), there are two possible content-types to use when encoding arguments: application/json and application/x_www-form-urlencoded. Compliant client applications MUST support sending bodies using application/json and SHOULD support sending bodies using application/x-www-urlencoded.

Sending application/json Bodies

When sending bodies encoded as application/json, clients SHOULD construct a simple JSON dictionary object that contains a set of name-value pairs that match the property objects in the HAL-FORMS document. For example, using the Example HAL-FORMS document (see above) as a guide, a client would construct a JSON dictionary object that looks like the following:

{
  "title" : "A Sample HAL Forms Response",
  "completed" : false
}

Sending application/x-www-form-urlencoded Bodies

When sending bodies encoded as application/x-www-form-urlencoded, clients SHOULD construct a body that is in compliance with the guidance in the W3C FORMS Encoding Algorithm. A sample (using the Example HAL-FORMS document) follows:

title=A+Sample+HAL+Forms+Response&completed=false

The HAL-FORMS Media Type Identifier String

The media type identifier string for HAL-FORMS documents is: application/prs.hal-forms+json This SHOULD be used as part of the HTTP accept header when making a request for a HAL-FORMS document. It SHOULD appear as the HTTP content-type header when sending a response that contains a HAL-FORMS document.

Suggested Process Flow for HAL-FORMS Documents

While it is completely up to providers and consumers to determine how they wish to use the HAL-FORMS media type, the following is a suggested process flow for runtime use of HAL-FORMS documents on the Web.

  1. Servers emit HAL responses that contain rel values which are valid URLs that return HAL-FORMS documents.

  2. Clients parse the HAL response and (either on-demand or in pre-fetch mode) request the HAL-FORMS documents.

  3. When a HAL-FORMS document is returned by the server, clients use this information to render an input UI for humans when needed.

  4. Clients collect the completed user inputs and, based on the value of contentType, craft a valid request to send to the server and execute that request.

What follows is an illustrated example of this process flow.

Starting With a Typical HAL Response.

A client application can start with a typical HAL response and use information in the representation to see if HAL-FORMS documents are available. Below is a HAL response w/ URLs for rel values. These MAY point to HAL-FORMS documents.

A Typical HAL Response with URLs for rel values
**** REQUEST
GET /task-list/ HTTP/1.1
Host: api.example.org
Accept: application/vnd.hal+json

**** RESPONSE
HTTP/1.1 200 OK
Content-Type: application/vnd.hal+json
Date: Wed, 01 Jun 2016 14:50:30 GMT

{
  "_links": {
    "self": {
      "href": "http://api.example.org/task-list/",
      "title": "Reload",
      "templated": false
    },
    "http://api.example.org/rels/create": {
      "href": "http://api.example.org/task-list/",
      "title": "Add Task",
      "templated": false
    },
    "http://api.example.org/rels/tasks": [
      {
        "href": "http://localhost:8181/1a14qx7qc81",
        "title": "Yard Work"
      },
      {
        "href": "http://localhost:8181/1d4jwe1ewt7",
        "title": "Home Work"
      },
      {
        "href": "http://localhost:8181/1e2ll5wa383",
        "title": "School Work"
      }
    ]
  }
}

Requesting a HAL-FORMS Document

After parsing the HAL response, a HAL-FORMS compliant client app MAY use the rel URLs to make requests for HAL-FORMS documents. In the case of this example, the http://api.example.org/rels/create is used to see if there is a HAL-FORMS document available.

Requesting a HAL-FORMS document
**** REQUEST
GET /rels/create HTTP/1.1
Host: api.example.org
Accept: application/prs.hal-forms+json

**** RESPONSE
HTTP/1.1 200 OK
Content-Type: application/prs.hal-forms+json
Date: Wed, 01 Jun 2016 14:59:30 GMT

{
  "_links" : {
     "self" : {
      "href" : "http://api.example.org/rels/create"
    }
 },
  "_templates" : {
    "default" : {
      "title" : "Create",
      "method" : "post",
      "contentType" : "application/json",
      "properties" : [
        {"name" : "title", "required" : true, "value" : "", "prompt" : "Title", "regex" : "", "templated" : false},
        {"name" : "completed", "required" : false, "value" : "false", "prompt" : "Completed", "regex" : ""}
      ]
    }
  }
}

Sending a HAL-FORMS Body Request

After receiving the HAL-FORMS response and rendering the UI, the client application can — once the user supplies inputs and executes the "submit" action — use the instructions in the HAL-FORMS document to compose a request and send it to the URL indicated in the HAL document response (as follows):

Sending a HAL-FORMS body request
**** REQUEST
POST /task-list/ HTTP/1.1
Host: api.example.org
Accept: application/json

{
  "title" : "A Sample HAL-FORMS Response",
  "completed" : false
}

**** RESPONSE
HTTP/1.1 201 OK
Content-Type: application/vnd.hal+json
Date: Wed, 01 Jun 2016 15:03:30 GMT
...

Extending the HAL-FORMS Document

Authors can extend the HAL-FORMS media type as long as the following rules are observed:

  1. No existing properties or objects are removed.

  2. No existing properties or objects or the list of valid values are altered in a way that is non-backward compatible (e.g. changes MUST NOT break existing implementations that adhere to this specification).

  3. All new properties or objects are treated as OPTIONAL (e.g. no new REQUIRED elements are introduced in an extension).

Warning

Authors should be aware that a future version of this specification MAY add new elements and should take care that any extensions are implemented in a way that reduces the likelihood that a future version of this specification is in conflict with your extension.

Acknowledgements

I thank the everyone who helped contribute to this specification including: Josh Cohen, Pete Johanson, Mike Kelly, Dilip Krishnan.