Traveling through hyperspace ain't like dusting crops, boy! Without precise calculations we could fly right through a star or bounce too close to a supernova, and that'd end your trip real quick, wouldn't it? --Han Solo
APIs are state machines. Their responses include resource(s) in a distinct state. These resource(s) can typically transition to other states. HAN facilitates this navigation by communicating the possibilities.
At it's core, HAN defines the response, resource, & resource transitions. It does not make assumptions about API consumers. Neither does it presume to dictate UI concerns.
Consider the following.
- The created state supports the
update
&delete
transitions. - The updated state supports the
delete
transition. - The deleted state doesn't support any transitions.
Note: The resource value (User) is the same across all states. Only the possible transitions are different.
HAN respsonses are JSON objects, but the examples below are written in JavaScript for the brevity that inline comments allow.
A response is the root element which contains the entire result. Note that the response's resource value can be a single resource or a list of resources.
{
han_version: "", // the version of the HAN spec being used
han_spec: "", // the href to the HAN spec
api_version: "", // the version of the API being used
api_spec: "", // the href to the API spec
action: {}, // the action invoked to obtain this response
errors: [], // errors encountered while creating this response
custom: {}, // a container for custom meta data about the response
resource_type: "", // type type of resource [object, list]
resource: {} || [] // the HAN resource(s)
}
A resource is a container that wraps a custom value & includes hypermedia meta data.
{
name: "", // the name of the resource
value: {}, // the resource value
transitions: [], // the navigational actions that can be performed with the resource
custom: {} // a container for custom meta data about the resource
}
An action describes all necessary info required to make a transition for a given resource.
{
type: "", // the type of action [hard, soft]
name: "", // the name of the action
href: "", // the url for the action
verbs: [], // the http verbs supported by the action [GET, POST, ...]
headers: [], // the http headers required by the action
formats: [], // the formats supported by the action [json, xml, ...]
params: {}, // the params supported by the action
}
- hard - indicates the action must be invoked exactly as outlined
- soft - indicates the action params should be modified prior to invocation
{
name: "", // the name of the error
message: "", // the error message
backtrace: [], // [the error's backtrace]
inner_error: {} // [the inner error]
}
This example illustrates the create user response outlined above.
{
han_version: "1.0",
han_spec: "https://github.com/hopsoft/han/tree/v1.0",
api_version: "2.1",
api_spec: "http://api.example.com/docs/v2.1",
action: { // the action invoked to obtain this response
type: "hard",
name: "Create User",
href: "http://api.example.com/users",
verbs: [ "POST" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {
name: "Han Solo",
team: "Rebel Alliance"
}
},
errors: [], // errors encountered while creating this response
custom: {}, // a container for custom meta data about the response
resource_type: "object",
resource: {
name: "User",
value: { // the value is your custom object
id: 1,
name: "Han Solo",
team: "Rebel Alliance"
},
transitions: [ // the actions that can be performed with the resource
{
type: "soft", // params should be modified prior to invocation
name: "Update User",
href: "http://api.example.com/users/1",
verbs: [ "PUT" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {
name: "Han Solo", // value to update to [optional]
team: "Rebel Alliance" // value to update to [optional]
}
},
{
type: "hard", // must be invoked exactly as outlined
name: "Delete User",
href: "http://api.example.com/users/1",
verbs: [ "DELETE" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {}
}
],
custom: {} // a container for custom meta data about the resource
}
}
This example illustrates a list users call. It includes a list of HAN resources which looks something like this.
{
han_version: "1.0",
han_spec: "https://github.com/hopsoft/han/tree/v1.0",
api_version: "2.1",
api_spec: "http://api.example.com/docs/v2.1",
action: { // the action invoked to obtain this response
type: "hard",
name: "List Users",
href: "http://api.example.com/users",
verbs: [ "GET" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {
team: "Rebel Alliance"
}
},
errors: [], // errors encountered while creating this response
custom: {}, // a container for custom meta data about the response
resource_type: "list",
resource: [
{
name: "User",
value: { // the value is your custom object
id: 1,
name: "Han Solo",
team: "Rebel Alliance"
},
transitions: [ // the actions that can be performed with the resource
{
type: "soft", // params should be modified prior to invocation
name: "Update User",
href: "http://api.example.com/users/1",
verbs: [ "PUT" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {
name: "Han Solo", // value to update to [optional]
team: "Rebel Alliance" // value to update to [optional]
}
},
{
type: "hard", // must be invoked exactly as outlined
name: "Delete User",
href: "http://api.example.com/users/1",
verbs: [ "DELETE" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {}
}
],
custom: {} // a container for custom meta data about the resource
},
{
name: "User",
value: { // the value is your custom object
id: 2,
name: "Luke Skywalker",
team: "Rebel Alliance"
},
transitions: [ // the actions that can be performed with the resource
{
type: "soft", // params should be modified prior to invocation
name: "Update User",
href: "http://api.example.com/users/2",
verbs: [ "PUT" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {
name: "Luke Skywalker", // value to update to [optional]
team: "Rebel Alliance" // value to update to [optional]
}
},
{
type: "hard", // must be invoked exactly as outlined
name: "Delete User",
href: "http://api.example.com/users/2",
verbs: [ "DELETE" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {}
}
],
custom: {} // a container for custom meta data about the resource
},
{
name: "User",
value: { // the value is your custom object
id: 3,
name: "Princess Leia",
team: "Rebel Alliance"
},
transitions: [ // the actions that can be performed with the resource
{
type: "soft", // params should be modified prior to invocation
name: "Update User",
href: "http://api.example.com/users/3",
verbs: [ "PUT" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {
name: "Princess Leia", // value to update to [optional]
team: "Rebel Alliance" // value to update to [optional]
}
},
{
type: "hard", // must be invoked exactly as outlined
name: "Delete User",
href: "http://api.example.com/users/3",
verbs: [ "DELETE" ],
headers: {
Accept: "application/vnd.example.v2.1+json"
},
formats: [ "json" ],
params: {}
}
],
custom: {} // a container for custom meta data about the resource
},
]
}