diff --git a/lib/layer.js b/lib/layer.js index 06f2ed6..c7932d8 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -1,6 +1,6 @@ const { parse: parseUrl, format: formatUrl } = require('node:url'); -const { pathToRegexp, compile, parse } = require('path-to-regexp'); +const { pathToRegexp, compile, parse, stringify } = require('path-to-regexp'); module.exports = class Layer { /** @@ -41,7 +41,14 @@ module.exports = class Layer { } this.path = path; - this.regexp = pathToRegexp(path, this.paramNames, this.opts); + + if (this.opts.pathIsRegexp === true) { + this.regexp = new RegExp(path); + } else if (this.path) { + const { regexp: regex, keys } = pathToRegexp(this.path, this.opts); + this.regexp = regex; + this.paramNames = keys; + } } /** @@ -116,13 +123,14 @@ module.exports = class Layer { const toPath = compile(url, { encode: encodeURIComponent, ...options }); let replaced; - - const tokens = parse(url); + const { tokens } = parse(url); let replace = {}; if (Array.isArray(args)) { for (let len = tokens.length, i = 0, j = 0; i < len; i++) { - if (tokens[i].name) replace[tokens[i].name] = args[j++]; + if (tokens[i].name) { + replace[tokens[i].name] = args[j++]; + } } } else if (tokens.some((token) => token.name)) { replace = params; @@ -130,6 +138,10 @@ module.exports = class Layer { options = params; } + for (const [key, value] of Object.entries(replace)) { + replace[key] = String(value); + } + replaced = toPath(replace); if (options && options.query) { @@ -212,8 +224,13 @@ module.exports = class Layer { this.path !== '/' || this.opts.strict === true ? `${prefix}${this.path}` : prefix; - this.paramNames = []; - this.regexp = pathToRegexp(this.path, this.paramNames, this.opts); + if (this.opts.pathIsRegexp === true || prefix instanceof RegExp) { + this.regexp = new RegExp(this.path); + } else if (this.path) { + const { regexp: regex, keys } = pathToRegexp(this.path, this.opts); + this.regexp = regex; + this.paramNames = keys; + } } return this; diff --git a/lib/router.js b/lib/router.js index f269482..9c5fadc 100644 --- a/lib/router.js +++ b/lib/router.js @@ -165,14 +165,14 @@ class Router { } } } else { - const keys = []; - pathToRegexp(router.opts.prefix || '', keys); + const { keys } = pathToRegexp(router.opts.prefix || '', router.opts); const routerPrefixHasParam = Boolean( router.opts.prefix && keys.length > 0 ); router.register(path || '([^/]*)', [], m, { end: false, - ignoreCaptures: !hasPath && !routerPrefixHasParam + ignoreCaptures: !hasPath && !routerPrefixHasParam, + pathIsRegexp: true }); } } @@ -380,7 +380,7 @@ class Router { * @returns {Router} */ all(name, path, middleware) { - if (typeof path === 'string') { + if (typeof path === 'string' || path instanceof RegExp) { middleware = Array.prototype.slice.call(arguments, 2); } else { middleware = Array.prototype.slice.call(arguments, 1); @@ -396,7 +396,12 @@ class Router { ) throw new Error('You have to provide a path when adding an all handler'); - this.register(path, methods, middleware, { name }); + const opts = { + name, + pathIsRegexp: path instanceof RegExp + }; + + this.register(path, methods, middleware, { ...this.opts, ...opts }); return this; } @@ -455,10 +460,10 @@ class Router { * @returns {Layer} * @private */ - register(path, methods, middleware, opts = {}) { + register(path, methods, middleware, newOpts = {}) { const router = this; const { stack } = this; - + const opts = { ...this.opts, ...newOpts }; // support array of paths if (Array.isArray(path)) { for (const curPath of path) { @@ -472,12 +477,15 @@ class Router { const route = new Layer(path, methods, middleware, { end: opts.end === false ? opts.end : true, name: opts.name, - sensitive: opts.sensitive || this.opts.sensitive || false, - strict: opts.strict || this.opts.strict || false, - prefix: opts.prefix || this.opts.prefix || '', - ignoreCaptures: opts.ignoreCaptures + sensitive: opts.sensitive || false, + strict: opts.strict || false, + prefix: opts.prefix || '', + ignoreCaptures: opts.ignoreCaptures, + pathIsRegexp: opts.pathIsRegexp, + trailing: opts.trailing }); + // if parent prefix exists, add prefix to new route if (this.opts.prefix) { route.setPrefix(this.opts.prefix); } @@ -812,8 +820,13 @@ for (const method of methods) { `You have to provide a path when adding a ${method} handler` ); - this.register(path, [method], middleware, { name }); + const opts = { + name, + pathIsRegexp: path instanceof RegExp + }; + // pass opts to register call on verb methods + this.register(path, [method], middleware, { ...this.opts, ...opts }); return this; }; } diff --git a/package.json b/package.json index f2255c5..9a31c26 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "dependencies": { "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "path-to-regexp": "^6.3.0" + "path-to-regexp": "^8.1.0" }, "devDependencies": { "@commitlint/cli": "^17.7.2", diff --git a/test/lib/router.js b/test/lib/router.js index bf2846f..affbb14 100644 --- a/test/lib/router.js +++ b/test/lib/router.js @@ -220,13 +220,13 @@ describe('Router', () => { const router = new Router(); router - .get('user_page', '/user/(.*).jsx', (ctx) => { + .get('user_page', '/user/{*any}.jsx', (ctx) => { ctx.body = { order: 1 }; }) - .all('app', '/app/(.*).jsx', (ctx) => { + .all('app', '/app/{*any}.jsx', (ctx) => { ctx.body = { order: 2 }; }) - .all('view', '(.*).jsx', (ctx) => { + .all('view', '{*any}.jsx', (ctx) => { ctx.body = { order: 3 }; }); @@ -244,7 +244,7 @@ describe('Router', () => { const router = new Router(); router - .get('users_single', '/users/:id(.*)', (ctx, next) => { + .get('users_single', '/users/:id{/*path}', (ctx, next) => { ctx.body = { single: true }; next(); }) @@ -268,10 +268,14 @@ describe('Router', () => { const router = new Router({ exclusive: true }); router - .get('users_single', '/users/:id(.*)', (ctx, next) => { - ctx.body = { single: true }; - next(); - }) + .get( + 'users_single', + new RegExp('/users/:id(.*)'), // eslint-disable-line prefer-regex-literals + (ctx, next) => { + ctx.body = { single: true }; + next(); + } + ) .get('users_all', '/users/all', (ctx, next) => { ctx.body = { ...ctx.body, all: true }; next(); @@ -293,7 +297,7 @@ describe('Router', () => { router.get( 'user_page', - '/user/(.*).jsx', + '/user/{*any}.jsx', () => { // no next() }, @@ -458,7 +462,7 @@ it('matches corresponding requests with optional route parameter', async () => { }); const id = '10'; const ext = '.json'; - router.get('/resources/:id{.:ext}?', (ctx) => { + router.get('/resources/:id{.:ext}', (ctx) => { assert.strictEqual('params' in ctx, true); assert.strictEqual(ctx.params.id, id); if (ctx.params.ext) assert.strictEqual(ctx.params.ext, ext.slice(1)); @@ -1653,7 +1657,7 @@ describe('Router#opts', () => { it('responds with 200', async () => { const app = new Koa(); const router = new Router({ - strict: true + trailing: false }); router.get('/info', (ctx) => { ctx.body = 'hello'; @@ -1685,7 +1689,7 @@ describe('Router#opts', () => { it('responds with 404 when has a trailing slash', async () => { const app = new Koa(); const router = new Router({ - strict: true + trailing: false }); router.get('/info', (ctx) => { ctx.body = 'hello'; @@ -1700,7 +1704,7 @@ describe('use middleware with opts', () => { it('responds with 200', async () => { const app = new Koa(); const router = new Router({ - strict: true + trailing: false }); router.get('/info', (ctx) => { ctx.body = 'hello'; @@ -1716,7 +1720,7 @@ describe('use middleware with opts', () => { it('responds with 404 when has a trailing slash', async () => { const app = new Koa(); const router = new Router({ - strict: true + trailing: false }); router.get('/info', (ctx) => { ctx.body = 'hello';