jaredhanson/crane

View on GitHub
lib/router.js

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * Module dependencies.
 */
var Route = require('./route')
  , utils = require('./utils')
  , debug = require('debug')('crane:router')


/**
 * `Router` constructor.
 *
 * @api protected
 */
function Router() {
  var self = this;
  this.arr = [];
  this.caseSensitive = true;
  this.strict = false;

  this.middleware = function router(msg, next) {
    self._dispatch(msg, next);
  };
}

/**
 * Route `topic` to one or more callbacks.
 *
 * @param {String} path
 * @param {Function|Array} fns
 * @return {Router}
 * @api protected
 */
Router.prototype.route = function(topic, fns) {
  var fns = utils.flatten([].slice.call(arguments, 1));
  
  // ensure topic was given
  if (topic === undefined) throw new TypeError('Router.route() requires a topic');
  
  // ensure all handlers are functions
  fns.forEach(function(fn, i){
    if ('function' == typeof fn) return;
    var type = {}.toString.call(fn);
    var msg = 'Router.route() requires callback functions but got a ' + type;
    throw new TypeError(msg);
  });
  
  // create the route
  debug('defined %s', topic);
  var route = new Route(topic, fns, {
    sensitive: this.caseSensitive,
    strict: this.strict
  });
  
  // add it
  this.arr.push(route);
  return this;
};

/**
 * Route dispatcher, aka the router "middleware".
 *
 * @param {Message} msg
 * @param {Function} next
 * @api private
 */
Router.prototype._dispatch = function(msg, next) {
  debug('dispatching %s (%s)', msg.topic, msg.originalTopic);
  
  var self = this;
  
  // route dispatch
  (function iter(i, err) {
    function nextRoute(err) {
      iter(i + 1, err);
    }
    
    var route = self._match(msg, i);
    if (!route) { return next(err); }
    
    debug('matched %s', route.topic);
    
    // invoke route callbacks
    var idx = 0;
    function callbacks(err) {
      var fn = route.fns[idx++];
      try {
        if ('route' == err) {
          nextRoute();
        } else if (err && fn) {
          if (fn.length < 3) { return callbacks(err); }
          fn(err, msg, callbacks);
        } else if (fn) {
          if (fn.length < 3) { return fn(msg, callbacks); }
          callbacks();
        } else {
          nextRoute(err);
        }
      } catch (err) {
        callbacks(err);
      }
    }
    callbacks();
  })(0);
}

/**
 * Attempt to match a route for `msg`
 * with optional starting index of `i`
 * defaulting to 0.
 *
 * @param {Message} msg
 * @param {Number} i
 * @return {Route}
 * @api private
 */
Router.prototype._match = function(msg, i) {
  var topic = msg.topic
    , routes = this.arr
    , route
    , i = i || 0;
  
  
  // matching routes
  for (var len = routes.length; i < len; ++i) {
    route = routes[i];
    if (route.match(topic, msg.params = [])) {
      return route;
    }
  }
  
  return null;
}

/**
 * Expose `Router`.
 */
module.exports = Router;