tests passing

This commit is contained in:
2025-07-03 01:43:49 -06:00
parent 0b4f504ba2
commit 0aecd354c7
3 changed files with 121 additions and 53 deletions

View File

@@ -80,10 +80,10 @@ describe("Router", () => {
it("should execute global middleware", async () => { it("should execute global middleware", async () => {
let middlewareExecuted = false; let middlewareExecuted = false;
// router.use(async (_ctx, next) => { router.use(async (_, _ctx, next) => {
// middlewareExecuted = true; middlewareExecuted = true;
// return await next(); return await next();
// }); });
router.route("/test") router.route("/test")
.get(async () => new Response("test")); .get(async () => new Response("test"));
@@ -101,8 +101,7 @@ describe("Router", () => {
router.use("/test", async (_, _ctx, next) => { router.use("/test", async (_, _ctx, next) => {
middlewareExecuted = true; middlewareExecuted = true;
console.log("middlware happened"); return await next();
return await next() ?? new Response("unresolved stack");
}); });
router.route("/test") router.route("/test")
@@ -121,7 +120,7 @@ describe("Router", () => {
router.use("/:version/.*", async (_, ctx, next) => { router.use("/:version/.*", async (_, ctx, next) => {
capturedParams.version = ctx.params.version; capturedParams.version = ctx.params.version;
return await next() ?? new Response("unresolved stack"); return await next();
}); });
router.route("/:version/test") router.route("/:version/test")
@@ -136,16 +135,16 @@ describe("Router", () => {
}); });
it("should execute middleware in correct order", async () => { it("should execute middleware in correct order", async () => {
const order: number[] = [1]; const order: number[] = [];
// router.use(async (_, _ctx, next) => { router.use(async (_, _ctx, next) => {
// order.push(1); order.push(1);
// return await next()?? new Response("unresolved stack"); return await next();
// }); });
router.use("/test", async (_, _ctx, next) => { router.use("/test", async (_, _ctx, next) => {
order.push(2); order.push(2);
return await next() ?? new Response("unresolved stack"); return await next();
}); });
router.route("/test") router.route("/test")
@@ -198,10 +197,10 @@ describe("Router", () => {
const apiRouter = new Router(); const apiRouter = new Router();
let middlewareExecuted = false; let middlewareExecuted = false;
// apiRouter.use(async (_,_ctx, next) => { apiRouter.use(async (_, _ctx, next) => {
// middlewareExecuted = true; middlewareExecuted = true;
// return await next(); return await next();
// }); });
apiRouter.route("/test") apiRouter.route("/test")
.get(async () => new Response("test")); .get(async () => new Response("test"));
@@ -219,10 +218,10 @@ describe("Router", () => {
describe("Context State", () => { describe("Context State", () => {
it("should maintain state across middleware chain", async () => { it("should maintain state across middleware chain", async () => {
// router.use(async (_,ctx, next) => { router.use(async (_, ctx, next) => {
// ctx.state.test = "value"; ctx.state.test = "value";
// return await next(); return await next();
// }); });
router.route("/test") router.route("/test")
.get(async (_, ctx) => { .get(async (_, ctx) => {
@@ -255,9 +254,9 @@ describe("Router", () => {
}); });
it("should handle errors in middleware", async () => { it("should handle errors in middleware", async () => {
// router.use(async () => { router.use(async () => {
// throw new Error("Middleware error"); throw new Error("Middleware error");
// }); });
router.route("/test") router.route("/test")
.get(async () => new Response("test")); .get(async () => new Response("test"));
@@ -269,6 +268,15 @@ describe("Router", () => {
const res = await router.handle(req); const res = await router.handle(req);
assertEquals(res.status, 500); assertEquals(res.status, 500);
}); });
it("should handle no handlers returning a response", async () => {
router.get("/test", async () => undefined as unknown as Response);
const req = new Request("http://localhost/test", {
method: "GET",
});
const res = await router.handle(req);
assertEquals(res.status, 501);
});
}); });
describe("HTTP Methods", () => { describe("HTTP Methods", () => {

View File

@@ -1,11 +1,12 @@
import type { RouterContext as oldContext } from "@bearmetal/router/types"; import type { RouterContext as oldContext } from "@bearmetal/router/types";
import { joinPaths } from "./util/join.ts"; import { joinPaths } from "./util/join.ts";
import { InternalError, NotFound } from "@bearmetal/router/util/response";
type RouterContext = Omit<oldContext, "pattern">; type RouterContext = Omit<oldContext, "pattern">;
type Handler = ( type Handler = (
req: Request, req: Request,
ctx: RouterContext, ctx: RouterContext,
next: () => Promise<void | Response> | null, next: () => Promise<Response>,
) => Promise<Response>; ) => Promise<Response>;
type RouteConfigurator = { type RouteConfigurator = {
get(handler: Handler): RouteConfigurator; get(handler: Handler): RouteConfigurator;
@@ -95,44 +96,93 @@ export class Router {
return configurator; return configurator;
} }
get(path: string, handler: Handler) { get(handler: Handler): void;
get(path: string, handler: Handler): void;
get(path: string | Handler, handler?: Handler): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path); path = fixPath(path);
const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(GET, config).push(handler);
} }
post(path: string, handler: Handler) {
path = fixPath(path);
const config = this.getOrCreateConfig(path); const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(POST, config).push(handler); this.getOrCreateConfigHandlers(GET, config).push(handler!);
} }
put(path: string, handler: Handler) { post(handler: Handler): void;
post(path: string, handler: Handler): void;
post(path: string | Handler, handler?: Handler): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path); path = fixPath(path);
const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(PUT, config).push(handler);
} }
patch(path: string, handler: Handler) {
path = fixPath(path);
const config = this.getOrCreateConfig(path); const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(PATCH, config).push(handler); this.getOrCreateConfigHandlers(POST, config).push(handler!);
} }
delete(path: string, handler: Handler) { put(handler: Handler): void;
put(path: string, handler: Handler): void;
put(path: string | Handler, handler?: Handler): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path); path = fixPath(path);
const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(DELETE, config).push(handler);
} }
options(path: string, handler: Handler) {
path = fixPath(path);
const config = this.getOrCreateConfig(path); const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(OPTIONS, config).push(handler); this.getOrCreateConfigHandlers(PUT, config).push(handler!);
}
patch(handler: Handler): void;
patch(path: string, handler: Handler): void;
patch(path: string | Handler, handler?: Handler): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path);
}
const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(PATCH, config).push(handler!);
}
delete(handler: Handler): void;
delete(path: string, handler: Handler): void;
delete(path: string | Handler, handler?: Handler): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path);
}
const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(DELETE, config).push(handler!);
}
options(handler: Handler): void;
options(path: string, handler: Handler): void;
options(path: string | Handler, handler?: Handler): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path);
}
const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(OPTIONS, config).push(handler!);
} }
use(path: string, handler: Handler | Router) { use(handler: Handler | Router): void;
use(path: string, handler: Handler | Router): void;
use(path: string | Handler | Router, handler?: Handler | Router): void {
if (typeof path !== "string") {
handler = path;
path = "/.*";
} else {
path = fixPath(path); path = fixPath(path);
}
if (handler instanceof Router) { if (handler instanceof Router) {
return this.resolveRouterHandlerStack(path, handler); return this.resolveRouterHandlerStack(path, handler);
} }
const config = this.getOrCreateConfig(path); const config = this.getOrCreateConfig(path);
this.getOrCreateConfigHandlers(_use, config).push(handler); this.getOrCreateConfigHandlers(_use, config).push(handler!);
} }
private getOrCreateConfig(path: string): RouteConfig { private getOrCreateConfig(path: string): RouteConfig {
@@ -142,6 +192,7 @@ export class Router {
handlers: {}, handlers: {},
pattern: new URLPattern({ pathname: path }), pattern: new URLPattern({ pathname: path }),
}; };
this.routes.set(path, config);
} }
return config; return config;
} }
@@ -172,12 +223,16 @@ export class Router {
const url = new URL(req.url); const url = new URL(req.url);
const method = req.method; const method = req.method;
const matchingRoutes = this.findMatchingRoutes(url).filter((r) => const matchingRoutes = this.findMatchingRoutes(url);
Object.hasOwn(r.config.handlers, method) || if (!matchingRoutes.length) return NotFound();
Object.hasOwn(r.config.handlers, _use) const matchingMethods = matchingRoutes.some((r) =>
Object.hasOwn(r.config.handlers, method)
); );
if (!matchingMethods) {
return new Response("Method Not Allowed", { status: 405 });
}
const middlewareStack = matchingRoutes.flatMap((r) => const middlewareStack = matchingRoutes.flatMap((r) =>
r.config.handlers[method] (r.config.handlers[_use] ?? []).concat(r.config.handlers[method] ?? [])
); );
const ctx: RouterContext = { const ctx: RouterContext = {
url, url,
@@ -189,7 +244,11 @@ export class Router {
let index = 0; let index = 0;
const executeMiddleware = async (): Promise<Response> => { const executeMiddleware = async (): Promise<Response> => {
if (index < middlewareStack.length) { if (index < middlewareStack.length) {
const res = await middlewareStack[index++](req, ctx, executeMiddleware); const res = await middlewareStack[index++]?.(
req,
ctx,
executeMiddleware,
);
if (res instanceof Response) return res; if (res instanceof Response) return res;
} }
return new Response("End of stack", { status: 501 }); return new Response("End of stack", { status: 501 });
@@ -197,7 +256,7 @@ export class Router {
try { try {
return await executeMiddleware(); return await executeMiddleware();
} catch { } catch {
return new Response("Internal Server Error", { status: 500 }); return InternalError();
} }
} }

View File

@@ -1,2 +1,3 @@
export const joinPaths = (...args: string[]) => export const joinPaths = (...args: string[]) =>
args.map((a) => a.replace(/\/?\*?$/, "")).join(); args.map((a, i, l) => i === l.length - 1 ? a : a.replace(/\/?\.?\*?$/, ""))
.join("");