@hono/zod-openapi 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,29 @@
1
1
  # Zod OpenAPI Hono
2
2
 
3
- A wrapper class for Hono that supports OpenAPI. With it, you can validate values and types using [Zod](https://zod.dev/) and generate OpenAPI Swagger documentation.
4
- This is based on [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi).
5
- For details on creating schemas and defining routes, please refer to this resource.
3
+ **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).
6
4
 
7
- _This is not a middleware but hosted on this monorepo_
5
+ _Note: This is not standalone middleware but is hosted on the monorepo "[github.com/honojs/middleware](https://github.com/honojs/middleware)"._
6
+
7
+ ## Limitations
8
+
9
+ - Currently, it does not support validation of _headers_ and _cookies_.
10
+ - An instance of Zod OpenAPI Hono cannot be used as a "subApp" in conjunction with `rootApp.route('/api', subApp)`.
8
11
 
9
12
  ## Usage
10
13
 
11
- ### Install
14
+ ### Installation
12
15
 
13
- ```
16
+ You can install it via npm. It should be installed alongside `hono` and `zod`.
17
+
18
+ ```sh
14
19
  npm i hono zod @hono/zod-openapi
15
20
  ```
16
21
 
17
- ### Write your application
22
+ ### Basic Usage
23
+
24
+ #### Setting up your application
18
25
 
19
- Define schemas:
26
+ First, define your schemas with Zod. The `z` object should be imported from `@hono/zod-openapi`:
20
27
 
21
28
  ```ts
22
29
  import { z } from '@hono/zod-openapi'
@@ -36,8 +43,8 @@ const ParamsSchema = z.object({
36
43
 
37
44
  const UserSchema = z
38
45
  .object({
39
- id: z.number().openapi({
40
- example: 123,
46
+ id: z.string().openapi({
47
+ example: '123',
41
48
  }),
42
49
  name: z.string().openapi({
43
50
  example: 'John Doe',
@@ -49,7 +56,7 @@ const UserSchema = z
49
56
  .openapi('User')
50
57
  ```
51
58
 
52
- Create routes:
59
+ Next, create a route:
53
60
 
54
61
  ```ts
55
62
  import { createRoute } from '@hono/zod-openapi'
@@ -67,27 +74,86 @@ const route = createRoute({
67
74
  schema: UserSchema,
68
75
  },
69
76
  },
70
- description: 'Get the user',
77
+ description: 'Retrieve the user',
71
78
  },
79
+ },
80
+ })
81
+ ```
82
+
83
+ Finally, set up the app:
84
+
85
+ ```ts
86
+ import { OpenAPIHono } from '@hono/zod-openapi'
87
+
88
+ const app = new OpenAPIHono()
89
+
90
+ app.openapi(route, (c) => {
91
+ const { id } = c.req.valid('param')
92
+ return c.jsonT({
93
+ id,
94
+ age: 20,
95
+ name: 'Ultra-man',
96
+ })
97
+ })
98
+
99
+ // The OpenAPI documentation will be available at /doc
100
+ app.doc('/doc', {
101
+ openapi: '3.0.0',
102
+ info: {
103
+ version: '1.0.0',
104
+ title: 'My API',
105
+ },
106
+ })
107
+ ```
108
+
109
+ You can start your app just like you would with Hono. For Cloudflare Workers and Bun, use this entry point:
110
+
111
+ ```ts
112
+ export default app
113
+ ```
114
+
115
+ ### Handling Validation Errors
116
+
117
+ Validation errors can be handled as follows:
118
+
119
+ First, define the error schema:
120
+
121
+ ```ts
122
+ const ErrorSchema = z.object({
123
+ code: z.number().openapi({
124
+ example: 400,
125
+ }),
126
+ message: z.string().openapi({
127
+ example: 'Bad Request',
128
+ }),
129
+ })
130
+ ```
131
+
132
+ Then, add the error response:
133
+
134
+ ```ts
135
+ const route = createRoute({
136
+ method: 'get',
137
+ path: '/users/:id',
138
+ request: {
139
+ params: ParamsSchema,
140
+ },
141
+ responses: {
72
142
  400: {
73
143
  content: {
74
144
  'application/json': {
75
145
  schema: ErrorSchema,
76
146
  },
77
147
  },
78
- description: 'Error!',
148
+ description: 'Returns an error',
79
149
  },
80
150
  },
81
151
  })
82
152
  ```
83
153
 
84
- Create the App:
154
+ Finally, add the hook:
85
155
 
86
156
  ```ts
87
- import { OpenAPIHono } from '@hono/zod-openapi'
88
-
89
- const app = new OpenAPIHono()
90
-
91
157
  app.openapi(
92
158
  route,
93
159
  (c) => {
@@ -98,31 +164,60 @@ app.openapi(
98
164
  name: 'Ultra-man',
99
165
  })
100
166
  },
167
+ // Hook
101
168
  (result, c) => {
102
169
  if (!result.success) {
103
- const res = c.jsonT(
170
+ return c.jsonT(
104
171
  {
105
- ok: false,
172
+ code: 400,
173
+ message: 'Validation Error',
106
174
  },
107
175
  400
108
176
  )
109
- return res
110
177
  }
111
178
  }
112
179
  )
180
+ ```
113
181
 
114
- app.doc('/doc', {
115
- openapi: '3.0.0',
116
- info: {
117
- version: '1.0.0',
118
- title: 'My API',
119
- },
182
+ ### Middleware
183
+
184
+ Zod OpenAPI Hono is an extension of Hono, so you can use Hono's middleware in the same way:
185
+
186
+ ```ts
187
+ import { prettyJSON } from 'hono/pretty-json'
188
+
189
+ //...
190
+
191
+ app.use('/doc/*', prettyJSON())
192
+ ```
193
+
194
+ ### RPC Mode
195
+
196
+ Zod OpenAPI Hono supports Hono's RPC mode. You can define types for the Hono Client as follows:
197
+
198
+ ```ts
199
+ import { hc } from 'hono/client'
200
+
201
+ const appRoutes = app.openapi(route, (c) => {
202
+ const data = c.req.valid('json')
203
+ return c.jsonT({
204
+ id: data.id,
205
+ message: 'Success',
206
+ })
120
207
  })
208
+
209
+ const client = hc<typeof appRoutes>('http://localhost:8787/')
121
210
  ```
122
211
 
123
- ## Author
212
+ ## References
213
+
214
+ - [Hono](https://hono.dev/)
215
+ - [Zod](https://zod.dev/)
216
+ - [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi)
217
+
218
+ ## Authors
124
219
 
125
- Yusuke Wada <https://github.com/yusukebe>
220
+ - Yusuke Wada <https://github.com/yusukebe>
126
221
 
127
222
  ## License
128
223
 
package/dist/index.cjs CHANGED
@@ -83,7 +83,7 @@ var OpenAPIHono = class extends import_hono.Hono {
83
83
  }
84
84
  }
85
85
  }
86
- this.on([route.method], route.path, ...validators, handler);
86
+ this.on([route.method], route.path.replace(/\/{(.+)}/, "/:$1"), ...validators, handler);
87
87
  return this;
88
88
  };
89
89
  this.getOpenAPIDocument = (config) => {
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
2
2
  import { RouteConfig, ZodRequestBody, ZodContentObject, ResponseConfig } from '@asteasolutions/zod-to-openapi';
3
3
  import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator';
4
- import { Env, Hono, Input, Handler, Context, TypedResponse } from 'hono';
4
+ import { Env, Hono, Input, Handler, Schema, Context, TypedResponse } from 'hono';
5
5
  import { AnyZodObject, z, ZodSchema, ZodError, ZodType } from 'zod';
6
6
  export { z } from 'zod';
7
7
 
@@ -14,18 +14,27 @@ type RequestTypes = {
14
14
  };
15
15
  type IsJson<T> = T extends string ? T extends `application/json${infer _Rest}` ? 'json' : never : never;
16
16
  type IsForm<T> = T extends string ? T extends `multipart/form-data${infer _Rest}` | `application/x-www-form-urlencoded${infer _Rest}` ? 'form' : never : never;
17
- type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request'] ? R['request'][Part] : never;
17
+ type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request'] ? R['request'][Part] : {};
18
18
  type InputTypeBase<R extends RouteConfig, Part extends string, Type extends string> = R['request'] extends RequestTypes ? RequestPart<R, Part> extends AnyZodObject ? {
19
+ in: {
20
+ [K in Type]: z.input<RequestPart<R, Part>>;
21
+ };
19
22
  out: {
20
23
  [K in Type]: z.input<RequestPart<R, Part>>;
21
24
  };
22
25
  } : {} : {};
23
26
  type InputTypeJson<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsJson<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
27
+ in: {
28
+ json: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
29
+ };
24
30
  out: {
25
31
  json: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
26
32
  };
27
33
  } : {} : {} : {} : {};
28
34
  type InputTypeForm<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsForm<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
35
+ in: {
36
+ form: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
37
+ };
29
38
  out: {
30
39
  form: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
31
40
  };
@@ -38,10 +47,11 @@ type Hook<T, E extends Env, P extends string, O> = (result: {
38
47
  success: false;
39
48
  error: ZodError;
40
49
  }, c: Context<E, P>) => TypedResponse<O> | Promise<TypedResponse<T>> | void;
50
+ type ConvertPathType<T extends string> = T extends `${infer _}/{${infer Param}}${infer _}` ? `/:${Param}` : T;
41
51
  declare class OpenAPIHono<E extends Env = Env, S = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
42
52
  #private;
43
53
  constructor();
44
- openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeForm<R> & InputTypeJson<R>>(route: R, handler: Handler<E, R["path"], I, OutputType<R>>, hook?: Hook<I, E, R["path"], OutputType<R>> | undefined) => this;
54
+ openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>(route: R, handler: Handler<E, P, I, OutputType<R>>, hook?: Hook<I, E, P, OutputType<R>> | undefined) => Hono<E, Schema<R["method"], P, I["in"], OutputType<R>>, BasePath>;
45
55
  getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
46
56
  doc: (path: string, config: OpenAPIObjectConfig) => void;
47
57
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
2
2
  import { RouteConfig, ZodRequestBody, ZodContentObject, ResponseConfig } from '@asteasolutions/zod-to-openapi';
3
3
  import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator';
4
- import { Env, Hono, Input, Handler, Context, TypedResponse } from 'hono';
4
+ import { Env, Hono, Input, Handler, Schema, Context, TypedResponse } from 'hono';
5
5
  import { AnyZodObject, z, ZodSchema, ZodError, ZodType } from 'zod';
6
6
  export { z } from 'zod';
7
7
 
@@ -14,18 +14,27 @@ type RequestTypes = {
14
14
  };
15
15
  type IsJson<T> = T extends string ? T extends `application/json${infer _Rest}` ? 'json' : never : never;
16
16
  type IsForm<T> = T extends string ? T extends `multipart/form-data${infer _Rest}` | `application/x-www-form-urlencoded${infer _Rest}` ? 'form' : never : never;
17
- type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request'] ? R['request'][Part] : never;
17
+ type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request'] ? R['request'][Part] : {};
18
18
  type InputTypeBase<R extends RouteConfig, Part extends string, Type extends string> = R['request'] extends RequestTypes ? RequestPart<R, Part> extends AnyZodObject ? {
19
+ in: {
20
+ [K in Type]: z.input<RequestPart<R, Part>>;
21
+ };
19
22
  out: {
20
23
  [K in Type]: z.input<RequestPart<R, Part>>;
21
24
  };
22
25
  } : {} : {};
23
26
  type InputTypeJson<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsJson<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
27
+ in: {
28
+ json: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
29
+ };
24
30
  out: {
25
31
  json: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
26
32
  };
27
33
  } : {} : {} : {} : {};
28
34
  type InputTypeForm<R extends RouteConfig> = R['request'] extends RequestTypes ? R['request']['body'] extends ZodRequestBody ? R['request']['body']['content'] extends ZodContentObject ? IsForm<keyof R['request']['body']['content']> extends never ? {} : R['request']['body']['content'][keyof R['request']['body']['content']]['schema'] extends ZodSchema<any> ? {
35
+ in: {
36
+ form: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
37
+ };
29
38
  out: {
30
39
  form: z.input<R['request']['body']['content'][keyof R['request']['body']['content']]['schema']>;
31
40
  };
@@ -38,10 +47,11 @@ type Hook<T, E extends Env, P extends string, O> = (result: {
38
47
  success: false;
39
48
  error: ZodError;
40
49
  }, c: Context<E, P>) => TypedResponse<O> | Promise<TypedResponse<T>> | void;
50
+ type ConvertPathType<T extends string> = T extends `${infer _}/{${infer Param}}${infer _}` ? `/:${Param}` : T;
41
51
  declare class OpenAPIHono<E extends Env = Env, S = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
42
52
  #private;
43
53
  constructor();
44
- openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeForm<R> & InputTypeJson<R>>(route: R, handler: Handler<E, R["path"], I, OutputType<R>>, hook?: Hook<I, E, R["path"], OutputType<R>> | undefined) => this;
54
+ openapi: <R extends RouteConfig, I extends Input = InputTypeBase<R, "params", "param"> & InputTypeBase<R, "query", "query"> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>(route: R, handler: Handler<E, P, I, OutputType<R>>, hook?: Hook<I, E, P, OutputType<R>> | undefined) => Hono<E, Schema<R["method"], P, I["in"], OutputType<R>>, BasePath>;
45
55
  getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
46
56
  doc: (path: string, config: OpenAPIObjectConfig) => void;
47
57
  }
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ var OpenAPIHono = class extends Hono {
58
58
  }
59
59
  }
60
60
  }
61
- this.on([route.method], route.path, ...validators, handler);
61
+ this.on([route.method], route.path.replace(/\/{(.+)}/, "/:$1"), ...validators, handler);
62
62
  return this;
63
63
  };
64
64
  this.getOpenAPIDocument = (config) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/zod-openapi",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "A wrapper class of Hono which supports OpenAPI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",