Skip to content

Commit

Permalink
Merge pull request #184 from storyofams/fix/route-matching
Browse files Browse the repository at this point in the history
[fix] route matching
  • Loading branch information
ggurkal authored Jul 2, 2021
2 parents 29c2a41 + 7f09043 commit ce4b67d
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 18 deletions.
6 changes: 3 additions & 3 deletions lib/createHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
import { findRoute } from './internals/findRoute';
import { getFileDirectory } from './internals/getFileDirectory';
import { getCallerInfo } from './internals/getCallerInfo';
import { getParams } from './internals/getParams';
import { notFound } from './internals/notFound';
import { parseRequestUrl } from './internals/parseRequestUrl';
Expand All @@ -26,14 +26,14 @@ import { parseRequestUrl } from './internals/parseRequestUrl';
*/
export function createHandler(cls: new (...args: any[]) => any): NextApiHandler {
const instance = new cls();
const directory = getFileDirectory();
const [directory, fileName] = getCallerInfo();

return (req: NextApiRequest, res: NextApiResponse) => {
if (!req.url || !req.method) {
return notFound(req, res);
}

const path = parseRequestUrl(req.url, directory);
const path = parseRequestUrl(req, directory, fileName);
const [keys, match, method] = findRoute(cls, req.method, path);
if (!method) {
return notFound(req, res);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getFileDirectory } from './getFileDirectory';
import { getCallerInfo } from './getCallerInfo';

it('getFileDirectory', () => {
const spyError = jest.spyOn(global, 'Error');
Expand All @@ -11,8 +11,8 @@ it('getFileDirectory', () => {
return err;
});

const dir = getFileDirectory();
expect(dir).toStrictEqual('/example-path/.next/server/pages/api/tags/[id]');
const dir = getCallerInfo();
expect(dir).toStrictEqual(['/example-path/.next/server/pages/api/tags/[id]', '[[...params]].ts']);

spyError.mockRestore();
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { dirname } from 'path';
import { basename, dirname } from 'path';

export function getFileDirectory(): string | undefined {
export function getCallerInfo(): [directoryPath: string | undefined, fileName: string | undefined] {
let directoryPath: string | undefined;
let fileName: string | undefined;

const parenthesisRegExp = /\(([^)]+)\)/;
const pathInError = new Error().stack
Expand All @@ -13,9 +14,11 @@ export function getFileDirectory(): string | undefined {
const [, pathWithRowCol] = parenthesisRegExp.exec(pathInError) ?? [];
/* istanbul ignore else */
if (pathWithRowCol) {
directoryPath = dirname(pathWithRowCol.replace(/:(\d+):(\d+)$/, ''));
const fullPath = pathWithRowCol.replace(/:(\d+):(\d+)$/, '');
directoryPath = dirname(fullPath);
fileName = basename(fullPath);
}
}

return directoryPath;
return [directoryPath, fileName];
}
40 changes: 35 additions & 5 deletions lib/internals/parseRequestUrl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,52 @@ import { parseRequestUrl } from './parseRequestUrl';

describe('parseRequestUrl', () => {
it('Should return "/"', () => {
expect(parseRequestUrl('/api/user')).toStrictEqual('/');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(parseRequestUrl({ url: '/api/user' })).toStrictEqual('/');
});

it('Should return "/1"', () => {
expect(parseRequestUrl('/api/user/1')).toStrictEqual('/1');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(parseRequestUrl({ url: '/api/user/1' })).toStrictEqual('/1');
});

it('Should return "/1/articles"', () => {
expect(parseRequestUrl('/api/user/1/articles')).toStrictEqual('/1/articles');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(parseRequestUrl({ url: '/api/user/1/articles' })).toStrictEqual('/1/articles');
});

it('Should return "/" when directory name is a paremeter ([id])', () =>
expect(parseRequestUrl('/api/user/1', '/next-api-decorators/.next/server/pages/api/user/[id]')).toStrictEqual('/'));
expect(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parseRequestUrl({ url: '/api/user/1' }, '/next-api-decorators/.next/server/pages/api/user/[id]')
).toStrictEqual('/'));

it('Should return "/articles" when directory name is a paremeter ([id])', () =>
expect(
parseRequestUrl('/api/user/1/articles', '/next-api-decorators/.next/server/pages/api/user/[id]')
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parseRequestUrl({ url: '/api/user/1/articles' }, '/next-api-decorators/.next/server/pages/api/user/[id]')
).toStrictEqual('/articles'));

it('Should return "/" when the file name is "articles.js" which gets compiled from "articles/index.ts"', () =>
expect(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parseRequestUrl({ url: '/api/articles' }, '/next-api-decorators/.next/server/pages/api', 'articles.js')
).toStrictEqual('/'));

it('Should return "/" when the file name starts with "[..."', () =>
expect(
parseRequestUrl(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
{ url: '/api/article/1', query: { id: '1' } },
'/next-api-decorators/.next/server/pages/api/article',
'[...id].js'
)
).toStrictEqual('/'));
});
26 changes: 23 additions & 3 deletions lib/internals/parseRequestUrl.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
export function parseRequestUrl(url: string, directoryPath?: string): string {
import { basename, extname } from 'path';
import type { NextApiRequest } from 'next';

export function parseRequestUrl(req: NextApiRequest, directoryPath?: string, fileName?: string): string {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const url = req.url!;
let path = url.split('?')[0].split('/').slice(3).join('/');

if (directoryPath) {
const pathRegExp = new RegExp(directoryPath.split('/.next/server/pages')[1].replace(/(\[\w+\])/, '(\\w+)'));
// In order for the main method (e.g: `@Get()`) to be matched,
// the path for catch all routes should be set to "/"
if (fileName?.startsWith('[...')) {
const qsKey = basename(fileName.replace('[...', '').replace(']', ''), extname(fileName));
/* istanbul ignore else */
if (req.query[qsKey]) {
path = '';
}
}

if (directoryPath && !fileName?.startsWith('[...')) {
const pathRegExp = new RegExp(
// "pages/api/articles/index.ts" is compiled into "pages/api/articles.js" which has to be appended to the directory path for parsing
(directoryPath + (fileName && !fileName.startsWith('[') ? basename(fileName, extname(fileName)) : ''))
.split('/.next/server/pages')[1]
.replace(/(\[\w+\])/, '(\\w+)')
);
/* istanbul ignore else */
if (pathRegExp.test(url)) {
path = url.replace(pathRegExp, '');
Expand Down

0 comments on commit ce4b67d

Please sign in to comment.