@hono/zod-openapi 0.0.1 → 0.1.0

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,27 @@
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).
3
+ **Zod OpenAPI Hono** is extending Hono to support OpenAPI.
4
+ With it, you can validate values and types using [**Zod**](https://zod.dev/) and generate OpenAPI Swagger documentation.
5
+ This is based on [**Zod to OpenAPI**](https://github.com/asteasolutions/zod-to-openapi).
5
6
  For details on creating schemas and defining routes, please refer to this resource.
6
7
 
7
- _This is not a middleware but hosted on this monorepo_
8
+ _This is not a real middleware but hosted on this monorepo._
8
9
 
9
10
  ## Usage
10
11
 
11
- ### Install
12
+ ### Installation
12
13
 
13
- ```
14
+ You can install it via the npm. Should be installed with `hono` and `zod`.
15
+
16
+ ```sh
14
17
  npm i hono zod @hono/zod-openapi
15
18
  ```
16
19
 
17
- ### Write your application
20
+ ### Basic Usage
21
+
22
+ #### Write your application
18
23
 
19
- Define schemas:
24
+ First, define schemas with Zod:
20
25
 
21
26
  ```ts
22
27
  import { z } from '@hono/zod-openapi'
@@ -36,7 +41,7 @@ const ParamsSchema = z.object({
36
41
 
37
42
  const UserSchema = z
38
43
  .object({
39
- id: z.number().openapi({
44
+ id: z.string().openapi({
40
45
  example: 123,
41
46
  }),
42
47
  name: z.string().openapi({
@@ -49,7 +54,7 @@ const UserSchema = z
49
54
  .openapi('User')
50
55
  ```
51
56
 
52
- Create routes:
57
+ Next, create routes:
53
58
 
54
59
  ```ts
55
60
  import { createRoute } from '@hono/zod-openapi'
@@ -69,25 +74,78 @@ const route = createRoute({
69
74
  },
70
75
  description: 'Get the user',
71
76
  },
77
+ },
78
+ })
79
+ ```
80
+
81
+ Finally, create the App:
82
+
83
+ ```ts
84
+ import { OpenAPIHono } from '@hono/zod-openapi'
85
+
86
+ const app = new OpenAPIHono()
87
+
88
+ app.openapi(route, (c) => {
89
+ const { id } = c.req.valid('param')
90
+ return c.jsonT({
91
+ id,
92
+ age: 20,
93
+ name: 'Ultra-man',
94
+ })
95
+ })
96
+
97
+ // OpenAPI document will be served on /doc
98
+ app.doc('/doc', {
99
+ openapi: '3.0.0',
100
+ info: {
101
+ version: '1.0.0',
102
+ title: 'My API',
103
+ },
104
+ })
105
+ ```
106
+
107
+ ### Handling validation errors
108
+
109
+ You can handle the validation errors the following ways.
110
+
111
+ Define the schema:
112
+
113
+ ```ts
114
+ const ErrorSchema = z.object({
115
+ code: z.number().openapi({
116
+ example: 400,
117
+ }),
118
+ message: z.string().openapi({
119
+ example: 'Bad Request',
120
+ }),
121
+ })
122
+ ```
123
+
124
+ Add the response:
125
+
126
+ ```ts
127
+ const route = createRoute({
128
+ method: 'get',
129
+ path: '/users/:id',
130
+ request: {
131
+ params: ParamsSchema,
132
+ },
133
+ responses: {
72
134
  400: {
73
135
  content: {
74
136
  'application/json': {
75
137
  schema: ErrorSchema,
76
138
  },
77
139
  },
78
- description: 'Error!',
140
+ description: 'Return Error!',
79
141
  },
80
142
  },
81
143
  })
82
144
  ```
83
145
 
84
- Create the App:
146
+ Add the hook:
85
147
 
86
148
  ```ts
87
- import { OpenAPIHono } from '@hono/zod-openapi'
88
-
89
- const app = new OpenAPIHono()
90
-
91
149
  app.openapi(
92
150
  route,
93
151
  (c) => {
@@ -98,31 +156,60 @@ app.openapi(
98
156
  name: 'Ultra-man',
99
157
  })
100
158
  },
159
+ // Hook
101
160
  (result, c) => {
102
161
  if (!result.success) {
103
- const res = c.jsonT(
162
+ return c.jsonT(
104
163
  {
105
- ok: false,
164
+ code: 400,
165
+ message: 'Validation Error!',
106
166
  },
107
167
  400
108
168
  )
109
- return res
110
169
  }
111
170
  }
112
171
  )
172
+ ```
113
173
 
114
- app.doc('/doc', {
115
- openapi: '3.0.0',
116
- info: {
117
- version: '1.0.0',
118
- title: 'My API',
119
- },
174
+ ### Middleware
175
+
176
+ You can use Hono's middleware as same as using Hono because Zod OpenAPI is just extending Hono.
177
+
178
+ ```ts
179
+ import { prettyJSON } from 'hono/pretty-json'
180
+
181
+ //...
182
+
183
+ app.use('/doc/*', prettyJSON())
184
+ ```
185
+
186
+ ### RPC-mode
187
+
188
+ Zod OpenAPI Hono supports Hono's RPC-mode. You can create the types for passing Hono Client:
189
+
190
+ ```ts
191
+ import { hc } from 'hono/client'
192
+
193
+ const appRoutes = app.openapi(route, (c) => {
194
+ const data = c.req.valid('json')
195
+ return c.jsonT({
196
+ id: data.id,
197
+ message: 'Success',
198
+ })
120
199
  })
200
+
201
+ const client = hc<typeof appRoutes>('http://localhost:8787/')
121
202
  ```
122
203
 
123
- ## Author
204
+ ## References
205
+
206
+ - [Hono](https://hono.dev/)
207
+ - [Zod](https://zod.dev/)
208
+ - [Zod to OpenAPI](https://github.com/asteasolutions/zod-to-openapi)
209
+
210
+ ## Authors
124
211
 
125
- Yusuke Wada <https://github.com/yusukebe>
212
+ - Yusuke Wada <https://github.com/yusukebe>
126
213
 
127
214
  ## License
128
215
 
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
  };
@@ -41,7 +50,7 @@ type Hook<T, E extends Env, P extends string, O> = (result: {
41
50
  declare class OpenAPIHono<E extends Env = Env, S = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
42
51
  #private;
43
52
  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;
53
+ 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) => Hono<E, Schema<R["method"], R["path"], I["in"], OutputType<R>>, BasePath>;
45
54
  getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
46
55
  doc: (path: string, config: OpenAPIObjectConfig) => void;
47
56
  }
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
  };
@@ -41,7 +50,7 @@ type Hook<T, E extends Env, P extends string, O> = (result: {
41
50
  declare class OpenAPIHono<E extends Env = Env, S = {}, BasePath extends string = '/'> extends Hono<E, S, BasePath> {
42
51
  #private;
43
52
  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;
53
+ 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) => Hono<E, Schema<R["method"], R["path"], I["in"], OutputType<R>>, BasePath>;
45
54
  getOpenAPIDocument: (config: OpenAPIObjectConfig) => openapi3_ts_oas30.OpenAPIObject;
46
55
  doc: (path: string, config: OpenAPIObjectConfig) => void;
47
56
  }
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.0",
4
4
  "description": "A wrapper class of Hono which supports OpenAPI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",