jaredhanson/crane

View on GitHub
lib/application.js

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * Module dependencies.
 */
var Router = require('./router')
  , configurable = require('configurable')
  , debug = require('debug')('crane');


/**
 * Application prototype.
 */
var app = exports = module.exports = {};
configurable(app);

/**
 * Initialize application.
 *
 * @api private
 */
app.init = function() {
  this.topic = '';
  this.settings = {};
  this._stack = [];
  this._router = new Router();
  this.defaultConfiguration();
};

/**
 * Initialize application configuration.
 *
 * @api private
 */
app.defaultConfiguration = function(){
  this.set('env', process.env.NODE_ENV || 'development');
  this.set('case sensitive routing', true);
  debug('booting in %s mode', this.get('env'));
  
  // router
  this.__defineGetter__('router', function() {
    this._usedRouter = true;
    this._router.caseSensitive = this.enabled('case sensitive routing');
    this._router.strict = this.enabled('strict routing');
    return this._router.middleware;
  });
};

/**
 * Utilize the given middleware `fn` for the given `topic`, defaulting to _/_.
 *
 * Examples:
 *
 *     app.use(crane.json());
 *
 * @param {String|Function} topic
 * @param {Function} fn
 * @return {app} for chaining
 * @api public
 */
app.use = function(topic, fn) {
  if ('string' != typeof topic) {
    fn = topic;
    topic = '';
  }
  
  // wrap sub-apps
  if ('function' == typeof fn.handle) {
    var server = fn;
    server.topic = topic;
    fn = function(msg, next) {
      server.handle(msg, next);
    };
  }
  
  // add the middleware
  debug('use %s %s', topic || '#', fn.name || 'anonymous');
  this._stack.push({ topic: topic, handle: fn });
  return this;
}

/**
 * Declare a worker for messages sent to `topic`.
 *
 * @param {String} path
 * @param {Function|Array} fns
 * @return {app} for chaining
 * @api public
 */
app.work = function(topic) {
  var args = [].slice.call(arguments);
  
  // if no router attached yet, attach the router
  if (!this._usedRouter) { this.use(this.router); }
  
  // setup route
  this._router.route.apply(this._router, args);
  return this;
}

/**
 * Handle messages, by running them through the middleware stack.
 *
 * @api private
 */
app.handle = function(msg, out) {
  var self = this
    , stack = this._stack
    , idx = 0;
  
  function next(err) {
    var layer = stack[idx++];
    
    // all done
    if (!layer) {
      // delegate to parent
      if (out) { return out(err); }
      // TODO: Implement default behavior for unhandled messages.
      if (err) {
        console.error(err.stack);
      }
      return;
    }
    
    try {
      // skip this layer if the topic doesn't match, noting that topic names are
      // case sensitive
      if (0 != msg.topic.indexOf(layer.topic)) return next(err);
      
      debug('%s %s', layer.handle.name || 'anonymous', layer.topic);
      var arity = layer.handle.length;
      if (err) {
        if (arity == 3) {
          layer.handle(err, msg, next);
        } else {
          next(err);
        }
      } else if (arity < 3) {
        layer.handle(msg, next);
      } else {
        next();
      }
    } catch (ex) {
      next(ex);
    }
  }
  next();
}