-
-
Notifications
You must be signed in to change notification settings - Fork 579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can we generate an openapi schema from any existing hono app? #3069
Comments
Hi @thenbe As far as I know, we can't reflect the type information of Hono RPC to a runtime function. As you know, Hono RPC is just for TypeScript typings. But to enable handling actual values like Swagger docs, I've made the Zod OpenAPI. I'm not sure, but we may emit type definitions to a file and use it later, but it may not be possible. |
Hi @yusukebe To confirm my understanding of how import { serve } from '@hono/node-server';
-import { zValidator } from '@hono/zod-validator';
+import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
-const app = new Hono();
+const app = new OpenAPIHono();
+const route_definition = createRoute({
+ method: 'get',
+ path: '/hello',
+ request: {
+ query: z.object({
+ name: z
+ .string()
+ .min(1)
+ .openapi({
+ param: {
+ name: 'name',
+ in: 'query',
+ },
+ example: 'Alice',
+ }),
+ }),
+ },
+ responses: {
+ 202: {
+ description: 'Success',
+ content: {
+ 'application/json': {
+ schema: z.object({
+ message: z.string().openapi({
+ example: 'Hello Name!',
+ }),
+ }),
+ },
+ },
+ },
+ 400: {
+ description: 'Failure',
+ content: {
+ 'application/json': {
+ schema: z.object({
+ error_title: z.string().openapi({
+ example: 'Bad input',
+ }),
+ }),
+ },
+ },
+ },
+ },
+});
const route = app
- .get(
- '/hello',
- zValidator('query', z.object({ name: z.string() })),
+ .openapi(
+ route_definition,
(c) => {
const { name } = c.req.valid('query');
if (name === '') {
return c.json({ error_title: 'Invalid name' }, 400);
}
return c.json({ message: `Hello! ${name}` }, 202);
},
);
app.doc('/doc', {
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'My API',
},
});
serve({ fetch: app.fetch, port: 3000 }); Then we can run Schema
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "My API",
},
"components": {
"schemas": {},
"parameters": {},
},
"paths": {
"/hello": {
"get": {
"parameters": [
{
"schema": {
"type": "string",
"minLength": 1,
"example": "Alice",
},
"required": true,
"name": "name",
"in": "query",
},
],
"responses": {
"202": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Hello Name!",
},
},
"required": ["message"],
},
},
},
},
"400": {
"description": "Failure",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error_title": {
"type": "string",
"example": "Bad input",
},
},
"required": ["error_title"],
},
},
},
},
},
},
},
},
} This is how One nice thing about this is that we get a typescript error if the endpoint implementation does not match the It would be really nice if we can also have it infer the rest of the Nestia does something similar, where it constructs the openapi schema definition directly from typescript types. In this example, notice how you only need to declare |
Yes, exactly! This |
This seems to be a duplicate ticket of: #2970 Looking at nestia/core SwaggerCustomizer, it seems to use typia. To setup typia you've to add a transformer to your tsconfig which allows to use validate the type against an object in runtime // tsconfig.json
....
"plugins": [
{
"transform": "typia/lib/transform"
}
],
... Then you can do this 🪄 import { validate } from 'typia'
type User = {
id: number
name: string
email: string
}
const userData = {
id: 1,
name: 'John Doe',
email: 'john.doe@example.com',
}
const isValid = validate<User>(userData)
if (isValid) {
console.log('Data is valid:', userData)
} else {
console.log('Data is invalid')
}
// This will log: "Data is valid: { id: 1, name: 'John Doe', email: 'john.doe@example.com' }" Which atm seems like black magic to me, but they definitely intercept the type and transform it to code which can be checked against. Haven't looked further into it, but they seem to be able to reflect the typing to be used in runtime functions (at least in some form in the typia 'validate' function). Just for demonstration, if I compile the above code to javascript without the transformer it produces the following code: import { validate } from 'typia';
// Sample data to validate and transform
const userData = {
id: 1,
name: 'John Doe',
email: 'john.doe@example.com',
};
// Validate the data
const isValid = validate(userData);
if (isValid) {
console.log('Data is valid:', userData);
}
else {
console.log('Data is invalid');
} And with the transformer it outputs the following javascript code: import { validate } from 'typia';
// Sample data to validate and transform
const userData = {
id: 1,
name: 'John Doe',
email: 'john.doe@example.com',
};
// Validate the data
const isValid = (input => {
const errors = [];
const __is = input => {
return "object" === typeof input && null !== input && ("number" === typeof input.id && "string" === typeof input.name && "string" === typeof input.email);
};
if (false === __is(input)) {
const $report = validate.report(errors);
((input, _path, _exceptionable = true) => {
const $vo0 = (input, _path, _exceptionable = true) => ["number" === typeof input.id || $report(_exceptionable, {
path: _path + ".id",
expected: "number",
value: input.id
}), "string" === typeof input.name || $report(_exceptionable, {
path: _path + ".name",
expected: "string",
value: input.name
}), "string" === typeof input.email || $report(_exceptionable, {
path: _path + ".email",
expected: "string",
value: input.email
})].every(flag => flag);
return ("object" === typeof input && null !== input || $report(true, {
path: _path + "",
expected: "User",
value: input
})) && $vo0(input, _path + "", true) || $report(true, {
path: _path + "",
expected: "User",
value: input
});
})(input, "$input", true);
}
const success = 0 === errors.length;
return {
success,
errors,
data: success ? input : undefined
};
})(userData);
if (isValid) {
console.log('Data is valid:', userData);
}
else {
console.log('Data is invalid');
}
console.log('Data:', userData); Definitely interesting to to look into, no idea how this works with more complex typing, as it definitely reflects the typings to runtime code. Although it's an optimisation to @hono/zod-openapi, would be fun for someone of the community to pick up as a side project for instance (I expect it's quite some work), but promising. |
Ahh I didn't see #2970, my bad. Closing as duplicate. |
What is the feature you are proposing?
I'm using zod validator, just like the technique described here. Is there a way I can generate an openapi schema from this app?
Regarding Swagger UI and Zod OpenApi, is my assumption correct that they cannot generate an openapi schema for a hono app, unless that hono app uses Zod OpenApi to explicitly declare the shape of each endpoint using
createRoute()
?Hono client does a really great job of inferring the request types, response types, status codes, etc when using zod-validator. However, I couldn't figure out how to make it generate an openapi schema or a swagger ui, despite all of the strong typing afforded to us by zod-validator. I'd just like to make sure that this not currently possible, and not due to a misunderstanding on my part.
The closest I got was this (using chanfana):
But it wasn't very intelligent as it did not make use of all the available type information. Here is what we get when we run it then call
curl http://localhost:3000/openapi.json
:It incorrectly assumed our endpoint returned status of 200 (instead of 202 or 400). It also did not capture the response shape.
Now compare that with how Hono client make use of the rich static typing (focus on the final 3 lines):
typescript playground: link
I'm aware this is operating on the type-level (not runtime), but I was wondering if there are some type reflection tools that would make this feature request possible? Especially given that all the required type information is available.
The text was updated successfully, but these errors were encountered: