text: // A router that is based on promises rather than req/res/next.
  // This is intended to replace the use of express.Router to handle
  // subsections of the API surface.
  // This will make it easier to have methods like 'batch' that
  // themselves use our routing information, without disturbing express
  // components that external developers may be modifying.
  
  function PromiseRouter() {
    // Each entry should be an object with:
    // path: the path to route, in express format
    // method: the HTTP method that this route handles.
    //   Must be one of: POST, GET, PUT, DELETE
    // handler: a function that takes request, and returns a promise.
    //   Successful handlers should resolve to an object with fields:
    //     status: optional. the http status code. defaults to 200
    //     response: a json object with the content of the response
    //     location: optional. a location header
    this.routes = [];
  }
  
  // Global flag. Set this to true to log every request and response.
  PromiseRouter.verbose = process.env.VERBOSE || false;
  
  // Merge the routes into this one
  PromiseRouter.prototype.merge = function(router) {
    for (var route of router.routes) {
      this.routes.push(route);
    }
  };
  
  PromiseRouter.prototype.route = function(method, path, handler) {
    switch(method) {
    case 'POST':
    case 'GET':
    case 'PUT':
    case 'DELETE':
      break;
    default:
      throw 'cannot route method: ' + method;
    }
  
    this.routes.push({
      path: path,
      method: method,
      handler: handler
    });
  };
  
  // Returns an object with:
  //   handler: the handler that should deal with this request
  //   params: any :-params that got parsed from the path
  // Returns undefined if there is no match.
  PromiseRouter.prototype.match = function(method, path) {
    for (var route of this.routes) {
      if (route.method != method) {
        continue;
      }
  
      // NOTE: we can only route the specific wildcards :className and
      // :objectId, and in that order.
      // This is pretty hacky but I don't want to rebuild the entire
      // express route matcher. Maybe there's a way to reuse its logic.
      var pattern = '^' + route.path + '$';
  
      pattern = pattern.replace(':className',
                                '(_?[A-Za-z][A-Za-z_0-9]*)');
      pattern = pattern.replace(':objectId',
                                '([A-Za-z0-9]+)');
      var re = new RegExp(pattern);
      var m = path.match(re);
      if (!m) {
        continue;
      }
      var params = {};
      if (m[1]) {
        params.className = m[1];
      }
      if (m[2]) {
        params.objectId = m[2];
      }
  
      return {params: params, handler: route.handler};
    }
  };
  
  // A helper function to make an express handler out of a a promise
  // handler.
  // Express handlers should never throw; if a promise handler throws we
  // just treat it like it resolved to an error.
  function makeExpressHandler(promiseHandler) {
    return function(req, res, next) {
      try {
        if (PromiseRouter.verbose) {
          console.log(req.method, req.originalUrl, req.headers,
                      JSON.stringify(req.body, null, 2));
        }
        promiseHandler(req).then((result) => {
          if (!result.response) {
            console.log('BUG: the handler did not include a "response" field');
            throw 'control should not get here';
          }
          if (PromiseRouter.verbose) {
            console.log('response:', JSON.stringify(result.response, null, 2));
          }
          var status = result.status || 200;
          res.status(status);
          if (result.location) {
            res.set('Location', result.location);
          }
          res.json(result.response);
        }, (e) => {
          if (PromiseRouter.verbose) {
            console.log('error:', e);
          }
          next(e);
        });
      } catch (e) {
        if (PromiseRouter.verbose) {
          console.log('error:', e);
        }
        next(e);
      }
    }
  }
  
  // Mount the routes on this router onto an express app (or express router)
  PromiseRouter.prototype.mountOnto = function(expressApp) {
    for (var route of this.routes) {
      switch(route.method) {
      case 'POST':
        expressApp.post(route.path, makeExpressHandler(route.handler));
        break;
      case 'GET':
        expressApp.get(route.path, makeExpressHandler(route.handler));
        break;
      case 'PUT':
        expressApp.put(route.path, makeExpressHandler(route.handler));
        break;
      case 'DELETE':
        expressApp.delete(route.path, makeExpressHandler(route.handler));
        break;
      default:
        throw 'unexpected code branch';
      }
    }
  };
  
  module.exports = PromiseRouter;
  