diff --git a/src/event/utils.ts b/src/event/utils.ts index 4ec7fb57..0a69fdb3 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -8,6 +8,9 @@ import type { _ResponseMiddleware, } from "../types"; import { hasProp } from "../utils/internal/object"; +import { validateData } from "../utils/internal/validate"; +import { readBody } from "../utils/body"; +import { getQuery } from "../utils/request"; import type { H3Event } from "./event"; type _EventHandlerHooks = { @@ -16,12 +19,17 @@ type _EventHandlerHooks = { }; export function defineEventHandler< - Request extends EventHandlerRequest = EventHandlerRequest, + Body extends EventHandlerRequest["body"] = EventHandlerRequest["body"], + Query extends EventHandlerRequest["query"] = EventHandlerRequest["query"], + Request extends EventHandlerRequest = EventHandlerRequest< + Body, + Query + >, Response = EventHandlerResponse, >( handler: | EventHandler - | EventHandlerObject, + | EventHandlerObject, ): EventHandler; // TODO: remove when appropriate // This signature provides backwards compatibility with previous signature where first generic was return type @@ -38,12 +46,17 @@ export function defineEventHandler< Request extends EventHandlerRequest ? Response : Request >; export function defineEventHandler< - Request extends EventHandlerRequest, + Body extends EventHandlerRequest["body"] = EventHandlerRequest["body"], + Query extends EventHandlerRequest["query"] = EventHandlerRequest["query"], + Request extends EventHandlerRequest = EventHandlerRequest< + Body, + Query + >, Response = EventHandlerResponse, >( handler: | EventHandler - | EventHandlerObject, + | EventHandlerObject, ): EventHandler { // Function Syntax if (typeof handler === "function") { @@ -55,7 +68,15 @@ export function defineEventHandler< onRequest: _normalizeArray(handler.onRequest), onBeforeResponse: _normalizeArray(handler.onBeforeResponse), }; - const _handler: EventHandler = (event) => { + const _handler: EventHandler = async (event) => { + if (handler.bodyValidator) { + const body = await readBody(event); + await validateData(body, handler.bodyValidator); + } + if (handler.queryValidator) { + const query = getQuery(event); + await validateData(query, handler.queryValidator); + } return _callHandler(event, handler.handler, _hooks); }; _handler.__is_handler__ = true; diff --git a/src/types.ts b/src/types.ts index 7116c475..61e03ded 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import type { Hooks as WSHooks } from "crossws"; import type { H3Event } from "./event"; import type { Session } from "./utils/session"; import type { RouteNode } from "./router"; +import type { ValidateFunction } from "./types"; export type { ValidateFunction, @@ -51,9 +52,12 @@ export interface H3EventContext extends Record { export type EventHandlerResponse = T | Promise; -export interface EventHandlerRequest { - body?: any; // TODO: Default to unknown in next major version - query?: QueryObject; +export interface EventHandlerRequest< + Body = any, // TODO: Default to unknown in next major version + Query extends QueryObject | undefined = QueryObject | undefined, +> { + body?: Body; + query?: Query; routerParams?: Record; } @@ -92,7 +96,12 @@ export type _ResponseMiddleware< ) => void | Promise; export type EventHandlerObject< - Request extends EventHandlerRequest = EventHandlerRequest, + Body extends EventHandlerRequest["body"] = EventHandlerRequest["body"], + Query extends EventHandlerRequest["query"] = EventHandlerRequest["query"], + Request extends EventHandlerRequest = EventHandlerRequest< + Body, + Query + >, Response extends EventHandlerResponse = EventHandlerResponse, > = { onRequest?: _RequestMiddleware | _RequestMiddleware[]; @@ -101,6 +110,8 @@ export type EventHandlerObject< | _ResponseMiddleware[]; /** @experimental */ websocket?: Partial; + bodyValidator?: ValidateFunction; + queryValidator?: ValidateFunction; handler: EventHandler; }; diff --git a/src/utils/body.ts b/src/utils/body.ts index 3f71b16c..eb0ebf38 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -142,7 +142,7 @@ export function readRawBody( export async function readBody< T, Event extends H3Event = H3Event, - _T = InferEventInput<"body", Event, T>, + _T = Exclude, undefined>, >(event: Event, options: { strict?: boolean } = {}): Promise<_T> { const request = event.node.req as InternalRequest; if (hasProp(request, ParsedBodySymbol)) { diff --git a/test/types.test-d.ts b/test/types.test-d.ts index 9160b0ef..3f97365b 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -34,6 +34,7 @@ describe("types", () => { foo: string; }>(); }); + it("return type (inferred)", () => { const handler = eventHandler(() => { return { @@ -45,7 +46,7 @@ describe("types", () => { }); it("return type (simple generic)", () => { - const handler = eventHandler(() => { + const handler = eventHandler(() => { return ""; }); const response = handler({} as H3Event); @@ -77,10 +78,19 @@ describe("types", () => { expectTypeOf(body).not.toBeAny(); expectTypeOf(body).toEqualTypeOf<{ id: string }>(); }); + + eventHandler({ + bodyValidator: (body: unknown) => body as { id: string }, + handler: async (event) => { + const body = await readBody(event); + expectTypeOf(body).not.toBeAny(); + expectTypeOf(body).toEqualTypeOf<{ id: string }>(); + } + }); }); it("typed via event handler", () => { - eventHandler<{ body: { id: string } }>(async (event) => { + eventHandler<{ id: string }>(async (event) => { const body = await readBody(event); expectTypeOf(body).not.toBeAny(); expectTypeOf(body).toEqualTypeOf<{ id: string }>(); @@ -107,15 +117,24 @@ describe("types", () => { it("typed via validator", () => { eventHandler(async (event) => { - const validator = (body: unknown) => body as { id: string }; - const body = await getValidatedQuery(event, validator); - expectTypeOf(body).not.toBeAny(); - expectTypeOf(body).toEqualTypeOf<{ id: string }>(); + const validator = (query: unknown) => query as { id: string }; + const query = await getValidatedQuery(event, validator); + expectTypeOf(query).not.toBeAny(); + expectTypeOf(query).toEqualTypeOf<{ id: string }>(); + }); + + eventHandler({ + queryValidator: (query: unknown) => query as { id: string }, + handler: (event) => { + const query = getQuery(event); + expectTypeOf(query).not.toBeAny(); + expectTypeOf(query).toEqualTypeOf<{ id: string }>(); + } }); }); it("typed via event handler", () => { - eventHandler<{ query: { id: string } }>((event) => { + eventHandler((event) => { const query = getQuery(event); expectTypeOf(query).not.toBeAny(); expectTypeOf(query).toEqualTypeOf<{ id: string }>();