reblend-routing
Version:
Reblend route pattern matcher
158 lines (157 loc) • 4.12 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
*/
'use strict';
//@ts-ignore
import pathRegexp from 'path-to-regexp';
import debugModule from 'debug';
const debug = debugModule('express:router:layer');
/**
* Module variables.
* @private
*/
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Module exports.
* @public
*/
export default class Layer {
handle;
name;
params;
path;
keys;
regexp;
route;
method;
constructor(path, options, fn) {
if (!(this instanceof Layer)) {
return new Layer(path, options, fn);
}
debug('new %o', path);
const opts = options || {};
this.handle = fn;
this.name = fn.name || '<anonymous>';
this.params = undefined;
this.path = undefined;
this.keys = [];
this.regexp = pathRegexp(path, this.keys, opts);
// set fast path flags
this.regexp.fast_star = path === '*';
this.regexp.fast_slash = path === '/' && opts.end === false;
}
/**
* Handle the error for the layer.
*
* @param {Error} error
* @param {Request} req
* @param {function} next
* @api private
*/
handle_error(error, req, next) {
const fn = this.handle;
if (fn.length !== 4) {
// not a standard error handler
Promise.resolve().then(() => next(error));
return;
}
Promise.resolve()
.then(() => fn(error, req, next))
.catch(err => {
Promise.resolve().then(() => next(err));
});
}
/**
* Handle the request for the layer.
*
* @param {Request} req
* @param {function} next
* @api private
*/
handle_request(req, next) {
const fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
Promise.resolve().then(() => next());
return;
}
Promise.resolve()
.then(() => fn(req, next))
.catch(err => {
Promise.resolve().then(() => next(err));
});
}
/**
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
match(path) {
let match;
if (path != null) {
// fast path non-ending match for / (any path matches)
if (this.regexp.fast_slash) {
this.params = {};
this.path = '';
return true;
}
// fast path for * (everything matched in a param)
if (this.regexp.fast_star) {
this.params = { '0': decode_param(path) };
this.path = path;
return true;
}
// match the path
match = this.regexp.exec(path);
}
if (!match) {
this.params = undefined;
this.path = undefined;
return false;
}
// store values
this.params = {};
this.path = match[0];
const keys = this.keys;
const params = this.params;
for (let i = 1; i < match.length; i++) {
const key = keys[i - 1];
const prop = key.name;
const val = decode_param(match[i]);
if (val !== undefined || !hasOwnProperty.call(params, prop)) {
params[prop] = val;
}
}
return true;
}
}
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @private
*/
function decode_param(val) {
if (typeof val !== 'string' || val.length === 0) {
return val;
}
try {
return decodeURIComponent(val);
}
catch (err) {
if (err instanceof URIError) {
err.message = `Failed to decode param '${val}'`;
err.status = err.statusCode = 400;
}
throw err;
}
}