Skip to content

Commit

Permalink
feat(app): onRequest, onBeforeResponse and onAfterResponse glob…
Browse files Browse the repository at this point in the history
…al hooks (#482)
  • Loading branch information
pi0 authored Aug 2, 2023
1 parent 21ed8d1 commit c266755
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 6 deletions.
36 changes: 32 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export interface AppUse {
export interface AppOptions {
debug?: boolean;
onError?: (error: H3Error, event: H3Event) => any;
onRequest?: (event: H3Event) => void | Promise<void>;
onBeforeResponse?: (
event: H3Event,
response: { body?: unknown }
) => void | Promise<void>;
onAfterResponse?: (
event: H3Event,
response?: { body?: unknown }
) => void | Promise<void>;
}

export interface App {
Expand Down Expand Up @@ -113,6 +122,11 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) {
// Layer path is the path without the prefix
let _layerPath: string;

// Call onRequest hook
if (options.onRequest) {
await options.onRequest(event);
}

for (const layer of stack) {
// 1. Remove prefix from path
if (layer.route.length > 1) {
Expand All @@ -137,23 +151,37 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) {
const val = await layer.handler(event);

// 5. Try to handle return value
const handledVal =
val !== undefined && handleHandlerResponse(event, val, spacing);
if (handledVal !== false) {
return handledVal;
const _response = val === undefined ? undefined : { body: await val };
if (_response !== undefined) {
if (options.onBeforeResponse) {
await options.onBeforeResponse(event, _response);
}
await handleHandlerResponse(event, val, spacing);
if (options.onAfterResponse) {
await options.onAfterResponse(event, _response);
}
return;
}

// Already handled
if (event.handled) {
if (options.onAfterResponse) {
await options.onAfterResponse(event, _response);
}
return;
}
}

if (!event.handled) {
throw createError({
statusCode: 404,
statusMessage: `Cannot find any path matching ${event.path || "/"}.`,
});
}

if (options.onAfterResponse) {
await options.onAfterResponse(event, undefined);
}
});
}

Expand Down
33 changes: 31 additions & 2 deletions test/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Readable, Transform } from "node:stream";
import supertest, { SuperTest, Test } from "supertest";
import { describe, it, expect, beforeEach } from "vitest";
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import {
createApp,
toNodeListener,
Expand All @@ -14,11 +14,26 @@ describe("app", () => {
let app: App;
let request: SuperTest<Test>;

const onRequest = vi.fn();
const onBeforeResponse = vi.fn();
const onAfterResponse = vi.fn();
const onError = vi.fn();

beforeEach(() => {
app = createApp({ debug: true });
app = createApp({
debug: true,
onError,
onRequest,
onBeforeResponse,
onAfterResponse,
});
request = supertest(toNodeListener(app));
});

afterEach(() => {
vi.resetAllMocks();
});

it("can return JSON directly", async () => {
app.use(
"/api",
Expand Down Expand Up @@ -372,4 +387,18 @@ describe("app", () => {
const res = await request.get("/");
expect(res.body).toEqual({ works: 1 });
});

it("calls onRequest and onResponse", async () => {
app.use(() => Promise.resolve("Hello World!"));
await request.get("/foo");

expect(onRequest).toHaveBeenCalledTimes(1);
expect(onRequest.mock.calls[0][0].path).toBe("/foo");

expect(onBeforeResponse).toHaveBeenCalledTimes(1);
expect(onBeforeResponse.mock.calls[0][1].body).toBe("Hello World!");

expect(onAfterResponse).toHaveBeenCalledTimes(1);
expect(onAfterResponse.mock.calls[0][1].body).toBe("Hello World!");
});
});

0 comments on commit c266755

Please sign in to comment.