nodulator
Version:
Complete NodeJS Framework for Restfull APIs
149 lines (132 loc) • 4.26 kB
JavaScript
/*
* Copyright 2012-2013 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/
(function (define) {
'use strict';
define(function (require) {
var interceptor, pathPrefix, rfc5988LinkParser, find;
interceptor = require('../interceptor');
pathPrefix = require('./pathPrefix');
rfc5988LinkParser = require('../parsers/rfc5988');
find = require('../util/find');
/**
* [Experimental]
*
* Supports 'Hypertext As The Engine Of Application State' style
* services by indexing the 'links' property from the entity to make
* accessing links via the 'rel' attribute easier.
*
* Links are index in two ways:
* 1. as link's 'rel' which when accessed issues a request for the
* linked resource. A promise for the related resourse is expected
* to be returned.
* 2. as link's 'rel' with 'Link' appended, as a reference to the link
* object
*
* The 'Link' response header is also parsed for related resources
* following rfc5988. The values parsed from the headers are indexed
* into the response.links object.
*
* Also defines a 'clientFor' factory function that creates a new
* client configured to communicate with a related resource.
*
* The client for the resoruce reference and the 'clientFor' function
* can be provided by the 'client' config property.
*
* Index links are exposed by default on the entity. A child object may be
* configed by the 'target' config property.
*
* @param {Client} [client] client to wrap
* @param {string} [config.target=''] property to create on the entity and
* parse links into. If empty, the response entity is used directly.
* @param {Client} [config.client=request.originator] the parent client to
* use when creating clients for a linked resources. Defaults to the
* request's originator if available, otherwise the current interceptor's
* client
*
* @returns {Client}
*/
return interceptor({
init: function (config) {
config.target = config.target || '';
return config;
},
response: function (response, config, meta) {
var client;
client = config.client || (response.request && response.request.originator) || meta.client;
function apply(target, links) {
links.forEach(function (link) {
Object.defineProperty(target, link.rel + 'Link', {
enumerable: false,
configurable: true,
value: link
});
Object.defineProperty(target, link.rel, {
enumerable: false,
configurable: true,
get: function () {
var response = client({ path: link.href });
Object.defineProperty(target, link.rel, {
enumerable: false,
configurable: true,
value: response
});
return response;
}
});
});
// if only Proxy was well supported...
Object.defineProperty(target, 'clientFor', {
enumerable: false,
value: function clientFor(rel, parentClient) {
return pathPrefix(
parentClient || client,
{ prefix: target[rel + 'Link'].href }
);
}
});
}
function parseLinkHeaders(headers) {
var links = [];
[].concat(headers).forEach(function (header) {
try {
links = links.concat(rfc5988LinkParser.parse(header));
}
catch (e) {
// ignore
// TODO consider a debug mode that logs
}
});
return links;
}
if (response.headers && response.headers.Link) {
response.links = response.links || {};
apply(response.links, parseLinkHeaders(response.headers.Link));
}
find.findProperties(response.entity, 'links', function (obj, host) {
var target;
if (Array.isArray(host.links)) {
if (config.target === '') {
target = host;
}
else {
target = {};
Object.defineProperty(host, config.target, {
enumerable: false,
value: target
});
}
apply(target, host.links);
}
});
return response;
}
});
});
}(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }
// Boilerplate for AMD and Node
));