From 46a91688f071d377ac3fd8decc04038fcbf93339 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 17 Dec 2024 21:42:10 +0100 Subject: [PATCH] Allow all schemas in responses (not only objects) --- src/utils.ts | 180 ++++++++++----------------------------------- test/index.test.ts | 17 +++++ 2 files changed, 54 insertions(+), 143 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 4b69103..22cd771 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,28 +24,29 @@ export const mapProperties = ( name: string, schema: TSchema | string | undefined, models: Record -) => { +): (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[] => { if (schema === undefined) return [] - if (typeof schema === 'string') + if (typeof schema === 'string') { if (schema in models) schema = models[schema] else throw new Error(`Can't find model ${schema}`) + } - return Object.entries(schema?.properties ?? []).map(([key, value]) => { + // this is used by parameters (query, headers...) and we only support + // object like schemas. + return Object.entries(schema?.properties as Record ?? []).map(([key, value]) => { const { type: valueType = undefined, description, examples, ...schemaKeywords - } = value as any + } = value; return { - // @ts-ignore description, examples, schema: { type: valueType, ...schemaKeywords }, in: name, name: key, - // @ts-ignore required: schema!.required?.includes(key) ?? false } }) @@ -53,13 +54,7 @@ export const mapProperties = ( const mapTypesResponse = ( types: string[], - schema: - | string - | { - type: string - properties: Object - required: string[] - } + schema: string | TSchema ) => { if ( typeof schema === 'object' && @@ -70,15 +65,11 @@ const mapTypesResponse = ( const responses: Record = {} for (const type of types) { - // console.log(schema) - responses[type] = { schema: typeof schema === 'string' - ? { - $ref: `#/components/schemas/${schema}` - } - : { ...(schema as any) } + ? { $ref: `#/components/schemas/${schema}` } + : schema } } @@ -148,118 +139,39 @@ export const registerSchemaPath = ({ if (typeof responseSchema === 'object') { if (Kind in responseSchema) { - const { - type, - properties, - required, - additionalProperties, - patternProperties, - ...rest - } = responseSchema as typeof responseSchema & { - type: string - properties: Object - required: string[] - } - + const value = responseSchema as TSchema; responseSchema = { '200': { - ...rest, - description: rest.description as any, - content: mapTypesResponse( - contentTypes, - type === 'object' || type === 'array' - ? ({ - type, - properties, - patternProperties, - items: responseSchema.items, - required - } as any) - : responseSchema - ) + description: value.description ?? "", + headers: value.headers, + content: mapTypesResponse(contentTypes, value), + links: value.links, } } } else { - Object.entries(responseSchema as Record).forEach( + Object.entries(responseSchema as Record).forEach( ([key, value]) => { - if (typeof value === 'string') { - if (!models[value]) return - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { - type, - properties, - required, - additionalProperties: _1, - patternProperties: _2, - ...rest - } = models[value] as TSchema & { - type: string - properties: Object - required: string[] - } - - responseSchema[key] = { - ...rest, - description: rest.description as any, - content: mapTypesResponse(contentTypes, value) - } - } else { - const { - type, - properties, - required, - additionalProperties, - patternProperties, - ...rest - } = value as typeof value & { - type: string - properties: Object - required: string[] - } - - responseSchema[key] = { - ...rest, - description: rest.description as any, - content: mapTypesResponse( - contentTypes, - type === 'object' || type === 'array' - ? ({ - type, - properties, - patternProperties, - items: value.items, - required - } as any) - : value - ) - } + const model = typeof value === 'string' ? models[value] : value; + if (!model) return; + responseSchema[key] = { + description: model.description ?? "", + headers: model.headers, + links: model.links, + content: mapTypesResponse(contentTypes, model), } } ) } } else if (typeof responseSchema === 'string') { - if (!(responseSchema in models)) return - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { - type, - properties, - required, - additionalProperties: _1, - patternProperties: _2, - ...rest - } = models[responseSchema] as TSchema & { - type: string - properties: Object - required: string[] - } + const value = models[responseSchema]; + if (!value) return responseSchema = { - // @ts-ignore '200': { - ...rest, - content: mapTypesResponse(contentTypes, responseSchema) + description: value.description ?? "", + headers: value.headers, + links: value.links, + content: mapTypesResponse(contentTypes, value) } } } @@ -273,33 +185,15 @@ export const registerSchemaPath = ({ schema[path] = { ...(schema[path] ? schema[path] : {}), [method.toLowerCase()]: { - ...((headerSchema || paramsSchema || querySchema || bodySchema - ? ({ parameters } as any) - : {}) satisfies OpenAPIV3.ParameterObject), - ...(responseSchema - ? { - responses: responseSchema - } - : {}), - operationId: - hook?.detail?.operationId ?? generateOperationId(method, path), + parameters, + responses: responseSchema, + operationId: hook?.detail?.operationId ?? generateOperationId(method, path), ...hook?.detail, - ...(bodySchema - ? { - requestBody: { - required: true, - content: mapTypesResponse( - contentTypes, - typeof bodySchema === 'string' - ? { - $ref: `#/components/schemas/${bodySchema}` - } - : (bodySchema as any) - ) - } - } - : null) - } satisfies OpenAPIV3.OperationObject + requestBody: bodySchema ? { + required: true, + content: mapTypesResponse(contentTypes, bodySchema) + } : undefined, + } } } diff --git a/test/index.test.ts b/test/index.test.ts index 868be79..2463d13 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -276,4 +276,21 @@ describe('Swagger', () => { expect(response.paths['/invalid']).toBeUndefined(); }) + + it('should work with defined models', async () => { + const app = new Elysia().use(swagger()) + .model({"test": t.Integer()}) + .get("/valid", 12, { response: { 200: "test" } }); + + await app.modules + + const res = await app.handle(req('/swagger/json')) + expect(res.status).toBe(200) + const response = await res.json() + expect(response.paths['/valid'].get.responses["200"].content["application/json"].schema.type) + .toBe("integer"); + expect(response.components.schemas.test.type).toBe("integer"); + await SwaggerParser.validate(response).catch((err) => fail(err)) + + }) })