Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom pathToRegexpModule #18

Merged
merged 3 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading