Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): onRequest, onBeforeResponse and onAfterResponse global hooks #482

Merged
merged 4 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 @@
// 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 @@
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);
}

Check warning on line 184 in src/app.ts

View check run for this annotation

Codecov / codecov/patch

src/app.ts#L181-L184

Added lines #L181 - L184 were not covered by tests
});
}

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!");
});
});