diff --git a/errors/invalid-route-source.md b/errors/invalid-route-source.md new file mode 100644 index 000000000..fe2f40914 --- /dev/null +++ b/errors/invalid-route-source.md @@ -0,0 +1,33 @@ +# Invalid Custom Route `source` + +#### Why This Error Occurred + +When defining custom routes a route was added that causes an error during parsing. This can be due to trying to use normal `RegExp` syntax like negative lookaheads (`?!exclude`) without following `path-to-regexp`'s syntax for it. + +#### Possible Ways to Fix It + +Wrap the `RegExp` part of your `source` as an un-named parameter. + +**Before** + +```js +{ + source: '/feedback/(?!general)', + destination: '/feedback/general' +} +``` + +**After** + +```js +{ + source: '/feedback/((?!general).*)', + destination: '/feedback/general' +} +``` + +### Useful Links + +- [path-to-regexp](https://github.com/pillarjs/path-to-regexp/tree/v6.1.0) +- [named parameters](https://github.com/pillarjs/path-to-regexp/blob/v6.1.0/Readme.md#named-parameters) +- [un-named paramters](https://github.com/pillarjs/path-to-regexp/blob/v6.1.0/Readme.md#unnamed-parameters) diff --git a/packages/now-routing-utils/src/index.ts b/packages/now-routing-utils/src/index.ts index f7aefc1b2..7139f0775 100644 --- a/packages/now-routing-utils/src/index.ts +++ b/packages/now-routing-utils/src/index.ts @@ -14,6 +14,7 @@ import { convertRedirects, convertHeaders, convertTrailingSlash, + sourceToRegex, } from './superstatic'; export { getCleanUrls } from './superstatic'; @@ -142,6 +143,18 @@ function checkRegexSyntax(src: string): NowErrorNested | null { return null; } +function checkPatternSyntax(src: string): NowErrorNested | null { + try { + sourceToRegex(src); + } catch (err) { + return { + message: `Invalid pattern: "${src}"`, + src, + }; + } + return null; +} + function createNowError( code: string, msg: string, @@ -231,16 +244,29 @@ export function getTransformedRoutes({ if (typeof redirects !== 'undefined') { const code = 'invalid_redirects'; - const errors = redirects + const errorsRegex = redirects .map(r => checkRegexSyntax(r.source)) .filter(notEmpty); - if (errors.length > 0) { + if (errorsRegex.length > 0) { return { routes, error: createNowError( code, - 'Redirect `source` contains invalid regex', - errors + 'Redirect `source` contains invalid regex. Read more: https://err.sh/now/invalid-route-source', + errorsRegex + ), + }; + } + const errorsPattern = redirects + .map(r => checkPatternSyntax(r.source)) + .filter(notEmpty); + if (errorsPattern.length > 0) { + return { + routes, + error: createNowError( + code, + 'Redirect `source` contains invalid pattern. Read more: https://err.sh/now/invalid-route-source', + errorsPattern ), }; } @@ -265,16 +291,29 @@ export function getTransformedRoutes({ if (typeof rewrites !== 'undefined') { const code = 'invalid_rewrites'; - const errors = rewrites + const errorsRegex = rewrites .map(r => checkRegexSyntax(r.source)) .filter(notEmpty); - if (errors.length > 0) { + if (errorsRegex.length > 0) { return { routes, error: createNowError( code, - 'Rewrites `source` contains invalid regex', - errors + 'Rewrites `source` contains invalid regex. Read more: https://err.sh/now/invalid-route-source', + errorsRegex + ), + }; + } + const errorsPattern = rewrites + .map(r => checkPatternSyntax(r.source)) + .filter(notEmpty); + if (errorsPattern.length > 0) { + return { + routes, + error: createNowError( + code, + 'Rewrites `source` contains invalid pattern. Read more: https://err.sh/now/invalid-route-source', + errorsPattern ), }; } diff --git a/packages/now-routing-utils/src/superstatic.ts b/packages/now-routing-utils/src/superstatic.ts index d0f3add5b..84613fc21 100644 --- a/packages/now-routing-utils/src/superstatic.ts +++ b/packages/now-routing-utils/src/superstatic.ts @@ -101,7 +101,9 @@ export function convertTrailingSlash(enable: boolean, status = 308): Route[] { return routes; } -function sourceToRegex(source: string): { src: string; segments: string[] } { +export function sourceToRegex( + source: string +): { src: string; segments: string[] } { const keys: Key[] = []; const r = pathToRegexp(source, keys, { strict: true }); const segments = keys.map(k => k.name).filter(isString); diff --git a/packages/now-routing-utils/test/index.spec.js b/packages/now-routing-utils/test/index.spec.js index d0f25d2fb..6a8c51d3a 100644 --- a/packages/now-routing-utils/test/index.spec.js +++ b/packages/now-routing-utils/test/index.spec.js @@ -568,6 +568,15 @@ describe('getTransformedRoutes', () => { assert.equal(actual.error.code, 'invalid_redirects'); }); + test('should error when redirects is invalid pattern', () => { + const nowConfig = { + redirects: [{ source: '/:?', destination: '/file.html' }], + }; + const actual = getTransformedRoutes({ nowConfig }); + assert.notEqual(actual.error, null); + assert.equal(actual.error.code, 'invalid_redirects'); + }); + test('should error when rewrites is invalid regex', () => { const nowConfig = { rewrites: [{ source: '^/(*.)\\.html$', destination: '/file.html' }], @@ -577,6 +586,15 @@ describe('getTransformedRoutes', () => { assert.equal(actual.error.code, 'invalid_rewrites'); }); + test('should error when rewrites is invalid pattern', () => { + const nowConfig = { + rewrites: [{ source: '/:?', destination: '/file.html' }], + }; + const actual = getTransformedRoutes({ nowConfig }); + assert.notEqual(actual.error, null); + assert.equal(actual.error.code, 'invalid_rewrites'); + }); + test('should normalize all redirects before rewrites', () => { const nowConfig = { cleanUrls: true,