Skip to content

Commit f8032e8

Browse files
committed
feat(routers): add throwing error to URLRouter
`URLRouter` will not grip the error and will try to reslow it as is BREAKING CHANGE: `URLRouter` will not grip the error and throw error as is #29
1 parent cac5f5d commit f8032e8

2 files changed

Lines changed: 38 additions & 162 deletions

File tree

routers.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
isOk,
1717
isResponse,
1818
LRUMap,
19+
partition,
1920
prop,
2021
safeResponse,
2122
Status,
@@ -39,6 +40,9 @@ const MAX_SIZE = 100_0;
3940
* {@link URLRouter} provides routing between HTTP request URLs and handlers.
4041
* Request URL are matched with the `URLPatten API`.
4142
*
43+
* @throws `AggregateError`
44+
* If the routes contain invalid route.
45+
*
4246
* ```ts
4347
* import { URLRouter } from "https://deno.land/x/http_router@$VERSION/mod.ts";
4448
* import { serve } from "https://deno.land/std@$VERSION/http/mod.ts";
@@ -57,8 +61,18 @@ const MAX_SIZE = 100_0;
5761
export const URLRouter: URLRouterConstructor = (routes: URLRoutes, options) => {
5862
const cache = new LRUMap<string, URLCache>(MAX_SIZE);
5963
const iterable = urlPatternRouteFrom(routes);
60-
const entries = Array.from(iterable).map(route2URLPatternRoute).filter(isOk)
61-
.map(prop("value"));
64+
const [oks, errs] = partition(
65+
Array.from(iterable).map(route2URLPatternRoute),
66+
isOk,
67+
);
68+
69+
if (errs.length) {
70+
const errors = errs.map(prop("value"));
71+
72+
throw AggregateError(errors, "Invalid routes.");
73+
}
74+
75+
const entries = oks.map(prop("value"));
6276

6377
function query(url: string): URLCache {
6478
const cached = cache.has(url);
@@ -86,15 +100,17 @@ export const URLRouter: URLRouterConstructor = (routes: URLRoutes, options) => {
86100

87101
return data;
88102
}
89-
const handler: Handler = (request) =>
90-
safeResponse(async () => {
91-
const result = query(request.url);
103+
const handler: Handler = async (request) => {
104+
const result = query(request.url);
92105

93-
if (!result.matched) return result.handler(request);
106+
if (!result.matched) return result.handler(request);
94107

95-
return await process(request, (request) =>
96-
result.handler(request, result.context), options);
97-
}, options?.onError);
108+
return await process(
109+
request,
110+
(request) => result.handler(request, result.context),
111+
options,
112+
);
113+
};
98114

99115
return handler;
100116
};

routers_test.ts

Lines changed: 13 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MethodRouter, URLRouter } from "./routers.ts";
2-
import { anyFunction, describe, expect, fn, it } from "./dev_deps.ts";
2+
import { describe, expect, fn, it } from "./dev_deps.ts";
33
import { Status, STATUS_TEXT } from "./deps.ts";
44

55
const handler = () => new Response();
@@ -24,23 +24,18 @@ describe("URLRouter", () => {
2424
},
2525
);
2626
it(
27-
"should return 500 when route handler has exception",
27+
"should throw error when route handler has exception",
2828
async () => {
2929
const router = URLRouter({
3030
"/": () => {
3131
throw Error("Unknown error");
3232
},
3333
});
34-
const res = await router(
35-
new Request("http://localhost/"),
36-
);
37-
38-
expect(res).toEqualResponse(
39-
new Response(null, {
40-
status: Status.InternalServerError,
41-
statusText: STATUS_TEXT[Status.InternalServerError],
42-
}),
43-
);
34+
try {
35+
await router(new Request("http://localhost/"));
36+
} catch (e) {
37+
expect(e).toEqual(new Error("Unknown error"));
38+
}
4439
},
4540
);
4641

@@ -90,25 +85,6 @@ describe("URLRouter", () => {
9085
},
9186
);
9287

93-
it(
94-
"should return 500 when promise is rejected",
95-
async () => {
96-
const router = URLRouter({
97-
"/": () => Promise.reject(new Response()),
98-
});
99-
const res = await router(
100-
new Request("http://localhost/"),
101-
);
102-
103-
expect(res).toEqualResponse(
104-
new Response(null, {
105-
status: Status.InternalServerError,
106-
statusText: STATUS_TEXT[Status.InternalServerError],
107-
}),
108-
);
109-
},
110-
);
111-
11288
it(
11389
"should pass params when url patten include",
11490
async () => {
@@ -139,17 +115,6 @@ describe("URLRouter", () => {
139115
},
140116
);
141117

142-
it(
143-
"should not throw error when the route path is invalid",
144-
() => {
145-
expect(
146-
URLRouter({
147-
"https://api/:id": () => new Response(),
148-
}),
149-
).toEqual(anyFunction());
150-
},
151-
);
152-
153118
it(
154119
"should match order by priority of registration",
155120
async () => {
@@ -174,13 +139,14 @@ describe("URLRouter", () => {
174139
);
175140

176141
it(
177-
`should not throw error when the path is invalid`,
142+
`should throw error when the path is invalid`,
178143
() => {
179144
expect(
180-
URLRouter({
181-
"+": handler,
182-
}),
183-
).toEqual(anyFunction());
145+
() =>
146+
URLRouter({
147+
"+": handler,
148+
}),
149+
).toThrow(`Invalid routes.`);
184150
},
185151
);
186152

@@ -202,36 +168,6 @@ describe("URLRouter", () => {
202168
expect(mock).toHaveBeenCalledTimes(2);
203169
});
204170

205-
it(
206-
`should catch error and return custom response`,
207-
async () => {
208-
const mock = fn();
209-
210-
const router = URLRouter({
211-
"/": () => {
212-
throw "test";
213-
},
214-
}, {
215-
onError: (error) => {
216-
mock(error);
217-
218-
return new Response("custom error");
219-
},
220-
});
221-
222-
const result = await router(new Request("http://localhost"));
223-
expect(mock).toHaveBeenCalledWith("test");
224-
expect(result).toEqualResponse(
225-
new Response("custom error", {
226-
status: Status.OK,
227-
headers: {
228-
"content-type": "text/plain;charset=UTF-8",
229-
},
230-
}),
231-
);
232-
},
233-
);
234-
235171
it(
236172
`should match when the URLPattern routes`,
237173
async () => {
@@ -266,29 +202,6 @@ describe("URLRouter", () => {
266202
},
267203
);
268204

269-
it(
270-
`should return default error response when throw error in onError`,
271-
async () => {
272-
const router = URLRouter({
273-
"/": () => {
274-
throw "test";
275-
},
276-
}, {
277-
onError: (error) => {
278-
throw error;
279-
},
280-
});
281-
282-
const result = await router(new Request("http://localhost"));
283-
expect(result).toEqualResponse(
284-
new Response(null, {
285-
status: Status.InternalServerError,
286-
statusText: STATUS_TEXT[Status.InternalServerError],
287-
}),
288-
);
289-
},
290-
);
291-
292205
it(
293206
`should call before each on before handler call`,
294207
async () => {
@@ -606,59 +519,6 @@ describe("MethodRouter", () => {
606519
},
607520
);
608521

609-
it(
610-
`should catch error and return custom response`,
611-
async () => {
612-
const mock = fn();
613-
614-
const router = MethodRouter({
615-
GET: () => {
616-
throw "test";
617-
},
618-
}, {
619-
onError: (error) => {
620-
mock(error);
621-
622-
return new Response("custom error");
623-
},
624-
});
625-
626-
const result = await router(new Request("http://localhost"));
627-
expect(mock).toHaveBeenCalledWith("test");
628-
expect(result).toEqualResponse(
629-
new Response("custom error", {
630-
status: Status.OK,
631-
headers: {
632-
"content-type": "text/plain;charset=UTF-8",
633-
},
634-
}),
635-
);
636-
},
637-
);
638-
639-
it(
640-
`should return default error response when throw error in onError`,
641-
async () => {
642-
const router = URLRouter({
643-
"/": () => {
644-
throw "test";
645-
},
646-
}, {
647-
onError: (error) => {
648-
throw error;
649-
},
650-
});
651-
652-
const result = await router(new Request("http://localhost"));
653-
expect(result).toEqualResponse(
654-
new Response(null, {
655-
status: Status.InternalServerError,
656-
statusText: STATUS_TEXT[Status.InternalServerError],
657-
}),
658-
);
659-
},
660-
);
661-
662522
it("should empty body when HEAD response", async () => {
663523
const handler = MethodRouter({
664524
GET: () => {

0 commit comments

Comments
 (0)