From 1dc68035c4fef0f39bd735691e0cdde8f920e63a Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Mon, 23 Sep 2024 18:22:42 +0800 Subject: [PATCH] fix: ensure compatibility with Swagger response schema and fix the parsing of enum types (#4083) * fix: swagger schema and enum value * fix: build * fix: test --- packages-legacy/cache/src/service/cache.ts | 2 +- packages/cache-manager/package.json | 1 + packages/cache-manager/test/index.test.ts | 5 +- packages/swagger/src/common/enum.utils.ts | 19 +--- .../api-exclude-controller.decorator.ts | 2 +- .../decorators/api-extra-model.decorator.ts | 2 +- packages/swagger/src/swaggerExplorer.ts | 21 ++++- .../test/__snapshots__/parser.test.ts.snap | 87 +++++++++++-------- packages/swagger/test/parser.test.ts | 34 ++++++-- packages/swagger/test/util.test.ts | 66 ++++++++++++++ 10 files changed, 170 insertions(+), 69 deletions(-) create mode 100644 packages/swagger/test/util.test.ts diff --git a/packages-legacy/cache/src/service/cache.ts b/packages-legacy/cache/src/service/cache.ts index 97500f9df809..6480a70af88c 100644 --- a/packages-legacy/cache/src/service/cache.ts +++ b/packages-legacy/cache/src/service/cache.ts @@ -1,5 +1,5 @@ import { Config, Init, Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import * as cacheManager from 'cache-manager'; +const cacheManager = require('cache-manager'); @Provide() @Scope(ScopeEnum.Singleton) diff --git a/packages/cache-manager/package.json b/packages/cache-manager/package.json index b970f296a8dc..9971800dcec4 100644 --- a/packages/cache-manager/package.json +++ b/packages/cache-manager/package.json @@ -31,6 +31,7 @@ "@midwayjs/core": "^3.18.0", "@midwayjs/mock": "^3.18.0", "@midwayjs/redis": "^3.18.0", + "cache-manager": "6.0.0", "cache-manager-ioredis-yet": "2.1.1" }, "dependencies": { diff --git a/packages/cache-manager/test/index.test.ts b/packages/cache-manager/test/index.test.ts index 5b8d8d3fbdfb..57514b5bff89 100644 --- a/packages/cache-manager/test/index.test.ts +++ b/packages/cache-manager/test/index.test.ts @@ -1,15 +1,14 @@ import * as path from 'path'; import * as assert from 'assert'; import { createLightApp, close } from '@midwayjs/mock'; -import { caching } from 'cache-manager'; +import { createCache } from 'cache-manager'; import { sleep } from '@midwayjs/core'; describe(`index.test.ts`, ()=>{ describe('cache manager', () => { it('test cache manager wrap method', async () => { - const memoryCache = await caching('memory', { - max: 100, + const memoryCache = await createCache({ ttl: 10 }); let i = 0; diff --git a/packages/swagger/src/common/enum.utils.ts b/packages/swagger/src/common/enum.utils.ts index d019267ca9b9..c369698a1b14 100644 --- a/packages/swagger/src/common/enum.utils.ts +++ b/packages/swagger/src/common/enum.utils.ts @@ -12,22 +12,11 @@ export function getEnumValues(enumType: SwaggerEnumType): string[] | number[] { return []; } - const values = []; - const uniqueValues = {}; + const values = Object.keys(enumType) + .filter(key => isNaN(Number(key))) + .map(key => enumType[key]); - for (const key in enumType) { - const value = enumType[key]; - /* eslint-disable no-prototype-builtins */ - // filter out cases where enum key also becomes its value (A: B, B: A) - if ( - !uniqueValues.hasOwnProperty(value) && - !uniqueValues.hasOwnProperty(key) - ) { - values.push(value); - uniqueValues[value] = value; - } - } - return values; + return Array.from(new Set(values)); } export function getEnumType(values: (string | number)[]): 'string' | 'number' { diff --git a/packages/swagger/src/decorators/api-exclude-controller.decorator.ts b/packages/swagger/src/decorators/api-exclude-controller.decorator.ts index 49573277e15b..b31babdefc86 100644 --- a/packages/swagger/src/decorators/api-exclude-controller.decorator.ts +++ b/packages/swagger/src/decorators/api-exclude-controller.decorator.ts @@ -1,7 +1,7 @@ import { saveClassMetadata } from '@midwayjs/core'; import { DECORATORS } from '../constants'; -export function ApiExcludeController(disable = true): any { +export function ApiExcludeController(disable = true): ClassDecorator { return (target: any) => { saveClassMetadata(DECORATORS.API_EXCLUDE_CONTROLLER, { disable }, target); }; diff --git a/packages/swagger/src/decorators/api-extra-model.decorator.ts b/packages/swagger/src/decorators/api-extra-model.decorator.ts index fdb808d5fbe9..1cc3b835803d 100644 --- a/packages/swagger/src/decorators/api-extra-model.decorator.ts +++ b/packages/swagger/src/decorators/api-extra-model.decorator.ts @@ -2,6 +2,6 @@ import { DECORATORS } from '../constants'; import { Type } from '../interfaces'; import { createMixedDecorator } from './helpers'; -export function ApiExtraModel(models: Type | Type[]) { +export function ApiExtraModel(models: Type | Type[]): ClassDecorator { return createMixedDecorator(DECORATORS.API_EXTRA_MODEL, models); } diff --git a/packages/swagger/src/swaggerExplorer.ts b/packages/swagger/src/swaggerExplorer.ts index 717db36b388b..41e37bb1b294 100644 --- a/packages/swagger/src/swaggerExplorer.ts +++ b/packages/swagger/src/swaggerExplorer.ts @@ -39,6 +39,7 @@ import { SwaggerOptions, } from './interfaces/'; import { BodyContentType } from '.'; +import { getEnumValues } from './common/enum.utils'; @Provide() @Scope(ScopeEnum.Singleton) @@ -643,7 +644,16 @@ export class SwaggerExplorer { for (const k of keys) { // 这里是引用,赋值可以直接更改 const tt = resp[k]; - if (tt.type) { + + if (tt.schema) { + // response 的 schema 需要包含在 content 内 + tt.content = { + 'application/json': { + schema: this.formatType(tt.schema), + }, + }; + delete tt.schema; + } else if (tt.type) { if (Types.isClass(tt.type)) { this.parseClzz(tt.type); @@ -856,8 +866,13 @@ export class SwaggerExplorer { // 如果有枚举,单独处理 if (metadata.enum) { - // enum 不需要处理 - metadata.enum.map(item => this.formatType(item)); + if (Array.isArray(metadata.enum)) { + // enum 不需要处理 + metadata.enum.map(item => this.formatType(item)); + } else { + // 枚举类型需要处理 + metadata.enum = getEnumValues(metadata.enum); + } } if (metadata.not) { diff --git a/packages/swagger/test/__snapshots__/parser.test.ts.snap b/packages/swagger/test/__snapshots__/parser.test.ts.snap index 11887f213516..7cb4676488db 100644 --- a/packages/swagger/test/__snapshots__/parser.test.ts.snap +++ b/packages/swagger/test/__snapshots__/parser.test.ts.snap @@ -2971,24 +2971,28 @@ exports[`test @ApiResponse should test use ApiOkResponse 1`] = ` "parameters": [], "responses": { "200": { - "description": "The record has been successfully created.", - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Cat", - }, - { - "properties": { - "age": { - "example": 1, - "type": "number", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Cat", }, - }, - "type": "object", + { + "properties": { + "age": { + "example": 1, + "type": "number", + }, + }, + "type": "object", + }, + ], + "title": "response data", }, - ], - "title": "response data", + }, }, + "description": "The record has been successfully created.", }, }, "summary": undefined, @@ -3142,17 +3146,20 @@ exports[`test @ApiResponse should test with schema 1`] = ` "parameters": [], "responses": { "201": { - "description": "The record has been successfully created.", - "schema": { - "properties": { - "name": { - "description": "The name of the Cat", - "example": "Kitty", - "type": "string", + "content": { + "application/json": { + "schema": { + "properties": { + "age": { + "example": 1, + "type": "number", + }, + }, + "type": "object", }, }, - "type": "object", }, + "description": "The record has been successfully created.", }, "403": { "description": "Forbidden.", @@ -3194,24 +3201,28 @@ exports[`test @ApiResponse should test with schema and set ref 1`] = ` "parameters": [], "responses": { "201": { - "description": "The record has been successfully created.", - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/Cat", - }, - { - "properties": { - "age": { - "example": 1, - "type": "number", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Cat", }, - }, - "type": "object", + { + "properties": { + "age": { + "example": 1, + "type": "number", + }, + }, + "type": "object", + }, + ], + "title": "response data", }, - ], - "title": "response data", + }, }, + "description": "The record has been successfully created.", }, "403": { "description": "Forbidden.", diff --git a/packages/swagger/test/parser.test.ts b/packages/swagger/test/parser.test.ts index 25452f1e3dd6..1c81acbe1a99 100644 --- a/packages/swagger/test/parser.test.ts +++ b/packages/swagger/test/parser.test.ts @@ -1100,13 +1100,16 @@ describe('test @ApiResponse', () => { @ApiResponse({ status: 201, description: 'The record has been successfully created.', - schema: { - type: 'object', - properties: { - name: { - type: 'string', - description: 'The name of the Cat', - example: 'Kitty', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + age: { + type: 'number', + example: 1, + }, + }, }, }, }, @@ -2144,6 +2147,23 @@ describe('test property metadata parse', () => { }); }); + it("should format enum with enum type", () => { + enum StatusEnum { + Disabled, + Enabled, + } + + const result = swaggerExplorer.formatType({ + type: 'enum', + enum: StatusEnum + }); + + expect(result).toEqual({ + type: 'enum', + enum: [0, 1], + }); + }); + it('should format additionalProperties', () => { const result = swaggerExplorer.formatType({ type: 'object', diff --git a/packages/swagger/test/util.test.ts b/packages/swagger/test/util.test.ts new file mode 100644 index 000000000000..6b55653d1161 --- /dev/null +++ b/packages/swagger/test/util.test.ts @@ -0,0 +1,66 @@ +import { getEnumValues } from "../src/common/enum.utils"; + +describe('/test/util.test.ts', () => { + it('test enum get values', () => { + enum StatusEnum { + Disabled, + Enabled, + } + + expect(getEnumValues(StatusEnum)).toEqual([0, 1]); + + enum StatusEnum2 { + Disabled = 'disabled', + Enabled = 'enabled', + } + + expect(getEnumValues(StatusEnum2)).toEqual(['disabled', 'enabled']); + + enum StatusEnum3 { + Disabled = 1, + Enabled = 2, + } + + expect(getEnumValues(StatusEnum3)).toEqual([1, 2]); + + enum StatusEnum4 { + Disabled = 'disabled', + Enabled = 2, + } + + expect(getEnumValues(StatusEnum4)).toEqual(['disabled', 2]); + + enum StatusEnum5 { + Disabled = 2, + Enabled = 'enabled', + } + + expect(getEnumValues(StatusEnum5)).toEqual([2, 'enabled']); + + enum StatusEnum6 { + Disabled = '1', + Enabled = 2, + } + + expect(getEnumValues(StatusEnum6)).toEqual(['1', 2]); + + enum StatusEnum7 { + Disabled = 3, + Enabled, + } + + expect(getEnumValues(StatusEnum7)).toEqual([3, 4]); + + enum StatusEnum8 { + Disabled = 1, + Enabled = 1, + } + + expect(getEnumValues(StatusEnum8)).toEqual([1]); + + expect(getEnumValues('test')).toEqual([]); + expect(getEnumValues([1])).toEqual([1]); + expect(getEnumValues([])).toEqual([]); + }); + +});