Skip to content

Commit bbb7b6d

Browse files
committed
Added includeRouter function to split routes into files
1 parent e155e72 commit bbb7b6d

File tree

4 files changed

+133
-1
lines changed

4 files changed

+133
-1
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,19 @@ export class ErrorHandlerRegistry {
7171

7272
return null;
7373
}
74+
75+
/**
76+
* Merges another {@link ErrorHandlerRegistry | `ErrorHandlerRegistry`} instance into the current instance.
77+
* It takes the handlers from the provided registry and adds them to the current registry.
78+
*
79+
* @param errorHandlerRegistry - The registry instance to merge with the current instance
80+
*/
81+
public merge(errorHandlerRegistry: ErrorHandlerRegistry): void {
82+
for (const [
83+
errorConstructor,
84+
errorHandler,
85+
] of errorHandlerRegistry.#handlers) {
86+
this.register(errorConstructor, errorHandler);
87+
}
88+
}
7489
}

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
ValidationResult,
99
} from '../types/rest.js';
1010
import { ParameterValidationError } from './errors.js';
11-
import type { Route } from './Route.js';
11+
import { Route } from './Route.js';
1212
import { compilePath, validatePathPattern } from './utils.js';
1313

1414
class RouteHandlerRegistry {
@@ -193,6 +193,43 @@ class RouteHandlerRegistry {
193193

194194
return null;
195195
}
196+
197+
/**
198+
* Merges another {@link RouteHandlerRegistry | `RouteHandlerRegistry`} instance into the current instance.
199+
* It takes the static and dynamic routes from the provided registry and adds them to the current registry.
200+
*
201+
* @param routeHandlerRegistry - The registry instance to merge with the current instance
202+
* @param options.prefix - An optional prefix to be added to the paths defined in the router
203+
*/
204+
public merge(
205+
routeHandlerRegistry: RouteHandlerRegistry,
206+
options?: { prefix: Path }
207+
): void {
208+
for (const route of routeHandlerRegistry.#staticRoutes.values()) {
209+
this.register(
210+
new Route(
211+
route.method as HttpMethod,
212+
options?.prefix
213+
? route.path === '/'
214+
? options.prefix
215+
: `${options.prefix}${route.path}`
216+
: route.path,
217+
route.handler,
218+
route.middleware
219+
)
220+
);
221+
}
222+
for (const route of routeHandlerRegistry.#dynamicRoutes) {
223+
this.register(
224+
new Route(
225+
route.method as HttpMethod,
226+
options?.prefix ? `${options.prefix}${route.path}` : route.path,
227+
route.handler,
228+
route.middleware
229+
)
230+
);
231+
}
232+
}
196233
}
197234

198235
export { RouteHandlerRegistry };

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,43 @@ class Router {
551551
handler
552552
);
553553
}
554+
555+
/**
556+
* Merges the routes, context and middleware from the passed router instance into this router instance
557+
*
558+
* @example
559+
* ```typescript
560+
* import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
561+
*
562+
* const todosRouter = new Router();
563+
*
564+
* todosRouter.get('/todos', async () => {
565+
* // List Todos
566+
* });
567+
*
568+
* todosRouter.get('/todos/{todoId}', async () => {
569+
* // Get Todo
570+
* });
571+
*
572+
* const app = new Router();
573+
* app.includeRouter(todosRouter);
574+
*
575+
* export const handler = async (event: unknown, context: Context) => {
576+
* return app.resolve(event, context);
577+
* };
578+
* ```
579+
* @param router - The Router from which to merge the routes, context and middleware
580+
* @param options.prefix - An optional prefix to be added to the paths defined in the router
581+
*/
582+
public includeRouter(router: Router, options?: { prefix: Path }): void {
583+
this.context = {
584+
...this.context,
585+
...router.context,
586+
};
587+
this.routeRegistry.merge(router.routeRegistry, options);
588+
this.errorHandlerRegistry.merge(router.errorHandlerRegistry);
589+
this.middleware.push(...router.middleware);
590+
}
554591
}
555592

556593
export { Router };

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,47 @@ describe('Class: Router - Basic Routing', () => {
144144
expect(JSON.parse(createResult.body).actualPath).toBe('/todos');
145145
expect(JSON.parse(getResult.body).actualPath).toBe('/todos/1');
146146
});
147+
148+
it('routes to the included router when using split routers', async () => {
149+
// Prepare
150+
const baseRouter = new Router();
151+
baseRouter.get('/', async () => ({ api: 'root' }));
152+
baseRouter.get('/version', async () => ({ api: 'listVersions' }));
153+
baseRouter.get('/version/:id', async () => ({ api: 'getVersion' }));
154+
baseRouter.notFound(async () => ({ error: 'NotFound' }));
155+
156+
const todoRouter = new Router();
157+
todoRouter.get('/', async () => ({ api: 'listTodos' }));
158+
todoRouter.post('/create', async () => ({ api: 'createTodo' }));
159+
todoRouter.get('/:id', async () => ({ api: 'getTodo' }));
160+
161+
const taskRouter = new Router();
162+
taskRouter.get('/', async () => ({ api: 'listTasks' }));
163+
taskRouter.post('/create', async () => ({ api: 'createTask' }));
164+
taskRouter.get('/:taskId', async () => ({ api: 'getTask' }));
165+
166+
const app = new Router();
167+
app.includeRouter(baseRouter);
168+
app.includeRouter(todoRouter, { prefix: '/todos' });
169+
app.includeRouter(taskRouter, { prefix: '/todos/:id/tasks' });
170+
171+
// Act & Assess
172+
const testCases = [
173+
['/', 'GET', 'api', 'root'],
174+
['/version', 'GET', 'api', 'listVersions'],
175+
['/version/1', 'GET', 'api', 'getVersion'],
176+
['/todos', 'GET', 'api', 'listTodos'],
177+
['/todos/create', 'POST', 'api', 'createTodo'],
178+
['/todos/1', 'GET', 'api', 'getTodo'],
179+
['/todos/1/tasks', 'GET', 'api', 'listTasks'],
180+
['/todos/1/tasks/create', 'POST', 'api', 'createTask'],
181+
['/todos/1/tasks/1', 'GET', 'api', 'getTask'],
182+
['/non-existent', 'GET', 'error', 'NotFound'],
183+
] as const;
184+
185+
for (const [path, method, key, expected] of testCases) {
186+
const result = await app.resolve(createTestEvent(path, method), context);
187+
expect(JSON.parse(result.body)[key]).toBe(expected);
188+
}
189+
});
147190
});

0 commit comments

Comments
 (0)