Skip to content

Commit

Permalink
added a simple router
Browse files Browse the repository at this point in the history
  • Loading branch information
oscarotero committed Dec 13, 2024
1 parent 55d37d0 commit 7e50536
Show file tree
Hide file tree
Showing 9 changed files with 477 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Go to the `v1` branch to see the changelog of Lume 1.
### Added
- New plugin: `json_ld` for generating JSON-LD tags in the output [#453]
- New plugin: `purgecss` to remove unused CSS. [#693]
- New middleware `router`.

## [2.4.3] - 2024-12-11
### Added
Expand Down
99 changes: 99 additions & 0 deletions middlewares/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { posix } from "../deps/path.ts";

import type { Middleware } from "../core/server.ts";

interface RouterParams {
request: Request;
[key: string]: unknown;
}

type RouteHandler<T = RouterParams> = (
params: T,
) => Response | Promise<Response>;

type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";

interface RouteDefinition {
method: HTTPMethod;
pattern: URLPattern;
handler: RouteHandler;
}

export interface Options {
basePath?: string;
strict?: boolean;
}

const baseURL = "http://localhost/";

export default class Router {
routes: RouteDefinition[] = [];
basePath: string;
strict: boolean;

constructor(options?: Options) {
this.basePath = options?.basePath ?? "/";
this.strict = options?.strict ?? true;
}

add(method: HTTPMethod, pattern: string, handler: RouteHandler) {
pattern = posix.join("/", this.basePath, pattern);

if (!this.strict) {
pattern += "{/}?";
}

this.routes.push({
method,
pattern: new URLPattern(pattern, baseURL),
handler,
});
}

get(pattern: string, handler: RouteHandler) {
this.add("GET", pattern, handler);
}

post(pattern: string, handler: RouteHandler) {
this.add("POST", pattern, handler);
}

put(pattern: string, handler: RouteHandler) {
this.add("PUT", pattern, handler);
}

delete(pattern: string, handler: RouteHandler) {
this.add("DELETE", pattern, handler);
}

async exec(request: Request): Promise<Response | undefined> {
for (const { method, pattern, handler } of this.routes) {
if (request.method !== method) {
continue;
}

const url = new URL(request.url);
const result = pattern.exec({
pathname: url.pathname,
baseURL,
});

if (result) {
const data = { ...result.pathname?.groups, request };
return await handler(data);
}
}
}

middleware(): Middleware {
return async (request, next) => {
const response = await this.exec(request);

if (response) {
return response;
}

return await next(request);
};
}
}
2 changes: 2 additions & 0 deletions tests/__snapshots__/cache_busting.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ snapshot[`cache busting middleware 1`] = `
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/v1/styles.css",
status: 200,
statusText: "",
Expand All @@ -18,6 +19,7 @@ snapshot[`cache busting middleware 2`] = `
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/v2873/scripts.js",
status: 200,
statusText: "",
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/no_cache.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ snapshot[`no cache middleware 1`] = `
"cache-control": "no-cache no-store must-revalidate",
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/",
status: 200,
statusText: "",
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/no_cors.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ snapshot[`no_cors middleware 1`] = `
"access-control-allow-origin": "*",
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/",
status: 200,
statusText: "",
Expand Down
222 changes: 222 additions & 0 deletions tests/__snapshots__/router.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
export const snapshot = {};

snapshot[`simple routes 1`] = `
{
body: "Hello World from GET",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes 2`] = `
{
body: "Hello World from GET",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://example.com/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes 3`] = `
{
body: "Hello World from POST",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "POST",
request: "http://localhost/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes 4`] = `
{
body: "Hello World from POST",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "POST",
request: "http://example.com:1234/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes 5`] = `
{
body: "Hello World from PUT",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "PUT",
request: "http://localhost/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes 6`] = `
{
body: "Hello World from DELETE",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "DELETE",
request: "http://localhost/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes with base path 1`] = `
{
body: "Not Found",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello",
status: 404,
statusText: "",
}
`;

snapshot[`simple routes with base path 2`] = `
{
body: "Hello World from GET",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://example.com/api/hello",
status: 200,
statusText: "",
}
`;

snapshot[`simple routes with base path 3`] = `
{
body: "Hello World from GET",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://example.com:1234/api/hello",
status: 200,
statusText: "",
}
`;

snapshot[`routes with parameters 1`] = `
{
body: "Not Found",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello",
status: 404,
statusText: "",
}
`;

snapshot[`routes with parameters 2`] = `
{
body: "Hello oscar",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello/oscar",
status: 200,
statusText: "",
}
`;

snapshot[`routes with parameters 3`] = `
{
body: "Not Found",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello/oscar/",
status: 404,
statusText: "",
}
`;

snapshot[`routes with parameters and strict = false 1`] = `
{
body: "Not Found",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello",
status: 404,
statusText: "",
}
`;

snapshot[`routes with parameters and strict = false 2`] = `
{
body: "Hello oscar / null",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello/oscar",
status: 200,
statusText: "",
}
`;

snapshot[`routes with parameters and strict = false 3`] = `
{
body: "Hello oscar / null",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello/oscar/",
status: 200,
statusText: "",
}
`;

snapshot[`routes with parameters and strict = false 4`] = `
{
body: "Hello oscar / foo",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello/oscar/?query=foo",
status: 200,
statusText: "",
}
`;

snapshot[`routes with parameters and strict = false 5`] = `
{
body: "Hello oscar / foo",
headers: {
"content-type": "text/plain;charset=UTF-8",
},
method: "GET",
request: "http://localhost/hello/oscar?query=foo",
status: 200,
statusText: "",
}
`;
Loading

0 comments on commit 7e50536

Please sign in to comment.