UNPKG

hono-zod-openapi

Version:

Alternative Hono middleware for creating OpenAPI documentation from Zod schemas

175 lines (170 loc) 7.45 kB
import { ZodOpenApiResponseObject, ZodOpenApiOperationObject, ZodOpenApiObject, createDocument } from 'zod-openapi'; export { extendZodWithOpenApi } from 'zod-openapi'; import { MiddlewareHandler, Env, ValidationTargets as ValidationTargets$1, Schema, Hono } from 'hono'; import { StatusCode } from 'hono/utils/http-status'; import { z } from 'zod'; type AnyZ = z.ZodType<any, z.ZodTypeDef, any>; type ValidationTarget = 'json' | 'query' | 'param' | 'cookie' | 'header'; type RequestParam = ValidationTarget; type ValidationSchemas = Partial<Record<ValidationTarget, AnyZ>>; type ValidationTargets = Omit<ValidationTargets$1, 'form'>; type ValidationTargetParams<T extends AnyZ> = { /** * Zod schema for the target. */ schema: T; /** * Determines whether the target should be validated or if the schema should only be used for documentation. * @default true */ validate?: boolean; }; type StatusCodePrefix = '1' | '2' | '3' | '4' | '5'; type StatusCodeWithoutMinus1 = Exclude<StatusCode, -1>; type StatusCodeWithWildcards = StatusCodeWithoutMinus1 | `${StatusCodePrefix}XX` | 'default'; /** * Mapping of zod-validator targets to their respective schemas, used both as a source of truth * for validation and for OpenAPI documentation. */ type HonoOpenApiRequestSchemas = Partial<Record<RequestParam, ValidationTargetParams<AnyZ> | AnyZ>>; type HasUndefined<T> = undefined extends T ? true : false; type IsUnknown<T> = unknown extends T ? [T] extends [null] ? false : true : false; type Clean<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K]; } & {}; type ExtractInValues<Schema extends AnyZ, Target extends keyof Omit<ValidationTargets, 'form'>, In = z.input<Schema>> = HasUndefined<In> extends true ? In extends ValidationTargets[Target] ? In : { [K2 in keyof In]?: ValidationTargets[Target][K2]; } : In extends ValidationTargets[Target] ? In : { [K2 in keyof In]: ValidationTargets[Target][K2]; }; type GetValidationSchemas<T extends HonoOpenApiRequestSchemas> = Clean<{ [K in keyof T]: T[K] extends ValidationTargetParams<infer S> ? T[K]['validate'] extends false ? never : S : T[K] extends AnyZ ? T[K] : never; }>; type ToValidatorValues<T extends ValidationSchemas> = { in: Clean<{ [K in keyof ValidationTargets]: IsUnknown<T[K]> extends true ? never : ExtractInValues<Exclude<T[K], undefined>, K>; }>; out: Clean<{ [K in keyof ValidationTargets]: IsUnknown<T[K]> extends true ? never : z.output<Exclude<T[K], undefined>>; }>; }; type Values<T extends HonoOpenApiRequestSchemas> = ToValidatorValues<GetValidationSchemas<T>>; type ZodValidatorFn = <S extends AnyZ, T extends keyof ValidationTargets>(target: T, schema: S) => MiddlewareHandler; interface ReferenceObject { $ref: string; summary?: string; description?: string; } interface SimpleResponseObject extends Pick<ZodOpenApiResponseObject, 'links' | 'headers' | 'ref'> { description?: string; schema: AnyZ; mediaType?: string; } /** * OpenAPI response object, augmented with Zod-based schema. */ type HonoOpenApiResponseObject = ZodOpenApiResponseObject | SimpleResponseObject | AnyZ | ReferenceObject; type HonoOpenApiResponses = Partial<Record<StatusCodeWithWildcards, HonoOpenApiResponseObject>>; /** * OpenAPI operation object, augmented with Zod-based request and response schemas. * See README for exhaustive set of examples. */ interface HonoOpenApiOperation<Req extends HonoOpenApiRequestSchemas = HonoOpenApiRequestSchemas> extends Omit<ZodOpenApiOperationObject, 'requestParams' | 'responses'> { request?: Req; responses: HonoOpenApiResponses; } /** * zod-openapi document without `openapi` property (set to 3.1.0, we do not support lower versions). * @see https://swagger.io/specification/ */ type HonoOpenApiDocument = Omit<ZodOpenApiObject, 'openapi'>; type HonoOpenApiMiddleware = <Req extends HonoOpenApiRequestSchemas, E extends Env, P extends string>(operation: HonoOpenApiOperation<Req>) => MiddlewareHandler<E, P, Values<Req>>; interface DocumentRouteSettings { /** * Whether to add a new route with the OpenAPI document. * @default true */ addRoute?: boolean; /** * Route name under which the OpenAPI document will be available, assuming `settings.addRoute` is `true`. * @default '/doc' */ routeName?: string; } /** * Creates an OpenAPI document from a Hono router based on the routes decorated with `openApi` middleware. * By default it will create a new route at `/doc` that returns the OpenAPI document. * @param router Hono router containing routes decorated with `openApi` middleware * @param document OpenAPI document base. An object with at least `info` property is required * @param [routeSettings] Settings for the route that will serve the OpenAPI document * @returns object representing the OpenAPI document * * @example * ```ts * import { Hono } from 'hono'; * import { z } from 'zod'; * import { createOpenApiDocument, openApi } from 'hono-zod-openapi'; * * export const app = new Hono().get( * '/user', * openApi({ * tags: ['User'], * responses: { * 200: z.object({ hi: z.string() }).openapi({ example: { hi: 'user' } }), * }, * request: { * query: z.object({ id: z.string() }), * }, * }), * (c) => { * const { id } = c.req.valid('query'); * return c.json({ hi: id }, 200); * }, * ); * * createOpenApiDocument(app, { * info: { * title: 'Example API', * version: '1.0.0', * }, * }); * ``` */ declare function createOpenApiDocument<E extends Env, S extends Schema, P extends string>(router: Hono<E, S, P>, document: HonoOpenApiDocument, { addRoute, routeName }?: DocumentRouteSettings): ReturnType<typeof createDocument>; /** * Used internally to create the `openApi` middleware. You can use it if you have a custom `zod-validator` middleware, * e.g. one that has a custom error handler. Otherwise, you probably don't need it and you should just use `openApi` instead. * @param [zodValidator] `@hono/zod-validator`-compatible middleware * @returns `openApi` middleware */ declare function createOpenApiMiddleware(zodValidator?: ZodValidatorFn): HonoOpenApiMiddleware; /** * Hono middleware that documents decorated route. Additionally validates request body/query params/path params etc., * the same way `@hono/zod-validator` does. * * @see HonoOpenApiOperation for more information on how to use it. */ declare const openApi: HonoOpenApiMiddleware; /** * A no-op function, used to ensure proper validator's type inference and provide autocomplete in cases where you don't want to define the spec inline. * @example * * ```ts * const operation = defineOpenApiOperation({ * responses: { * 200: z.object({ name: z.string() }), * }, * request: { * json: z.object({ email: z.string() }), * }, * }); * * const app = new Hono().post('/user', openApi(operation), async (c) => { * const { name } = c.req.valid('json'); * * return c.json({ name }, 200); * }); * ``` */ declare const defineOpenApiOperation: <Req extends HonoOpenApiRequestSchemas>(operation: HonoOpenApiOperation<Req>) => HonoOpenApiOperation<Req>; export { type HonoOpenApiDocument, type HonoOpenApiOperation, type HonoOpenApiRequestSchemas, type HonoOpenApiResponseObject, createOpenApiDocument, createOpenApiMiddleware, defineOpenApiOperation, openApi };