/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ import type { ResponseConfig, RouteConfig, ZodContentObject, ZodRequestBody, } from '@asteasolutions/zod-to-openapi' import { OpenApiGeneratorV3, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi' import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi' import type { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator' import { zValidator } from '@hono/zod-validator' import { Hono } from 'hono' import type { Context, Input, Schema, TypedResponse } from 'hono' import type { Env, Handler, MiddlewareHandler } from 'hono' import type { AnyZodObject, ZodSchema, ZodError } from 'zod' import { z, ZodType } from 'zod' type RequestTypes = { body?: ZodRequestBody params?: AnyZodObject query?: AnyZodObject cookies?: AnyZodObject // not support headers?: AnyZodObject | ZodType[] // not support } type IsJson = T extends string ? T extends `application/json${infer _Rest}` ? 'json' : never : never type IsForm = T extends string ? T extends | `multipart/form-data${infer _Rest}` | `application/x-www-form-urlencoded${infer _Rest}` ? 'form' : never : never type RequestPart = Part extends keyof R['request'] ? R['request'][Part] : {} type InputTypeBase< R extends RouteConfig, Part extends string, Type extends string > = R['request'] extends RequestTypes ? RequestPart extends AnyZodObject ? { in: { [K in Type]: z.input> } out: { [K in Type]: z.input> } } : {} : {} type InputTypeJson = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsJson extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema ? { in: { json: z.input< R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] > } out: { json: z.input< R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] > } } : {} : {} : {} : {} type InputTypeForm = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsForm extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema ? { in: { form: z.input< R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] > } out: { form: z.input< R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] > } } : {} : {} : {} : {} type InputTypeParam = InputTypeBase type InputTypeQuery = InputTypeBase type OutputType = R['responses'] extends Record ? C extends ResponseConfig ? C['content'] extends ZodContentObject ? IsJson extends never ? {} : C['content'][keyof C['content']]['schema'] extends ZodSchema ? z.infer : {} : {} : {} : {} type Hook = ( result: | { success: true data: T } | { success: false error: ZodError }, c: Context ) => TypedResponse | Promise> | void export class OpenAPIHono extends Hono< E, S, BasePath > { #registry: OpenAPIRegistry constructor() { super() this.#registry = new OpenAPIRegistry() } openapi = < R extends RouteConfig, I extends Input = InputTypeParam & InputTypeQuery & InputTypeForm & InputTypeJson >( route: R, handler: Handler>, hook?: Hook> ): Hono>, BasePath> => { this.#registry.registerPath(route) const validators: MiddlewareHandler[] = [] if (route.request?.query) { const validator = zValidator('query', route.request.query as any, hook as any) validators.push(validator as any) } if (route.request?.params) { const validator = zValidator('param', route.request.params as any, hook as any) validators.push(validator as any) } const bodyContent = route.request?.body?.content if (bodyContent) { for (const mediaType of Object.keys(bodyContent)) { if (mediaType.startsWith('application/json')) { const schema = bodyContent[mediaType]['schema'] if (schema instanceof ZodType) { const validator = zValidator('json', schema as any, hook as any) validators.push(validator as any) } } if ( mediaType.startsWith('multipart/form-data') || mediaType.startsWith('application/x-www-form-urlencoded') ) { const schema = bodyContent[mediaType]['schema'] if (schema instanceof ZodType) { const validator = zValidator('form', schema as any, hook as any) validators.push(validator as any) } } } } this.on([route.method], route.path, ...validators, handler) return this } getOpenAPIDocument = (config: OpenAPIObjectConfig) => { const generator = new OpenApiGeneratorV3(this.#registry.definitions) const document = generator.generateDocument(config) return document } doc = (path: string, config: OpenAPIObjectConfig) => { this.get(path, (c) => { const document = this.getOpenAPIDocument(config) return c.json(document) }) } } export const createRoute =

& { path: P }>( routeConfig: R ) => routeConfig extendZodWithOpenApi(z) export { z }