Skip to content
This repository has been archived by the owner on Apr 30, 2021. It is now read-only.

Commit

Permalink
0.0.5 introduces stackable api route callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
Manuel Alabor committed May 19, 2013
1 parent 282065f commit b199e94
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 57 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
@@ -0,0 +1,3 @@
# Changes
## 0.0.5
* Stack multiple callbacks to an APIAdapter route. Call `success` to proceed with the next callback, call `error` to stop the execution of any further callback.
202 changes: 147 additions & 55 deletions lib/server/apiadapter-mixin.js
@@ -1,11 +1,28 @@
/** Mixin: Barefoot.APIAdapter.Server
* The server mixin for the <APIAdapter> takes the specified apiRoutes and does
* two things:
* * Create Express.JS routes for the client part
* * Map server-side API calls to the correct callback
* * Create RESTful API routes with Express.JS, callable via HTTP
* * Map server-side API calls to the correct local callback
*
* The creation of the Express.JS routes is done on initalization. The mapping
* of server-side API calls is executed during runtime.
*
* Function Mapping:
* The following matrix describes which function of <APIAdapter.Server> is
* related to which task:
*
* > + REST API + Local API +
* > +------------------------+----------+-----------+
* > processCallbacks | X | X |
* > createExpressJsCallback | X | |
* > createExpressJsRoute | X | |
* > urlRegexp | | X |
* > matchRoute | | X |
* > addRoute | X | X |
* > createRouteFactories | X | X |
* > dispatchLocalApiCall | | X |
* > sync | | X |
* > +------------------------+----------+-----------+
*/
var _ = require('underscore')
, httpMethods = require('methods')
Expand All @@ -17,9 +34,86 @@ var _ = require('underscore')
, 'read': 'get'
};

/** PrivateFunction: processCallbacks
* This function is used to run a callback function or an array of stacked
* callback functions which are registered for an API route.
*
* Parameters:
* (Object) namedRouteParameters - If a route contains named parameters
* (stuff like ":id", ":name"), this object
* should contain these values. They are
* passed as arguments to each callback
* function.
* (Object) data - The data argument contains any larger data amount. Stuff
* like the body of a POST request in example.
* (Object) req - ExpressJS request object is needed for creating scope
* objects for callback execution
* (Object) res - ExpressJS response object is needed for creating the scope
* object for calling the handler functions.
* (Function) successHandler - A function which is injected as the first
* argument when executing the callback
* (Function) errorHandler - A function which is injected as the second
* argument when executing the callback
* (Function)/(Array) callbacks - An API function which should be executed.
* If you pass an array with functions, each
* function gets executed as it is stacked
* upon the array. Calling success will
* proceed, error will stop execution.
* Make sure you call one of them or your
* code will go nuts.
*/
function processCallbacks(namedRouteParameters, data, req, res, successHandler
, errorHandler, callbacks) {

var callbackScope = {
app: req.app
, req: req
}
, handlerScope = {
app: req.app
, req: req
, res: res
}
, callbackArguments = _.union(
successHandler.bind(handlerScope)
, errorHandler.bind(handlerScope)
, namedRouteParameters
, data
)
, index = -1
, executeCallback = function executeCallback(callback) {
callback.apply(callbackScope, callbackArguments);
}
, runner;

if(!_.isArray(callbacks)) {
runner = function() { executeCallback(callbacks); };
} else {
var finalSuccessHandler = callbackArguments[0]
, stackedSuccessHandler = function stackedSuccessHandler() {
index += 1;

if(index < callbacks.length) {
executeCallback(callbacks[index]);
} else {
finalSuccessHandler.apply(handlerScope, arguments);
}
};

callbackArguments[0] = stackedSuccessHandler;
runner = stackedSuccessHandler;
}

try {
runner();
} catch(err) {
errorHandler.call(handlerScope, err);
}
}

/** Function: createExpressJsCallback
* Encapsulates given callback function and prepares it so it can be registered
* as an express js route.
* Encapsulates given callback function or an array with stacked callback
* functions and prepares it so it can be registered as an express js route.
*
* The two functions successHandler and errorHandler are passed to the callback
* on runtime as the first two arguments.
Expand All @@ -29,36 +123,27 @@ var _ = require('underscore')
* argument when executing the callback
* (Function) errorHandler - A function which is injected as the second
* argument when executing the callback
* (Function) callback - The actual callback function you want to execute
* for an express js route.
* (Function)/(Array) callbacks - An API function which should be executed.
* If you pass an array with functions, each
* function gets executed as it is stacked
* upon the array.
*
* Returns:
* (Function) to be registered with express js.
*
* See also:
* * <processCallbacks>
*/
function createExpressJsCallback(successHandler, errorHandler, callback) {
function createExpressJsCallback(successHandler, errorHandler, callbacks) {
return function handler(req, res) {
var callbackScope = {
app: req.app
, req: req
}
, handlerScope = {
app:req.app
, req:req
, res:res
}
, args = _.union(
successHandler.bind(handlerScope)
, errorHandler.bind(handlerScope)
, _.values(req.params)
, req.body
, req.query
);

try {
callback.apply(callbackScope, args);
} catch(err) {
errorHandler.call(handlerScope, err);
}
processCallbacks(
_.values(req.params)
, req.body
, req
, res
, successHandler
, errorHandler
, callbacks);
};
}

Expand Down Expand Up @@ -87,19 +172,19 @@ function createExpressJsCallback(successHandler, errorHandler, callback) {
*
* For a set of errors with predefined HTTP status codes, see <Barefoot.Errors>.
*
* Scope:
* <createExpressJsRoute> creates each a special scope object which provides
* access to the express.js app and the current request object.
*
* Parameters:
* (Object) routes - Routes to bind
* (Object) url - URL route to bind
* (Function)/(Array) callbacks - An API function which should be executed.
* If you pass an array with functions, each
* function gets executed as it is stacked
* upon the array.
* (Function) expressJsMethod - A function of Express.JS like app.get etc.
* (Object) app - The Express.JS app
*
* See also:
* * <Barefoot.Errors>
*/
function createExpressJsRoute(url, callback, expressJsMethod, app) {
function createExpressJsRoute(url, callbacks, expressJsMethod, app) {
var expressJsHandler = createExpressJsCallback(
function success(apiResult, httpStatusCode) {
httpStatusCode = httpStatusCode || 200;
Expand All @@ -112,7 +197,7 @@ function createExpressJsRoute(url, callback, expressJsMethod, app) {
this.res.send(500);
}
}
, callback
, callbacks
, app);

expressJsMethod.call(app, url, expressJsHandler);
Expand Down Expand Up @@ -228,21 +313,24 @@ function matchRoute(method, url, apiRoutes, params) {
* Parameters:
* (String) method - The HTTP method for this route
* (String) url - The URL for this route. Gets prepared with <prepareAPIUrl>
* (Function) callback - The handler for this route
* (Function)/(Array) callbacks - An API function which should be executed.
* If you pass an array with functions, each
* function gets executed as it is stacked
* upon the array.
*/
function addRoute(method, url, callback) {
function addRoute(method, url, callbacks) {
var urlParamKeys = []
, regexp = urlRegexp(url, urlParamKeys, true, true);

if(_.isUndefined(this.apiRoutes)) { this.apiRoutes = {}; }
if(_.isUndefined(this.apiRoutes[method])) { this.apiRoutes[method] = {}; }

createExpressJsRoute.call(
this, url, callback, this.app[method], this.app
this, url, callbacks, this.app[method], this.app
);

this.apiRoutes[method][url] = {
callback: callback
callback: callbacks
, regexp: regexp
, keys: urlParamKeys
};
Expand All @@ -256,6 +344,11 @@ function addRoute(method, url, callback) {
* through the <APIAdapter> itself.
*
* These functions then can be used to create an APIAdapter route.
*
* Example:
* > apiAdapter.get('/myroute', function myCallback(success) { success(); });
* > apiAdapter.post('/myroute', function myCallback(success) { success(); });
* > // any HTTP verb :)
*/
function createRouteFactories() {
var self = this;
Expand All @@ -272,7 +365,8 @@ function createRouteFactories() {
* by Barefoots <sync> replacement.
*
* It tries to match the given url with a registered route for the given
* httpMethod. If found, the route callback is invoked.
* httpMethod. If found, the route callback(s) is/are invoked using
* <processCallbacks>.
*
* If the data argument contains any information, that stuf gets passed to the
* callback. If the options argument contains a success or error element, these
Expand Down Expand Up @@ -302,23 +396,21 @@ function dispatchLocalApiCall(httpMethod, url, data, options) {
throw new Error('Could not resolve API route: ' + url);
}

var success = function success(apiResult) {
var successHandler = function successHandler(apiResult) {
if(_.has(options, 'success')) { options.success(apiResult); }
}
, error = function error(err) {
, errorHandler = function errorHandler(err) {
if(_.has(options, 'error')) { options.error(err); }
}
, args = _.union(
success
, error
, _.values(params)
, data
);
try {
matchedRoute.callback.apply(this, args);
} catch(err) {
error(err);
}
};

processCallbacks(
_.values(params)
, data
, this.req
, {}
, successHandler
, errorHandler
, matchedRoute.callback);
}

/** Function: sync
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "node-barefoot"
, "version": "0.0.4"
, "version": "0.0.5"
, "description": "Barefoot makes code sharing between browser and server reality. Write your application once and run it on both ends of the wire."
, "keywords": [
"backbone"
Expand Down
6 changes: 6 additions & 0 deletions test/mocks/expressjs/req.js
@@ -0,0 +1,6 @@
module.exports = {
app: require('./app')
, body: ''
, params: {}
, query: {}
};
38 changes: 37 additions & 1 deletion test/specs/apiadapter.server.js
Expand Up @@ -3,15 +3,18 @@ describe('APIAdapter.Server', function() {
describe('dispatchLocalApiCall', function() {
var apiAdapter
, app
, req
, getRoute = '/testGet'
, postRoute = '/testPost';

before(function() {
app = require('../mocks/expressjs/app');
req = require('../mocks/expressjs/req');

apiAdapter = new Barefoot.APIAdapter({
app: app
});
apiAdapter.req = req;
})

it('should not fail if the options argument is omited', function() {
Expand Down Expand Up @@ -57,7 +60,40 @@ describe('APIAdapter.Server', function() {

apiAdapter.post(postRoute, handler);
apiAdapter.dispatchLocalApiCall('post', postRoute, expectedData);
})
})

it('should execute a single callback', function(done) {
apiAdapter.get(getRoute, function(success) { success(); });
apiAdapter.dispatchLocalApiCall('get', getRoute, {}, {
success: done
});
})

it('should execute all callbacks if more than one is stacked upon a route', function(done) {
var callbacks = [
function callback1(success) { success(); }
, function callback2(success) { success(); }
, function callback3(success) { success(); }
];

apiAdapter.get(getRoute, callbacks);
apiAdapter.dispatchLocalApiCall('get', getRoute, {}, {
success: done
});
})

it('should stop execution of stacked callbacks as soon as error callback is called', function(done) {
var callbacks = [
function callback1(success) { success(); }
, function callback2(success) { error(); }
];

apiAdapter.get(getRoute, callbacks);
apiAdapter.dispatchLocalApiCall('get', getRoute, {}, {
success: function() {}
, error: function() { done(); }
});
})
})

})

0 comments on commit b199e94

Please sign in to comment.