Security News
PyPI Introduces Digital Attestations to Strengthen Python Package Security
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
A simple approach to generating HAL-esque hypermedia responses based on resource definitions. Built to work best with autohost but should work with most Node.JS HTTP stacks with a little extra effort.
This is alphaware!
You can skip to the Using section but understanding the concepts behind should explain
hyped generates an abstract hypermedia model based off a resource definition and data model. This hypermedia model is then passed to a "rendering engine" that produces a HTTP response body based on the requested media type.
Understanding the structure of the hypermedia model is important if you'd like to define your own rendering function for custom media types.
HyperModel Structure
{
// top level properties starting with a '_' are metadata
// all other properties are attached based on the resource definition
_origin: { href: "", method: "" },
_links: {
self: { href: "/thing/100", method: "get" },
child: { href: "/thing/100/child/:childId", method: "get", templated: true }
},
_embedded: {
children: [
// each embedded resource will follow the same pattern/format
]
}
}
Rendering engines are simply functions with the signature function( hyperModel )
. These functions take the hyperModel and produce the correct response body for their given media type. The built in engine for "application/hal+json" looks like this:
function render( model ) {
return JSON.stringify( model );
}
Note: the engine for "application/json" is more complex since it has to effectively reduce and filter a more complex data structure to produce simple JSON.
The accept header is used to select an appropriate rendering engine (if one is available). If no engine matches the provided media type, hyped will respond to the client request with a 415 explaining that the requested media type is not supported.
New versions are implemented as diffs that get applied, in order, against the baseline. Each version provides new values for properties that replace parts of the metadata for the resource. This will hopefully make it easy to see the differences between versions of a resource and reduce the amount of copy/pasted code between versions of a resource definition.
hyped will attempt to replace path variables following two formats:
:property
:property.childProperty
The fist form will be used to attempt to read a property directly on the model. The second will attempt to read a nested property. In either case, if no match is found, the variable will be left in tact. In the second case, the period is removed and the variable becomes camel-case.
Example:
{
name: "user",
actions: {
self: {
method: "get",
url: "/user/:user.name"
}
}
}
If the user model in question had a name property of "leroyJenkins"
, the response body would look like this:
{
"name": "leroyJenkins",
"_links": {
"self": { "href": "/user/leroyJenkins", "method": "GET" }
}
}
When a URL contains path variables that could not be replaced by a value in the model, the action/origin link will indicated this with a templated
property set to true
.
{
"name": "leroyJenkins",
"_links": {
"self": { "href": "/user/leroyJenkins", "verb": "GET" },
"insult": { "href": "/user/leroyJenkins/:insult", "verb": "POST", "templated": true }
}
}
These examples show the bare minimum. You'll only get support for built in mediatypes - presently application/json
and application/hal+json
. This means if you don't provide your own engine for custom media types and a client sends an accept header for a media type hyped knows about, it will send back a 415 (unsupported media type) to the client rather than throwing an exception.
This example will add an express middleware to autohost
that extends the envelope with a fluent set of calls that can be used to construct and render a hypermedia response. The correct version, rendering engine, resource and action are all determined during the hyped middleware you add to autohost so that when rendering a response, the only things you must provide is the model.
Note: at this time autohost 0.3.0-3 or greater is required
index.js
var autohost = require( "autohost" );
var hyped = require( "hyped" )();
autohost
.init( {
noOptions: true, // turn off autohost's OPTIONS middleware
urlStrategy: hyped.urlStrategy // use hyped's URL strategy
} )
.then( hyped.addResources );
hyped.setupMiddleware( autohost );
resource.js
var databass = require( "myDAL" );
module.exports = {
name: "something",
actions: {
self: {
method: "get",
url: "/something/:id",
exclude: [],
handle: function( envelope ) {
var model = databass.getSomethingById( id );
envelope.hyped( model ).status( 200 ).render();
}
}
},
versions: {
2: {
exlude: [ "weirdFieldWeShouldNotExpose" ]
}
}
};
express
// assumes you have a hash containing all your resource definitions and express set up
var hyped = require( "hyped" )( resources );
hyped.setupMiddleware( app );
app.get( "something/:id", function( req, res ) {
var id = req.param( "id" );
var model = databass.getSomethingById( id );
req.hyped( model ).resource( "something" ).action( "self" ).status( 200 ).render();
} );
Notes
include
andexclude
are mutually exclusive
- Properties listed in
embed
are not included in the response body. To override this, you'll need to list them in theinclude
array.
{
name: the resource name (must be unique)
[inherit]: indicate if this resource should inherit a parent url
actions: {
alias: {
method: the http verb/method
url: the URL for this action
include: property names array to include in the rendered response
exclude: property names array to exclude from rendered response
filter: predicate to determine if a property's key/value should be included
condition: returns true if the action is valid for the given model
embed: defines which model properties are embedded resources and how to render them
}
},
versions: {
#: {
actionAlias: {
// any properties valid
}
}
}
}
// an embed section looks like this:
{
propertyName: {
resource: name of the embedded resource
render: which action will be used to render the resource (usually "self")
actions: an array of the actions that should be included
}
}
// account resource
{
name: "account",
actions: {
self: {
method: "get",
url: "/account/:id",
include: [ "id", "balance" ]
},
withdraw: {
method: "POST",
url: "/account/:id/withdrawal",
condition: function( account ) {
return account.balance > 0;
},
include: [ "id", "accountId", "amount", "balance", "newBalance" ]
},
deposit: {
method: "POST",
url: "/account/:id/deposit",
include: [ "id", "accountId", "amount", "balance", "newBalance" ]
}
}
}
//transaction resource
{
name: "transaction",
actions: {
self: {
method: "get",
url: "/account/:account.id/transaction/:id",
include: [ "id", "amount", "date" ]
},
detail: {
method: "get",
url: "/account/:account.id/transaction/:id?detailed=true",
include: [ "id", "amount", "date", "location", "method" ]
}
}
}
If you are using this library with Autohost, the only API you really need to know about is used for the initial setup and the response generation.
Adds the metadata for a particular resource.
Adds multiple resources at once. Intended to make autohost setup simple.
autohost.init( { noOptions: true } )
.then( hyped.addResources );
Registers a rendering function with one or more mediaTypes.
// this is a ridiculous example. don't do it.
hyped.registerEngine( "text/plain", function( model ) {
return model.toString();
} );
This call can take either a reference to the autohost lib or express's app instance. It will register both the hypermediaMiddleware
and the optionsMiddleware
with the HTTP library in use.
Refer to the usage examples above to see this call in use.
Changes hyped's default versioning detection approach. This is experimental and not recommended. The versionFn
is a function that takes the request object and returns a version number.
// this isn't great, but if you have to ...
hyped.versionWith( req ) {
return req.headers[ "x-version" ] || 1;
}
The hypermedia middleware extends autohost's envelope and the underlying request object with a set of fluent calls to help with the construction of a hypermedia response.
Keep in mind that normally, you will only use the hyped
, status
and render
calls. The middleware should correctly detect the version, mediatype, resource and action to be rendered.
// within an autohost handler
envelope.hyped( myData ).status( 200 ).render();
// within an express route
req.hyped( myData ).status( 200 ).render();
You provide the data model that the resource will render a response based on. The resources are designed to work with models that may have a great deal more information than should ever be exposed to the client.
For use in rare occasions when the resource that needs to be rendered is not the one the action is contained within.
For use in rare occasions when the action you want rendered is not the one being activated.
If omitted, this is always 200. Be good to your API's consumers and use proper status codes.
Returns the response to the client.
I think HAL is pretty awesome. We"ve added a few minor extensions and may continue. This is where we"ll describe them.
_versions
and _mediatypes
properties have been added to the payload returned from the OPTIONS call to the API root. They will list the available versions and mediatypes currently supported by the server.
Provides a data structure identical to the link that would be called to produce the response. This is especially useful within embedded resources as it allows the client to see what link action was used to produce the embedded resource included in the payload.
We added this to make it easier for clients to know which representation they have in memory for the sake of both local caching and requesting updated versions. We recommend including etag or last-modified data along with resources to further improve efficiency.
HAL does not include the HTTP verb/method used for an link"s href
. Our version does include this information in the link using the verb
property. As in our URL example from above:
{
"name": "leroyJenkins",
"_links": {
"self": { "href": "/user/leroyJenkins", "verb": "GET" }
}
}
The tests are a bit of a contrived mess at the moment but do excercise the features. If submitting a PR, please be sure to include tests to cover any new feature or changes as well as making sure no existing tests have broken.
None of this is guaranteed but here are some items that would be nice/great to have.
0.0.7
Fix edge case causing origins and links rendered for a nested resource not to have the correct prefix or parentUrl.
FAQs
Hypermedia response generation engine
The npm package hyped receives a total of 6 weekly downloads. As such, hyped popularity was classified as not popular.
We found that hyped demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.