@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 +113 -26
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
# Zod OpenAPI Hono
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
8
|
+
_This is not a real middleware but hosted on this monorepo._
|
|
8
9
|
|
|
9
10
|
## Usage
|
|
10
11
|
|
|
11
|
-
###
|
|
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
|
-
###
|
|
20
|
+
### Basic Usage
|
|
21
|
+
|
|
22
|
+
#### Write your application
|
|
18
23
|
|
|
19
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
+
return c.jsonT(
|
|
104
163
|
{
|
|
105
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
##
|
|
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] :
|
|
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) =>
|
|
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] :
|
|
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) =>
|
|
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
|
}
|