reblend-routing
Version:
Reblend route pattern matcher
170 lines (169 loc) • 4.96 kB
JavaScript
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* Copyright(c) 2024 Emmanuel Paul Elom
* MIT Licensed
*/
;
import debugModule from 'debug';
//@ts-ignore
import flatten from 'array-flatten';
import Layer from './layer';
import { methods } from '../utils';
const debug = debugModule('express:router:route');
/**
* Module exports.
* @public
*/
export default class Route {
path;
stack;
methods;
constructor(path) {
this.path = path;
this.stack = [];
debug('new %o', path);
// route handlers for various http methods
this.methods = {};
methods.forEach(method => {
this[method] = function (...handlers) {
const handles = flatten(handlers);
for (const handle of handles) {
if (typeof handle !== 'function') {
const type = toString.call(handle);
const msg = `Route.${method}() requires a callback function but got a ${type}`;
throw new Error(msg);
}
debug('%s %o', method, this.path);
const layer = new Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
});
}
/**
* Determine if the route handles a given method.
* @private
*/
_handles_method(method) {
if (this.methods._all) {
return true;
}
// normalize name
let name = typeof method === 'string' ? method.toLowerCase() : method;
if (name === 'head' && !this.methods['head']) {
name = 'get';
}
return Boolean(this.methods[name]);
}
/**
* @return {Array} supported HTTP methods
* @private
*/
_options() {
const methods = Object.keys(this.methods);
// append automatic head
if (this.methods.get && !this.methods.head) {
methods.push('head');
}
for (let i = 0; i < methods.length; i++) {
// make upper case
methods[i] = methods[i].toUpperCase();
}
return methods;
}
/**
* dispatch req into this route
* @private
*/
dispatch(req, done) {
let idx = 0;
const stack = this.stack;
let sync = 0;
if (stack.length === 0) {
return done();
}
let method = typeof req.method === 'string' ? req.method.toLowerCase() : req.method;
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = this;
next();
function next(err) {
// signal to exit route
if (err && err === 'route') {
return done();
}
// signal to exit router
if (err && err === 'router') {
return done(err);
}
// max sync stack
if (++sync > 100) {
return next(err);
}
const layer = stack[idx++];
// end of layers
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
next(err);
}
else if (err) {
layer.handle_error(err, req, next);
}
else {
layer.handle_request(req, next);
}
sync = 0;
}
}
/**
* Add a handler for all HTTP verbs to this route.
*
* Behaves just like middleware and can respond or call `next`
* to continue processing.
*
* You can use multiple `.all` call to add multiple handlers.
*
* function check_something(req, res, next){
* next();
* };
*
* function validate_user(req, res, next){
* next();
* };
*
* route
* .all(validate_user)
* .all(check_something)
* .get(function(req, res, next){
* res.send('hello world');
* });
*
* @param {function} handler
* @return {Route} for chaining
* @api public
*/
all(...handlers) {
const handles = flatten(handlers);
for (const handle of handles) {
if (typeof handle !== 'function') {
const type = toString.call(handle);
const msg = `Route.all() requires a callback function but got a ${type}`;
throw new TypeError(msg);
}
const layer = new Layer('/', {}, handle);
layer.method = undefined;
this.methods._all = true;
this.stack.push(layer);
}
return this;
}
}