rest-boubou
Version:
RESTful HTTP client library
166 lines (142 loc) • 5.71 kB
JavaScript
/*
* Copyright 2012-2015 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/
(function (define) {
'use strict';
define(function (require) {
var defaultClient, mixin, responsePromise, client, when;
defaultClient = require('./client/default');
mixin = require('./util/mixin');
responsePromise = require('./util/responsePromise');
client = require('./client');
when = require('when');
/**
* Interceptors have the ability to intercept the request and/org response
* objects. They may augment, prune, transform or replace the
* request/response as needed. Clients may be composed by wrapping
* together multiple interceptors.
*
* Configured interceptors are functional in nature. Wrapping a client in
* an interceptor will not affect the client, merely the data that flows in
* and out of that client. A common configuration can be created once and
* shared; specialization can be created by further wrapping that client
* with custom interceptors.
*
* @param {Client} [target] client to wrap
* @param {Object} [config] configuration for the interceptor, properties will be specific to the interceptor implementation
* @returns {Client} A client wrapped with the interceptor
*
* @class Interceptor
*/
function defaultInitHandler(config) {
return config;
}
function defaultRequestHandler(request /*, config, meta */) {
return request;
}
function defaultResponseHandler(response /*, config, meta */) {
return response;
}
function race(promisesOrValues) {
// this function is different than when.any as the first to reject also wins
return when.promise(function (resolve, reject) {
promisesOrValues.forEach(function (promiseOrValue) {
when(promiseOrValue, resolve, reject);
});
});
}
/**
* Alternate return type for the request handler that allows for more complex interactions.
*
* @param properties.request the traditional request return object
* @param {Promise} [properties.abort] promise that resolves if/when the request is aborted
* @param {Client} [properties.client] override the defined client with an alternate client
* @param [properties.response] response for the request, short circuit the request
*/
function ComplexRequest(properties) {
if (!(this instanceof ComplexRequest)) {
// in case users forget the 'new' don't mix into the interceptor
return new ComplexRequest(properties);
}
mixin(this, properties);
}
/**
* Create a new interceptor for the provided handlers.
*
* @param {Function} [handlers.init] one time intialization, must return the config object
* @param {Function} [handlers.request] request handler
* @param {Function} [handlers.response] response handler regardless of error state
* @param {Function} [handlers.success] response handler when the request is not in error
* @param {Function} [handlers.error] response handler when the request is in error, may be used to 'unreject' an error state
* @param {Function} [handlers.client] the client to use if otherwise not specified, defaults to platform default client
*
* @returns {Interceptor}
*/
function interceptor(handlers) {
var initHandler, requestHandler, successResponseHandler, errorResponseHandler;
handlers = handlers || {};
initHandler = handlers.init || defaultInitHandler;
requestHandler = handlers.request || defaultRequestHandler;
successResponseHandler = handlers.success || handlers.response || defaultResponseHandler;
errorResponseHandler = handlers.error || function () {
// Propagate the rejection, with the result of the handler
return when((handlers.response || defaultResponseHandler).apply(this, arguments), when.reject, when.reject);
};
return function (target, config) {
if (typeof target === 'object') {
config = target;
}
if (typeof target !== 'function') {
target = handlers.client || defaultClient;
}
config = initHandler(config || {});
function interceptedClient(request) {
var context, meta;
context = {};
meta = { 'arguments': Array.prototype.slice.call(arguments), client: interceptedClient };
request = typeof request === 'string' ? { path: request } : request || {};
request.originator = request.originator || interceptedClient;
return responsePromise(
requestHandler.call(context, request, config, meta),
function (request) {
var response, abort, next;
next = target;
if (request instanceof ComplexRequest) {
// unpack request
abort = request.abort;
next = request.client || next;
response = request.response;
// normalize request, must be last
request = request.request;
}
response = response || when(request, function (request) {
return when(
next(request),
function (response) {
return successResponseHandler.call(context, response, config, meta);
},
function (response) {
return errorResponseHandler.call(context, response, config, meta);
}
);
});
return abort ? race([response, abort]) : response;
},
function (error) {
return when.reject({ request: request, error: error });
}
);
}
return client(interceptedClient, target);
};
}
interceptor.ComplexRequest = ComplexRequest;
return interceptor;
});
}(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }
// Boilerplate for AMD and Node
));