@asteasolutions/zod-to-openapiの問題

@hono/zod-openapi@asteasolutions/zod-to-openapiというライブラリに依存しており、このライブラリはz.lazyをサポートしていません。

Drizzle Zod + lazy · Issue #255 · asteasolutions/zod-to-openapi

回避策として、以下のように.openapiを手動で定義する方法が提案されています。

BaseSearchOrdersFiltersSchema.extend({
    oneOf: z
      .lazy(() => SearchOrdersFiltersSchema.array().optional())
      .openapi({
        type: "array",
        items: {
          type: "object",
        },
      }),

より効率的なワークアラウンド

しかし、上記の方法ではZodでスキーマを定義する恩恵を完全に得られないため、zod-to-json-schemaを使うことができます。

import { serve } from "@hono/node-server";
import { swaggerUI } from "@hono/swagger-ui";
import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
import { z } from "@hono/zod-openapi";
import { zodToJsonSchema } from "zod-to-json-schema";
 
const baseCategorySchema = z.object({ name: z.string() });
 
type Category = z.infer<typeof baseCategorySchema> & {
  subcategories: Category[];
};
 
const categorySchemaRaw: z.ZodType<Category> = baseCategorySchema.extend({
  subcategories: z.lazy(() => categorySchemaRaw.array()),
});
 
const name = "Category";
const jsonSchema = zodToJsonSchema(categorySchemaRaw, {
  basePath: [`#/components/schemas/${name}`],
});
// console.dir(jsonSchema, { depth: null });
const schema = categorySchemaRaw.openapi(name, jsonSchema as {}).openapi({
  example: {
    name: "test1",
    subcategories: [
      {
        name: "test2",
        subcategories: [],
      },
    ],
  },
});
 
const app = new OpenAPIHono({ strict: false });
 
app.openapi(
  createRoute({
    method: "post",
    path: "/test",
    request: { body: { content: { "application/json": { schema } } } },
    responses: { 200: { description: "test" } },
  }),
  (c) => c.text("test"),
);
 
type OpenAPIObjectConfig = Parameters<typeof app.getOpenAPIDocument>[0];
const config: OpenAPIObjectConfig = {
  openapi: "3.0.3",
  info: { version: "0.0.1", title: "Some API" },
};
const pathOpenAPI = "/openapi";
app.doc(pathOpenAPI, config);
 
app.get("/swagger-ui", swaggerUI({ url: pathOpenAPI }));
 
serve({
  async fetch(req, env) {
    return app.fetch(req, env);
  },
  port: 4001,
});
 
// eslint-disable-next-line no-console
console.log(`Server is running on port 4001`);
 
// const schemaOpenAPI = app.getOpenAPIDocument(config);
// console.dir(schemaOpenAPI, { depth: null });

samchungy/zod-openapiに移行する提案

@hono/zod-openapi@samchungy/zod-openapiに依存を移行することでz.lazyをサポートすることができます。

[zod-openapi] use `https://github.com/samchungy/zod-openapi` instead of `https://github.com/asteasolutions/zod-to-openapi` · Issue #881 · honojs/middleware