@hono/zod-openapi
Advanced tools
Comparing version
import * as openapi3_ts_oas31 from 'openapi3-ts/oas31'; | ||
import * as _asteasolutions_zod_to_openapi_dist_v3_1_openapi_generator from '@asteasolutions/zod-to-openapi/dist/v3.1/openapi-generator'; | ||
import * as openapi3_ts_oas30 from 'openapi3-ts/oas30'; | ||
import { RouteConfig as RouteConfig$1, ZodMediaTypeObject, OpenAPIRegistry, ZodRequestBody, ZodContentObject, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'; | ||
import * as _asteasolutions_zod_to_openapi_dist_v3_0_openapi_generator from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator'; | ||
import { RouteConfig as RouteConfig$1, ZodRequestBody, ZodContentObject, ZodMediaTypeObject, OpenApiGeneratorV3, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; | ||
export { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; | ||
import { MiddlewareHandler, TypedResponse, Env, ValidationTargets, Context, Input, Handler, Schema, Hono, ToSchema } from 'hono'; | ||
import { MergePath, MergeSchemaPath } from 'hono/types'; | ||
import { StatusCode, InfoStatusCode, SuccessStatusCode, RedirectStatusCode, ClientErrorStatusCode, ServerErrorStatusCode } from 'hono/utils/http-status'; | ||
import { RemoveBlankRecord, SimplifyDeepArray, JSONValue, JSONParsed } from 'hono/utils/types'; | ||
import { ZodError, ZodType, z, ZodSchema } from 'zod'; | ||
import { InfoStatusCode, SuccessStatusCode, RedirectStatusCode, ClientErrorStatusCode, ServerErrorStatusCode, StatusCode } from 'hono/utils/http-status'; | ||
import { SimplifyDeepArray, JSONValue, JSONParsed, RemoveBlankRecord } from 'hono/utils/types'; | ||
import { ZodSchema, z, ZodError, ZodType } from 'zod'; | ||
export { z } from 'zod'; | ||
@@ -75,4 +77,8 @@ | ||
type RouteConfigToTypedResponse<R extends RouteConfig> = { | ||
[Status in DefinedStatusCodes<R>]: undefined extends R['responses'][Status]['content'] ? TypedResponse<{}, ExtractStatusCode<Status>, string> : ReturnJsonOrTextOrResponse<keyof R['responses'][Status]['content'], ExtractContent<R['responses'][Status]['content']>, Status>; | ||
}[DefinedStatusCodes<R>] | ('default' extends keyof R['responses'] ? undefined extends R['responses']['default']['content'] ? TypedResponse<{}, Exclude<StatusCode, ExtractStatusCode<DefinedStatusCodes<R>>>, string> : ReturnJsonOrTextOrResponse<keyof R['responses']['default']['content'], ExtractContent<R['responses']['default']['content']>, Exclude<StatusCode, ExtractStatusCode<DefinedStatusCodes<R>>>> : never); | ||
[Status in DefinedStatusCodes<R>]: R['responses'][Status] extends { | ||
content: infer Content; | ||
} ? undefined extends Content ? never : ReturnJsonOrTextOrResponse<keyof R['responses'][Status]['content'], ExtractContent<R['responses'][Status]['content']>, Status> : TypedResponse<{}, ExtractStatusCode<Status>, string>; | ||
}[DefinedStatusCodes<R>] | ('default' extends keyof R['responses'] ? R['responses']['default'] extends { | ||
content: infer Content; | ||
} ? undefined extends Content ? never : ReturnJsonOrTextOrResponse<keyof Content, ExtractContent<Content>, Exclude<StatusCode, ExtractStatusCode<DefinedStatusCodes<R>>>> : TypedResponse<{}, Exclude<StatusCode, ExtractStatusCode<DefinedStatusCodes<R>>>, string> : never); | ||
type Hook<T, E extends Env, P extends string, R> = (result: { | ||
@@ -124,3 +130,3 @@ target: keyof ValidationTargets; | ||
...Rest | ||
]> : never : never : never : M extends [infer Last] ? Last : never; | ||
]> : never : never : never : M extends [infer Last] ? Last : MiddlewareHandler<Env>; | ||
type RouteMiddlewareParams<R extends RouteConfig> = OfHandlerType<MiddlewareToHandlerType<AsArray<R['middleware']>>>; | ||
@@ -175,3 +181,3 @@ type RouteConfigToEnv<R extends RouteConfig> = RouteMiddlewareParams<R> extends never ? Env : RouteMiddlewareParams<R>['env']; | ||
*/ | ||
openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeBase<R, "headers", "header"> & InputTypeBase<R, "cookies", "cookie"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>({ middleware: routeMiddleware, hide, ...route }: R, handler: Handler<R['middleware'] extends MiddlewareHandler[] | MiddlewareHandler ? RouteMiddlewareParams<R>['env'] & E : E, P, I, R extends { | ||
openapi: <R extends RouteConfig, I extends Input = InputTypeParam<R> & InputTypeQuery<R> & InputTypeHeader<R> & InputTypeCookie<R> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>({ middleware: routeMiddleware, hide, ...route }: R, handler: Handler<R["middleware"] extends MiddlewareHandler[] | MiddlewareHandler ? RouteMiddlewareParams<R>["env"] & E : E, P, I, R extends { | ||
responses: { | ||
@@ -192,7 +198,7 @@ [statusCode: number]: { | ||
}; | ||
} ? MaybePromise<RouteConfigToTypedResponse<R>> | undefined : MaybePromise<RouteConfigToTypedResponse<R>> | MaybePromise<Response> | undefined> | undefined) => OpenAPIHono<E, S & ToSchema<R['method'], MergePath<BasePath, P>, I, RouteConfigToTypedResponse<R>>, BasePath>; | ||
getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject; | ||
getOpenAPI31Document: (config: OpenAPIObjectConfig) => openapi3_ts_oas31.OpenAPIObject; | ||
doc: <P extends string>(path: P, configure: OpenAPIObjectConfigure<E, P>) => OpenAPIHono<E, S & ToSchema<'get', P, {}, {}>, BasePath>; | ||
doc31: <P extends string>(path: P, configure: OpenAPIObjectConfigure<E, P>) => OpenAPIHono<E, S & ToSchema<'get', P, {}, {}>, BasePath>; | ||
} ? MaybePromise<RouteConfigToTypedResponse<R>> | undefined : MaybePromise<RouteConfigToTypedResponse<R>> | MaybePromise<Response> | undefined> | undefined) => OpenAPIHono<E, S & ToSchema<R["method"], MergePath<BasePath, P>, I, RouteConfigToTypedResponse<R>>, BasePath>; | ||
getOpenAPIDocument: (config: OpenAPIObjectConfig) => ReturnType<(config: _asteasolutions_zod_to_openapi_dist_v3_0_openapi_generator.OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject>; | ||
getOpenAPI31Document: (config: OpenAPIObjectConfig) => ReturnType<(config: _asteasolutions_zod_to_openapi_dist_v3_1_openapi_generator.OpenAPIObjectConfigV31) => openapi3_ts_oas31.OpenAPIObject>; | ||
doc: <P extends string>(path: P, configure: OpenAPIObjectConfigure<E, P>) => OpenAPIHono<E, S & ToSchema<"get", P, {}, {}>, BasePath>; | ||
doc31: <P extends string>(path: P, configure: OpenAPIObjectConfigure<E, P>) => OpenAPIHono<E, S & ToSchema<"get", P, {}, {}>, BasePath>; | ||
route<SubPath extends string, SubEnv extends Env, SubSchema extends Schema, SubBasePath extends string>(path: SubPath, app: Hono<SubEnv, SubSchema, SubBasePath>): OpenAPIHono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> & S, BasePath>; | ||
@@ -206,5 +212,5 @@ route<SubPath extends string>(path: SubPath): Hono<E, RemoveBlankRecord<S>, BasePath>; | ||
}>(routeConfig: R) => R & { | ||
getRoutingPath(): RoutingPath<R['path']>; | ||
getRoutingPath(): RoutingPath<R["path"]>; | ||
}; | ||
export { type DeepSimplify, type Hook, type MiddlewareToHandlerType, type OfHandlerType, OpenAPIHono, type OpenAPIHonoOptions, type OpenAPIObjectConfigure, type RouteConfig, type RouteConfigToEnv, type RouteConfigToTypedResponse, type RouteHandler, type RouteHook, createRoute }; |
@@ -1,35 +0,13 @@ | ||
"use strict"; | ||
var __defProp = Object.defineProperty; | ||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
var __getOwnPropNames = Object.getOwnPropertyNames; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __export = (target, all) => { | ||
for (var name in all) | ||
__defProp(target, name, { get: all[name], enumerable: true }); | ||
}; | ||
var __copyProps = (to, from, except, desc) => { | ||
if (from && typeof from === "object" || typeof from === "function") { | ||
for (let key of __getOwnPropNames(from)) | ||
if (!__hasOwnProp.call(to, key) && key !== except) | ||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
} | ||
return to; | ||
}; | ||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
// src/index.ts | ||
var src_exports = {}; | ||
__export(src_exports, { | ||
OpenAPIHono: () => OpenAPIHono, | ||
createRoute: () => createRoute, | ||
extendZodWithOpenApi: () => import_zod_to_openapi.extendZodWithOpenApi, | ||
z: () => import_zod.z | ||
}); | ||
module.exports = __toCommonJS(src_exports); | ||
var import_zod_to_openapi = require("@asteasolutions/zod-to-openapi"); | ||
var import_zod_validator = require("@hono/zod-validator"); | ||
var import_hono = require("hono"); | ||
var import_url = require("hono/utils/url"); | ||
var import_zod = require("zod"); | ||
var OpenAPIHono = class _OpenAPIHono extends import_hono.Hono { | ||
import { | ||
OpenAPIRegistry, | ||
OpenApiGeneratorV3, | ||
OpenApiGeneratorV31, | ||
extendZodWithOpenApi | ||
} from "@asteasolutions/zod-to-openapi"; | ||
import { zValidator } from "@hono/zod-validator"; | ||
import { Hono } from "hono"; | ||
import { mergePath } from "hono/utils/url"; | ||
import { ZodType, z } from "zod"; | ||
var OpenAPIHono = class _OpenAPIHono extends Hono { | ||
openAPIRegistry; | ||
@@ -39,3 +17,3 @@ defaultHook; | ||
super(init); | ||
this.openAPIRegistry = new import_zod_to_openapi.OpenAPIRegistry(); | ||
this.openAPIRegistry = new OpenAPIRegistry(); | ||
this.defaultHook = init?.defaultHook; | ||
@@ -80,15 +58,15 @@ } | ||
if (route.request?.query) { | ||
const validator = (0, import_zod_validator.zValidator)("query", route.request.query, hook); | ||
const validator = zValidator("query", route.request.query, hook); | ||
validators.push(validator); | ||
} | ||
if (route.request?.params) { | ||
const validator = (0, import_zod_validator.zValidator)("param", route.request.params, hook); | ||
const validator = zValidator("param", route.request.params, hook); | ||
validators.push(validator); | ||
} | ||
if (route.request?.headers) { | ||
const validator = (0, import_zod_validator.zValidator)("header", route.request.headers, hook); | ||
const validator = zValidator("header", route.request.headers, hook); | ||
validators.push(validator); | ||
} | ||
if (route.request?.cookies) { | ||
const validator = (0, import_zod_validator.zValidator)("cookie", route.request.cookies, hook); | ||
const validator = zValidator("cookie", route.request.cookies, hook); | ||
validators.push(validator); | ||
@@ -103,7 +81,7 @@ } | ||
const schema = bodyContent[mediaType]["schema"]; | ||
if (!(schema instanceof import_zod.ZodType)) { | ||
if (!(schema instanceof ZodType)) { | ||
continue; | ||
} | ||
if (isJSONContentType(mediaType)) { | ||
const validator = (0, import_zod_validator.zValidator)("json", schema, hook); | ||
const validator = zValidator("json", schema, hook); | ||
if (route.request?.body?.required) { | ||
@@ -125,3 +103,3 @@ validators.push(validator); | ||
if (isFormContentType(mediaType)) { | ||
const validator = (0, import_zod_validator.zValidator)("form", schema, hook); | ||
const validator = zValidator("form", schema, hook); | ||
if (route.request?.body?.required) { | ||
@@ -155,3 +133,3 @@ validators.push(validator); | ||
getOpenAPIDocument = (config) => { | ||
const generator = new import_zod_to_openapi.OpenApiGeneratorV3(this.openAPIRegistry.definitions); | ||
const generator = new OpenApiGeneratorV3(this.openAPIRegistry.definitions); | ||
const document = generator.generateDocument(config); | ||
@@ -161,3 +139,3 @@ return this._basePath ? addBasePathToDocument(document, this._basePath) : document; | ||
getOpenAPI31Document = (config) => { | ||
const generator = new import_zod_to_openapi.OpenApiGeneratorV31(this.openAPIRegistry.definitions); | ||
const generator = new OpenApiGeneratorV31(this.openAPIRegistry.definitions); | ||
const document = generator.generateDocument(config); | ||
@@ -201,3 +179,3 @@ return this._basePath ? addBasePathToDocument(document, this._basePath) : document; | ||
...def.route, | ||
path: (0, import_url.mergePath)( | ||
path: mergePath( | ||
pathForOpenAPI, | ||
@@ -212,3 +190,3 @@ // @ts-expect-error _basePath is private | ||
...def.webhook, | ||
path: (0, import_url.mergePath)( | ||
path: mergePath( | ||
pathForOpenAPI, | ||
@@ -248,7 +226,7 @@ // @ts-expect-error _basePath is private | ||
}; | ||
(0, import_zod_to_openapi.extendZodWithOpenApi)(import_zod.z); | ||
extendZodWithOpenApi(z); | ||
function addBasePathToDocument(document, basePath) { | ||
const updatedPaths = {}; | ||
Object.keys(document.paths).forEach((path) => { | ||
updatedPaths[(0, import_url.mergePath)(basePath.replaceAll(/:([^\/]+)/g, "{$1}"), path)] = document.paths[path]; | ||
updatedPaths[mergePath(basePath.replaceAll(/:([^\/]+)/g, "{$1}"), path)] = document.paths[path]; | ||
}); | ||
@@ -266,4 +244,3 @@ return { | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
export { | ||
OpenAPIHono, | ||
@@ -273,2 +250,2 @@ createRoute, | ||
z | ||
}); | ||
}; |
{ | ||
"name": "@hono/zod-openapi", | ||
"version": "0.19.2", | ||
"version": "0.19.3", | ||
"description": "A wrapper class of Hono which supports OpenAPI.", | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"type": "module", | ||
"module": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
@@ -12,6 +12,7 @@ "files": [ | ||
"scripts": { | ||
"test": "vitest run && vitest --typecheck --run --passWithNoTests", | ||
"build": "tsup ./src/index.ts --format esm,cjs --dts", | ||
"publint": "publint", | ||
"release": "yarn build && yarn test && yarn publint && yarn publish" | ||
"build": "tsup ./src/index.ts", | ||
"prepack": "yarn build", | ||
"publint": "attw --pack && publint", | ||
"typecheck": "tsc -b tsconfig.json", | ||
"test": "vitest" | ||
}, | ||
@@ -21,8 +22,8 @@ "exports": { | ||
"import": { | ||
"types": "./dist/index.d.mts", | ||
"default": "./dist/index.mjs" | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
@@ -38,3 +39,4 @@ } | ||
"type": "git", | ||
"url": "https://github.com/honojs/middleware.git" | ||
"url": "git+https://github.com/honojs/middleware.git", | ||
"directory": "packages/zod-openapi" | ||
}, | ||
@@ -47,8 +49,7 @@ "homepage": "https://github.com/honojs/middleware", | ||
"devDependencies": { | ||
"@cloudflare/workers-types": "^4.20240117.0", | ||
"hono": "^4.6.10", | ||
"jest": "^29.7.0", | ||
"tsup": "^8.0.1", | ||
"typescript": "^5.4.4", | ||
"vitest": "^1.4.0", | ||
"@arethetypeswrong/cli": "^0.17.4", | ||
"publint": "^0.3.9", | ||
"tsup": "^8.4.0", | ||
"typescript": "^5.8.2", | ||
"vitest": "^3.0.8", | ||
"yaml": "^2.4.3", | ||
@@ -59,3 +60,3 @@ "zod": "^3.22.1" | ||
"@asteasolutions/zod-to-openapi": "^7.1.0", | ||
"@hono/zod-validator": "^0.4.1" | ||
"@hono/zod-validator": "workspace:^" | ||
}, | ||
@@ -62,0 +63,0 @@ "engines": { |
# Zod OpenAPI Hono | ||
[](https://codecov.io/github/honojs/middleware) | ||
**Zod OpenAPI Hono** is an extended Hono class that supports OpenAPI. With it, you can validate values and types using [**Zod**](https://zod.dev/) and generate OpenAPI Swagger documentation. This is based on [**Zod to OpenAPI**](https://github.com/asteasolutions/zod-to-openapi) (thanks a lot!). For details on creating schemas and defining routes, please refer to [the "Zod to OpenAPI" resource](https://github.com/asteasolutions/zod-to-openapi). | ||
@@ -473,2 +475,14 @@ | ||
When using the `.route()` method to mount a child OpenAPIHono app that uses path parameters, you should use the Hono *:param* syntax in the parent route path, rather than the OpenAPI *{param}* syntax: | ||
``` | ||
const bookActionsApp = new OpenAPIHono() | ||
... | ||
// ❌ Incorrect: This will not match the route | ||
app.route('/books/{bookId}', bookActionsApp) | ||
// ✅ Using Hono parameter syntax | ||
app.route('/books/:bookId', bookActionsApp) | ||
``` | ||
### Header keys | ||
@@ -475,0 +489,0 @@ |
56450
2.68%7
-12.5%718
0.56%513
2.81%Yes
NaN- Removed