Skip to content

Commit fc32593

Browse files
committed
Implemented catch all route using regex
1 parent de62353 commit fc32593

File tree

3 files changed

+68
-23
lines changed

3 files changed

+68
-23
lines changed

packages/event-handler/src/rest/RouteHandlerRegistry.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './utils.js';
1717

1818
class RouteHandlerRegistry {
19+
readonly #regexRoutes: Map<string, DynamicRoute> = new Map();
1920
readonly #staticRoutes: Map<string, Route> = new Map();
2021
readonly #dynamicRoutesSet: Set<string> = new Set();
2122
readonly #dynamicRoutes: DynamicRoute[] = [];
@@ -103,6 +104,18 @@ class RouteHandlerRegistry {
103104

104105
const compiled = compilePath(route.path);
105106

107+
if (/[^a-zA-Z0-9/-:]/.test(compiled.path)) {
108+
if (this.#regexRoutes.has(route.id)) {
109+
this.#logger.warn(
110+
`Handler for method: ${route.method} and path: ${route.path} already exists. The previous handler will be replaced.`
111+
);
112+
}
113+
this.#regexRoutes.set(route.id, {
114+
...route,
115+
...compiled,
116+
});
117+
return;
118+
}
106119
if (compiled.isDynamic) {
107120
const dynamicRoute = {
108121
...route,
@@ -171,28 +184,10 @@ class RouteHandlerRegistry {
171184
};
172185
}
173186

174-
for (const route of this.#dynamicRoutes) {
175-
if (route.method !== method) continue;
176-
177-
const match = route.regex.exec(path);
178-
if (match?.groups) {
179-
const params = match.groups;
180-
181-
const processedParams = this.#processParams(params);
182-
183-
const validation = this.#validateParams(processedParams);
184-
185-
if (!validation.isValid) {
186-
throw new ParameterValidationError(validation.issues);
187-
}
188-
189-
return {
190-
handler: route.handler,
191-
params: processedParams,
192-
rawParams: params,
193-
middleware: route.middleware,
194-
};
195-
}
187+
const routes = [...this.#dynamicRoutes, ...this.#regexRoutes.values()];
188+
for (const route of routes) {
189+
const result = this.#processRoute(route, method, path);
190+
if (result) return result;
196191
}
197192

198193
return null;
@@ -227,6 +222,28 @@ class RouteHandlerRegistry {
227222
);
228223
}
229224
}
225+
226+
#processRoute(route: DynamicRoute, method: HttpMethod, path: Path) {
227+
if (route.method !== method) return;
228+
229+
const match = route.regex.exec(path);
230+
if (!match) return;
231+
232+
const params = match.groups || {};
233+
const processedParams = this.#processParams(params);
234+
const validation = this.#validateParams(processedParams);
235+
236+
if (!validation.isValid) {
237+
throw new ParameterValidationError(validation.issues);
238+
}
239+
240+
return {
241+
handler: route.handler,
242+
params: processedParams,
243+
rawParams: params,
244+
middleware: route.middleware,
245+
};
246+
}
230247
}
231248

232249
export { RouteHandlerRegistry };

packages/event-handler/src/types/rest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ type HttpMethod = keyof typeof HttpVerbs;
6464

6565
type HttpStatusCode = (typeof HttpStatusCodes)[keyof typeof HttpStatusCodes];
6666

67-
type Path = `/${string}`;
67+
type Path = string;
6868

6969
type RestRouteHandlerOptions = {
7070
handler: RouteHandler;

packages/event-handler/tests/unit/rest/Router/basic-routing.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,32 @@ describe('Class: Router - Basic Routing', () => {
190190
'Handler for method: GET and path: /todos already exists. The previous handler will be replaced.'
191191
);
192192
});
193+
194+
it.each([
195+
['/files/test', 'GET', 'serveFileOverride'],
196+
['/api/v1/test', 'GET', 'apiVersioning'],
197+
['/users/1/files/test', 'GET', 'dynamicRegex1'],
198+
['/any-route', 'GET', 'getAnyRoute'],
199+
['/no-matches', 'POST', 'catchAllUnmatched'],
200+
])('routes %s %s to %s handler', async (path, method, expectedApi) => {
201+
// Prepare
202+
const app = new Router();
203+
app.get('/files/.+', async () => ({ api: 'serveFile' }));
204+
app.get('/files/.+', async () => ({ api: 'serveFileOverride' }));
205+
app.get('/api/v\\d+/.*', async () => ({ api: 'apiVersioning' }));
206+
app.get('/users/:userId/files/.+', async (reqCtx) => ({
207+
api: `dynamicRegex${reqCtx.params.userId}`,
208+
}));
209+
app.get('.+', async () => ({ api: 'getAnyRoute' }));
210+
app.route(async () => ({ api: 'catchAllUnmatched' }), {
211+
path: '.*',
212+
method: [HttpVerbs.GET, HttpVerbs.POST],
213+
});
214+
215+
// Act
216+
const result = await app.resolve(createTestEvent(path, method), context);
217+
218+
// Assess
219+
expect(JSON.parse(result.body).api).toEqual(expectedApi);
220+
});
193221
});

0 commit comments

Comments
 (0)