mirror of
https://github.com/zoriya/elysia-swagger.git
synced 2025-12-06 00:36:10 +00:00
🎉 feat: 0.8.5
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
# 0.8.5 - 24 Jan 2024
|
||||
Bug fix:
|
||||
- [#39](https://github.com/elysiajs/elysia-swagger/issues/39) Array type does not work
|
||||
|
||||
# 0.8.4 - 24 Jan 2024
|
||||
Feature:
|
||||
- [#96](https://github.com/elysiajs/elysia-swagger/pull/96) move to scalar configuration prop
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Elysia } from 'elysia'
|
||||
import { Elysia, InternalRoute } from 'elysia'
|
||||
import { swagger } from '../src/index'
|
||||
import { plugin } from './plugin'
|
||||
import { registerSchemaPath } from '../src/utils'
|
||||
|
||||
const app = new Elysia()
|
||||
.use(
|
||||
swagger({
|
||||
provider: 'scalar',
|
||||
documentation: {
|
||||
info: {
|
||||
title: 'Elysia Scalar',
|
||||
|
||||
@@ -42,44 +42,40 @@ export const plugin = new Elysia({
|
||||
summary: 'Using reference model'
|
||||
}
|
||||
})
|
||||
// .post(
|
||||
// '/json/:id',
|
||||
// ({ body, params: { id }, query: { name } }) => ({
|
||||
// ...body,
|
||||
// id
|
||||
// }),
|
||||
// {
|
||||
// transform({ params }) {
|
||||
// params.id = +params.id
|
||||
// },
|
||||
// schema: {
|
||||
// body: 'sign',
|
||||
// params: t.Object({
|
||||
// id: t.Number()
|
||||
// }),
|
||||
// response: {
|
||||
// 200: t.Object(
|
||||
// {
|
||||
// id: t.Number(),
|
||||
// username: t.String(),
|
||||
// password: t.String()
|
||||
// },
|
||||
// {
|
||||
// title: 'User',
|
||||
// description:
|
||||
// "Contains user's confidential metadata"
|
||||
// }
|
||||
// ),
|
||||
// 400: t.Object({
|
||||
// error: t.String()
|
||||
// })
|
||||
// },
|
||||
// detail: {
|
||||
// summary: 'Transform path parameter'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
.post(
|
||||
'/json/:id',
|
||||
({ body, params: { id }, query: { name } }) => ({
|
||||
...body,
|
||||
id
|
||||
}),
|
||||
{
|
||||
body: 'sign',
|
||||
params: t.Object({
|
||||
id: t.Numeric()
|
||||
}),
|
||||
response: {
|
||||
200: t.Object(
|
||||
{
|
||||
id: t.Number(),
|
||||
username: t.String(),
|
||||
password: t.String()
|
||||
},
|
||||
{
|
||||
title: 'User',
|
||||
description: "Contains user's confidential metadata"
|
||||
}
|
||||
),
|
||||
418: t.Array(
|
||||
t.Object({
|
||||
error: t.String()
|
||||
})
|
||||
),
|
||||
},
|
||||
detail: {
|
||||
summary: 'Complex JSON'
|
||||
}
|
||||
}
|
||||
)
|
||||
.post('/file', ({ body: { file } }) => file, {
|
||||
type: 'formdata',
|
||||
body: t.Object({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@elysiajs/swagger",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"description": "Plugin for Elysia to auto-generate Swagger page",
|
||||
"author": {
|
||||
"name": "saltyAom",
|
||||
|
||||
28
src/index.ts
28
src/index.ts
@@ -62,26 +62,28 @@ export const swagger =
|
||||
|
||||
const relativePath = path.startsWith('/') ? path.slice(1) : path
|
||||
|
||||
app.get(path, () => {
|
||||
app.get(
|
||||
path,
|
||||
(() => {
|
||||
const combinedSwaggerOptions = {
|
||||
url: `${relativePath}/json`,
|
||||
dom_id: '#swagger-ui',
|
||||
...swaggerOptions
|
||||
}
|
||||
|
||||
const stringifiedSwaggerOptions = JSON.stringify(
|
||||
combinedSwaggerOptions,
|
||||
(key, value) => {
|
||||
if (typeof value == 'function') {
|
||||
return undefined
|
||||
} else {
|
||||
if (typeof value == 'function') return undefined
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const scalarConfiguration: ReferenceConfiguration = {
|
||||
spec: {
|
||||
url: `${relativePath}/json`
|
||||
...scalarConfig.spec,
|
||||
url: `${relativePath}/json`,
|
||||
},
|
||||
...scalarConfig
|
||||
}
|
||||
@@ -95,14 +97,21 @@ export const swagger =
|
||||
stringifiedSwaggerOptions,
|
||||
autoDarkMode
|
||||
)
|
||||
: ScalarRender(scalarVersion, scalarConfiguration, scalarCDN),
|
||||
: ScalarRender(
|
||||
scalarVersion,
|
||||
scalarConfiguration,
|
||||
scalarCDN
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'text/html; charset=utf8'
|
||||
}
|
||||
}
|
||||
)
|
||||
}).get(`${path}/json`, () => {
|
||||
})()
|
||||
).get(
|
||||
`${path}/json`,
|
||||
() => {
|
||||
const routes = app.routes as InternalRoute[]
|
||||
|
||||
if (routes.length !== totalRoutes) {
|
||||
@@ -147,7 +156,8 @@ export const swagger =
|
||||
}
|
||||
}
|
||||
} satisfies OpenAPIV3.Document
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// This is intentional to prevent deeply nested type
|
||||
return app
|
||||
|
||||
66
src/utils.ts
66
src/utils.ts
@@ -25,7 +25,7 @@ export const mapProperties = (
|
||||
else throw new Error(`Can't find model ${schema}`)
|
||||
|
||||
return Object.entries(schema?.properties ?? []).map(([key, value]) => {
|
||||
const { type: valueType = undefined, ...rest } = value as any;
|
||||
const { type: valueType = undefined, ...rest } = value as any
|
||||
return {
|
||||
// @ts-ignore
|
||||
...rest,
|
||||
@@ -33,9 +33,9 @@ export const mapProperties = (
|
||||
in: name,
|
||||
name: key,
|
||||
// @ts-ignore
|
||||
required: schema!.required?.includes(key) ?? false,
|
||||
};
|
||||
});
|
||||
required: schema!.required?.includes(key) ?? false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const mapTypesResponse = (
|
||||
@@ -48,8 +48,11 @@ const mapTypesResponse = (
|
||||
required: string[]
|
||||
}
|
||||
) => {
|
||||
if (typeof schema === 'object'
|
||||
&& ['void', 'undefined', 'null'].includes(schema.type)) return;
|
||||
if (
|
||||
typeof schema === 'object' &&
|
||||
['void', 'undefined', 'null'].includes(schema.type)
|
||||
)
|
||||
return
|
||||
|
||||
const responses: Record<string, OpenAPIV3.MediaTypeObject> = {}
|
||||
|
||||
@@ -122,8 +125,13 @@ export const registerSchemaPath = ({
|
||||
|
||||
if (typeof responseSchema === 'object') {
|
||||
if (Kind in responseSchema) {
|
||||
const { type, properties, required, additionalProperties, ...rest } =
|
||||
responseSchema as typeof responseSchema & {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
...rest
|
||||
} = responseSchema as typeof responseSchema & {
|
||||
type: string
|
||||
properties: Object
|
||||
required: string[]
|
||||
@@ -139,6 +147,7 @@ export const registerSchemaPath = ({
|
||||
? ({
|
||||
type,
|
||||
properties,
|
||||
items: responseSchema.items,
|
||||
required
|
||||
} as any)
|
||||
: responseSchema
|
||||
@@ -152,9 +161,13 @@ export const registerSchemaPath = ({
|
||||
if (!models[value]) return
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { type, properties, required, additionalProperties: _, ...rest } = models[
|
||||
value
|
||||
] as TSchema & {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties: _,
|
||||
...rest
|
||||
} = models[value] as TSchema & {
|
||||
type: string
|
||||
properties: Object
|
||||
required: string[]
|
||||
@@ -166,8 +179,13 @@ export const registerSchemaPath = ({
|
||||
content: mapTypesResponse(contentTypes, value)
|
||||
}
|
||||
} else {
|
||||
const { type, properties, required, additionalProperties, ...rest } =
|
||||
value as typeof value & {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
...rest
|
||||
} = value as typeof value & {
|
||||
type: string
|
||||
properties: Object
|
||||
required: string[]
|
||||
@@ -176,11 +194,17 @@ export const registerSchemaPath = ({
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description as any,
|
||||
content: mapTypesResponse(contentTypes, {
|
||||
type,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
rest.type === 'object' || rest.type === 'array'
|
||||
? ({
|
||||
type: rest.type,
|
||||
properties,
|
||||
items: value.items,
|
||||
required
|
||||
})
|
||||
} as any)
|
||||
: value
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,9 +214,13 @@ export const registerSchemaPath = ({
|
||||
if (!(responseSchema in models)) return
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { type, properties, required, additionalProperties: _, ...rest } = models[
|
||||
responseSchema
|
||||
] as TSchema & {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties: _,
|
||||
...rest
|
||||
} = models[responseSchema] as TSchema & {
|
||||
type: string
|
||||
properties: Object
|
||||
required: string[]
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
import { Elysia, t } from 'elysia'
|
||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import SwaggerParser from '@apidevtools/swagger-parser'
|
||||
import { swagger } from '../src'
|
||||
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import { fail } from 'assert';
|
||||
import { fail } from 'assert'
|
||||
|
||||
const req = (path: string) => new Request(`http://localhost${path}`)
|
||||
|
||||
it('returns a valid Swagger/OpenAPI json config for many routes', async () => {
|
||||
const app = new Elysia()
|
||||
.use(swagger())
|
||||
.get('/', () => 'hi', { response: t.String({ description: 'sample description' }) })
|
||||
.get('/unpath/:id', ({ params: { id } }) => id, { response: t.String({ description: 'sample description' }) })
|
||||
.get('/unpath/:id/:name/:age', ({ params: { id, name } }) => `${id} ${name}`,
|
||||
{ type: "json", response: t.String({ description: 'sample description' }), params: t.Object({ id: t.String(), name: t.String() }) })
|
||||
.get('/', () => 'hi', {
|
||||
response: t.String({ description: 'sample description' })
|
||||
})
|
||||
.get('/unpath/:id', ({ params: { id } }) => id, {
|
||||
response: t.String({ description: 'sample description' })
|
||||
})
|
||||
.get(
|
||||
'/unpath/:id/:name/:age',
|
||||
({ params: { id, name } }) => `${id} ${name}`,
|
||||
{
|
||||
type: 'json',
|
||||
response: t.String({ description: 'sample description' }),
|
||||
params: t.Object({ id: t.String(), name: t.String() })
|
||||
}
|
||||
)
|
||||
.post(
|
||||
'/json/:id',
|
||||
({ body, params: { id }, query: { name } }) => ({
|
||||
@@ -32,14 +43,18 @@ it('returns a valid Swagger/OpenAPI json config for many routes', async () => {
|
||||
username: t.String(),
|
||||
password: t.String()
|
||||
}),
|
||||
response: t.Object({
|
||||
response: t.Object(
|
||||
{
|
||||
username: t.String(),
|
||||
password: t.String(),
|
||||
id: t.String(),
|
||||
name: t.String()
|
||||
}, { description: 'sample description 3' })
|
||||
},
|
||||
{ description: 'sample description 3' }
|
||||
)
|
||||
}
|
||||
);
|
||||
const res = await app.handle(req('/swagger/json')).then((x) => x.json());
|
||||
await SwaggerParser.validate(res).catch((err) => fail(err));
|
||||
});
|
||||
)
|
||||
|
||||
const res = await app.handle(req('/swagger/json')).then((x) => x.json())
|
||||
await SwaggerParser.validate(res).catch((err) => fail(err))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user