hono-zod-openapi
Version:
Alternative Hono middleware for creating OpenAPI documentation from Zod schemas
175 lines (170 loc) • 7.45 kB
TypeScript
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 };