Open vincent-vade opened 1 year ago
Hi @vincent-vade!
Yes, creating a middleware for Swagger sounds like a good idea. Ideally, we would create an OpenAPI middleware, but we're still in the planning phase since we haven't started the API design yet. However, if you're talking about implementing just the Swagger UI, it might be simpler to accomplish.
If you're referring to the full suite of OpenAPI tools, we would need to start designing the middleware. For example, we would need to decide how to define schemas, whether it be with YAML, JSON, or a JS code base.
Hope we can have an auto openapi middleware like Hapi.js Hapi generates openapi doc by Joi validator. Hono already has zod validator middleware. https://github.com/asteasolutions/zod-to-openapi
"zod-to-openapi" looks good. I'll check it in more detail later.
I think it'd be a good idea to also allow support for different validation libraries, so a modular design would be good. Maybe a general combination with the validation middlewares to just extract the schema from them and a mapper for validation lib to openapi schema
@kiancyc
Is Hapi's generator this?: https://github.com/hapi-swagger/hapi-swagger
@ZerNico
Yeah, it's great that we can support Zod, Valibot, Joi, or others. Hono's validator (though the design might not be the best) can support any validator. So, we have Zod, Valibot, and TypeBox validators. If we implement something like that, we can handle any validator, and it can generate OpenAPI docs.
I've tried "zod-to-openapi" and a new feature for the validator.
Even though it's not released yet, I've made the zod validator capable of receiving the schema argument to validate response types (just types) as the third argument:
const apiRoutes = api.get('/users/:id', zValidator('param', ParamsSchema, UserSchema), (c) => {
const { id } = c.req.valid('param');
//
return c.jsonT({
id: '123',
name: 'foo',
age: 30,
});
})
UserSchema
will validate response types, which are rewritten in jsonT()
. Perhaps it should validate actual values, but for now, it validates types only.
Next, I created the schema using zod-to-openapi:
import type { RouteConfig } from '@asteasolutions/zod-to-openapi'
import { extendZodWithOpenApi, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'
import { z } from 'zod'
extendZodWithOpenApi(z)
const registry = new OpenAPIRegistry()
const ParamsSchema = z.object({
id: registry.registerParameter(
'UserId',
z.string().openapi({
param: {
name: 'id',
in: 'path',
},
example: '1212121',
})
),
})
const UserSchema = z
.object({
id: z.string().openapi({
example: '1212121',
}),
name: z.string().openapi({
example: 'John Doe',
}),
age: z.number().openapi({
example: 42,
}),
})
.openapi('User')
const route = {
method: 'get',
path: '/users/:id',
description: 'Get user data by its id',
summary: 'Get a single user',
request: {
params: ParamsSchema,
},
responses: {
200: {
description: 'Object with user data.',
content: {
'application/json': {
schema: UserSchema,
},
},
},
},
} as RouteConfig
registry.registerPath(route)
export { ParamsSchema, UserSchema, registry }
Then, it can return the docs as JSON from an endpoint:
app.get('/schema', prettyJSON(), (c) => {
const generator = new OpenApiGeneratorV3(registry.definitions)
const document = generator.generateDocument({
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'My API',
},
servers: [{ url: 'v1' }],
})
return c.json(document)
})
Result:
Of course, you can generate it as a YAML file as well.
This looks good, and we can use it in the way described above already. However, we can make it more like an application.
@kiancyc
Is Hapi's generator this?: https://github.com/hapi-swagger/hapi-swagger
@ZerNico
Yeah, it's great that we can support Zod, Valibot, Joi, or others. Hono's validator (though the design might not be the best) can support any validator. So, we have Zod, Valibot, and TypeBox validators. If we implement something like that, we can handle any validator, and it can generate OpenAPI docs.
Yes, it is.
I can give two more web framework examples that has openai doc function buil-in:
Nest.JS TS FastApi Python
They all have openapi integration out of box.
I've tried "zod-to-openapi" and a new feature for the validator.
Even though it's not released yet, I've made the zod validator capable of receiving the schema argument to validate response types (just types) as the third argument:
const apiRoutes = api.get('/users/:id', zValidator('param', ParamsSchema, UserSchema), (c) => { const { id } = c.req.valid('param'); // return c.jsonT({ id: '123', name: 'foo', age: 30, }); })
UserSchema
will validate response types, which are rewritten injsonT()
. Perhaps it should validate actual values, but for now, it validates types only.Next, I created the schema using zod-to-openapi:
import type { RouteConfig } from '@asteasolutions/zod-to-openapi' import { extendZodWithOpenApi, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi' import { z } from 'zod' extendZodWithOpenApi(z) const registry = new OpenAPIRegistry() const ParamsSchema = z.object({ id: registry.registerParameter( 'UserId', z.string().openapi({ param: { name: 'id', in: 'path', }, example: '1212121', }) ), }) const UserSchema = z .object({ id: z.string().openapi({ example: '1212121', }), name: z.string().openapi({ example: 'John Doe', }), age: z.number().openapi({ example: 42, }), }) .openapi('User') const route = { method: 'get', path: '/users/:id', description: 'Get user data by its id', summary: 'Get a single user', request: { params: ParamsSchema, }, responses: { 200: { description: 'Object with user data.', content: { 'application/json': { schema: UserSchema, }, }, }, }, } as RouteConfig registry.registerPath(route) export { ParamsSchema, UserSchema, registry }
Then, it can return the docs as JSON from an endpoint:
app.get('/schema', prettyJSON(), (c) => { const generator = new OpenApiGeneratorV3(registry.definitions) const document = generator.generateDocument({ openapi: '3.0.0', info: { version: '1.0.0', title: 'My API', }, servers: [{ url: 'v1' }], }) return c.json(document) })
Result:
Of course, you can generate it as a YAML file as well.
This looks good, and we can use it in the way described above already. However, we can make it more like an application.
I think the tricky part is to auto generate the RouteConfig.
Express.js joi and swagger integration middleware:
https://github.com/vforv/express-joi-simple
import * as express from 'express';
import * as joi from 'joi';
import * as BodyParser from 'body-parser';
import { Doc, Validate, RequestHandler } from 'express-joi-simple';
const app = express();
app.use(BodyParser.json());
const schema = {
body: {
test1: joi.string().required()
},
model: 'Register'
}
// Note middleware here
app.post('register', Validate(schema), (req: any, res: any) => {
res.json({
message: 'register'
})
})
app.use(RequestHandler);
app.listen(3000, () => {
// Note function Doc here
Doc(app);
})
Express.js joi and swagger integration middleware:
https://github.com/vforv/express-joi-simple
import * as express from 'express'; import * as joi from 'joi'; import * as BodyParser from 'body-parser'; import { Doc, Validate, RequestHandler } from 'express-joi-simple'; const app = express(); app.use(BodyParser.json()); const schema = { body: { test1: joi.string().required() }, model: 'Register' } // Note middleware here app.post('register', Validate(schema), (req: any, res: any) => { res.json({ message: 'register' }) }) app.use(RequestHandler); app.listen(3000, () => { // Note function Doc here Doc(app); })
This extracts the schema by calling the route with "schemaBypass" as the req parameter. The joi middlware then just returns the schema instead of doing validation. I'm not sure if that is the best way to handle something like that, it stops working if there is any other middleware infront of it that returns something or throws. But I also can't think of a good way to access the schema. Maybe attaching it to the returned Middleware function kind of like how displayName and such is set in react?
Also instead of modifying (or wrapping) the validation library zod-to-openapi does it'd probably be better to have another parameter to set the example object and just check the type of that with the passed schema. Would make it easier to use with different libs.
This goes very deep into Hono then, would a plugin API make more sense to accomplish something like this?
@yusukebe should this issue be closed or converted to a discussion?
Hello. Since we are discussing open api here, I would like to point out another solution https://github.com/jlalmes/trpc-openapi that requires integration of both swagger and hono.
Hi,
Hono is awesome but is it possible to add swagger middleware ?
Best regards
Vincent