Skip to content

Commit

Permalink
feat: support custom pathToRegexpModule (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
fengmk2 authored Jan 22, 2025
1 parent 1c9265e commit b4b7a2c
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 19 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Router core component for [Egg.js](https://github.com/eggjs).
- [Router.url(path, params \[, options\]) ⇒ String](#routerurlpath-params--options--string)
- [Tests](#tests)
- [License](#license)
- [Contributors](#contributors)

<a name="exp_module_egg-router--Router"></a>

Expand Down Expand Up @@ -444,3 +445,9 @@ Run tests using `npm test`.
## License

[MIT](LICENSE)

## Contributors

[![Contributors](https://contrib.rocks/image?repo=eggjs/router)](https://github.com/eggjs/router/graphs/contributors)

Made with [contributors-img](https://contrib.rocks).
31 changes: 25 additions & 6 deletions lib/layer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';

const debug = require('util').debuglog('egg-router:layer');
const pathToRegExp = require('path-to-regexp');
const assert = require('assert');
const pathToRegexpModule = require('path-to-regexp');
const uri = require('urijs');
const utility = require('utility');

Expand All @@ -16,10 +17,15 @@ module.exports = class Layer {
* @param {String=} opts.name route name
* @param {String=} opts.sensitive case sensitive (default: false)
* @param {String=} opts.strict require the trailing slash (default: false)
* @param {Object=} opts.pathToRegexpModule custom path-to-regexp module
* @private
*/
constructor(path, methods, middleware, opts) {
this.opts = opts || {};
this._pathToRegexpModule = this.opts.pathToRegexpModule || pathToRegexpModule;
// support path-to-regexp@8 and path-to-regexp@1
this._pathToRegexp = this._pathToRegexpModule.pathToRegexp || this._pathToRegexpModule;
assert(typeof this._pathToRegexp === 'function', 'opts.pathToRegexpModule.pathToRegexp must be a function');
this.name = this.opts.name || null;
this.methods = [];
this.paramNames = [];
Expand All @@ -44,11 +50,22 @@ module.exports = class Layer {
}, this);

this.path = path;
this.regexp = pathToRegExp(path, this.paramNames, this.opts);

const { regexp, keys } = this._convertPathToRegexp(path, this.opts);
this.regexp = regexp;
this.paramNames = keys;
debug('defined route %s %s', this.methods, this.opts.prefix + this.path);
}

_convertPathToRegexp(path, options) {
let regexp = this._pathToRegexp(path, [], options);
const keys = regexp.keys;
if (regexp.regexp) {
// support path-to-regexp@8
regexp = regexp.regexp;
}
return { regexp, keys };
}

/**
* Returns whether request `path` matches route.
*
Expand Down Expand Up @@ -112,7 +129,7 @@ module.exports = class Layer {
url(params, options) {
let args = params;
const url = this.path.replace(/\(\.\*\)/g, '');
const toPath = pathToRegExp.compile(url);
const toPath = this._pathToRegexpModule.compile(url);

if (typeof params !== 'object') {
args = Array.prototype.slice.call(arguments);
Expand All @@ -122,7 +139,7 @@ module.exports = class Layer {
}
}

const tokens = pathToRegExp.parse(url);
const tokens = this._pathToRegexpModule.parse(url);
let replace = {};

if (args instanceof Array) {
Expand Down Expand Up @@ -209,7 +226,9 @@ module.exports = class Layer {
if (this.path) {
this.path = prefix + this.path;
this.paramNames = [];
this.regexp = pathToRegExp(this.path, this.paramNames, this.opts);
const { regexp, keys } = this._convertPathToRegexp(this.path, this.opts);
this.regexp = regexp;
this.paramNames = keys;
}

return this;
Expand Down
22 changes: 16 additions & 6 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const debug = require('util').debuglog('egg-router');
const compose = require('koa-compose');
const HttpError = require('http-errors');
const methods = require('methods');
const pathToRegexpModule = require('path-to-regexp');
const Layer = require('./layer');

/**
Expand Down Expand Up @@ -86,6 +87,7 @@ class Router {
* @param {Function} middleware middleware function
* @return {Router} router instance
*/

use(/* path, middleware */) {
const router = this;
const middleware = Array.prototype.slice.call(arguments);
Expand Down Expand Up @@ -172,7 +174,9 @@ class Router {

ctx.router = router;

if (!matched.route) return next();
if (!matched.route) {
return next();
}

const matchedLayers = matched.pathAndMethod;
const layerChain = matchedLayers.reduce(function(memo, layer) {
Expand Down Expand Up @@ -303,6 +307,7 @@ class Router {
* @return {Router} router instance
* @private
*/

all(name, path/* , middleware */) {
let middleware;

Expand Down Expand Up @@ -394,6 +399,7 @@ class Router {
strict: opts.strict || this.opts.strict || false,
prefix: opts.prefix || this.opts.prefix || '',
ignoreCaptures: opts.ignoreCaptures,
pathToRegexpModule: this.opts.pathToRegexpModule,
});

if (this.opts.prefix) {
Expand Down Expand Up @@ -462,6 +468,7 @@ class Router {
* @param {Object|String} [options.query] query options
* @return {String|Error} string or error instance
*/

url(name/* , params */) {
const route = this.route(name);

Expand Down Expand Up @@ -497,14 +504,16 @@ class Router {
for (let len = layers.length, i = 0; i < len; i++) {
layer = layers[i];

debug('test %s %s', layer.path, layer.regexp);
debug('test %s %o', layer.path, layer.regexp);

if (layer.match(path)) {
matched.path.push(layer);

if (layer.methods.length === 0 || layer.methods.includes(method)) {
matched.pathAndMethod.push(layer);
if (layer.methods.length) matched.route = true;
if (layer.methods.length) {
matched.route = true;
}
}
// if (layer.methods.length === 0) {
// matched.pathAndMethod.push(layer);
Expand Down Expand Up @@ -563,7 +572,7 @@ class Router {
* Match URL patterns to callback functions or controller actions using `router.verb()`,
* where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`.
*
* Additionaly, `router.all()` can be used to match against all methods.
* Additionally, `router.all()` can be used to match against all methods.
*
* ```javascript
* router
Expand Down Expand Up @@ -706,17 +715,18 @@ Router.prototype.del = Router.prototype.delete;
* @example
*
* ```javascript
* var url = Router.url('/users/:id', {id: 1});
* var url = Router.url('/users/:id', { id: 1 });
* // => "/users/1"
* ```
*
* @param {String} path url pattern
* @param {Object} params url parameters
* @return {String} url string
*/

Router.url = function(path/* , params */) {
const args = Array.prototype.slice.call(arguments, 1);
return Layer.prototype.url.apply({ path }, args);
return Layer.prototype.url.apply({ path, _pathToRegexpModule: pathToRegexpModule }, args);
};

Router.prototype.middleware = Router.prototype.routes;
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"node": ">= 8.5.0"
},
"publishConfig": {
"access": "public",
"tag": "release-2.x"
"access": "public"
},
"description": "Router middleware for egg/koa. Provides RESTful resource routing.",
"repository": {
Expand Down Expand Up @@ -46,7 +45,8 @@
"koa": "^2.7.0",
"mocha": "^2.0.1",
"should": "^6.0.3",
"supertest": "^1.0.1"
"supertest": "^1.0.1",
"path-to-regexp-v8": "npm:path-to-regexp@8"
},
"scripts": {
"test-local": "egg-bin test",
Expand Down
29 changes: 29 additions & 0 deletions test/lib/egg_router.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,33 @@ describe('test/lib/egg_router.test.js', () => {
assert(router.pathFor('fooo') === '');
assert(router.pathFor('hello') === '/hello/world');
});

it('should router.url work with pathToRegexpModule', () => {
// Not working on Node.js v8
// SyntaxError: Invalid regular expression: /^[$_\p{ID_Start}]$/: Invalid escape
if (process.version.startsWith('v8.')) return;

const app = {
controller: {
async foo() { return; },
hello: {
* world() { return; },
},
},
};
const router = new EggRouter({
pathToRegexpModule: require('path-to-regexp-v8'),
}, app);
router.get('post', '/post/:id', app.controller.foo);
router.get('hello', '/hello/world', app.controller.hello.world);

assert(router.url('post', { id: 1, foo: [ 1, 2 ], bar: 'bar' }) === '/post/1?foo=1&foo=2&bar=bar');
assert(router.url('post', { foo: [ 1, 2 ], bar: 'bar' }) === '/post/:id?foo=1&foo=2&bar=bar');
assert(router.url('fooo') === '');
assert(router.url('hello') === '/hello/world');

assert(router.pathFor('post', { id: 1, foo: [ 1, 2 ], bar: 'bar' }) === '/post/1?foo=1&foo=2&bar=bar');
assert(router.pathFor('fooo') === '');
assert(router.pathFor('hello') === '/hello/world');
});
});
34 changes: 30 additions & 4 deletions test/lib/layer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Router = require('../../lib/router');
const Layer = require('../../lib/layer');

describe('test/lib/layer.test.js', function() {
it('composes multiple callbacks/middlware', function(done) {
it('composes multiple callbacks/middleware', function(done) {
const app = new Koa();
const router = new Router();
app.use(router.routes());
Expand Down Expand Up @@ -53,7 +53,33 @@ describe('test/lib/layer.test.js', function() {
});
});

it('return orginal path parameters when decodeURIComponent throw error', function(done) {
it('captures URL path parameters with pathToRegexpModule', function(done) {
// Not working on Node.js v8
// SyntaxError: Invalid regular expression: /^[$_\p{ID_Start}]$/: Invalid escape
if (process.version.startsWith('v8.')) return done();

const app = new Koa();
const router = new Router({
pathToRegexpModule: require('path-to-regexp-v8'),
});
app.use(router.routes());
router.get('/:category/:title', function(ctx) {
ctx.should.have.property('params');
ctx.params.should.be.type('object');
ctx.params.should.have.property('category', 'match');
ctx.params.should.have.property('title', 'this');
ctx.status = 204;
});
request(http.createServer(app.callback()))
.get('/match/this')
.expect(204)
.end(function(err) {
if (err) return done(err);
done();
});
});

it('return original path parameters when decodeURIComponent throw error', function(done) {
const app = new Koa();
const router = new Router();
app.use(router.routes());
Expand Down Expand Up @@ -94,7 +120,7 @@ describe('test/lib/layer.test.js', function() {
});
});

it('return orginal ctx.captures when decodeURIComponent throw error', function(done) {
it('return original ctx.captures when decodeURIComponent throw error', function(done) {
const app = new Koa();
const router = new Router();
app.use(router.routes());
Expand All @@ -118,7 +144,7 @@ describe('test/lib/layer.test.js', function() {
});
});

it('populates ctx.captures with regexp captures include undefiend', function(done) {
it('populates ctx.captures with regexp captures include undefined', function(done) {
const app = new Koa();
const router = new Router();
app.use(router.routes());
Expand Down

0 comments on commit b4b7a2c

Please sign in to comment.