如果 class 在我将它变成一个函数并将其应用于 NestJS 中的 Swagger 响应时无法正常工作怎么办?

What if the class is not working properly when i make it into a function and apply it to Swagger's response in NestJS?

请理解我的英语很差。

示例代码:Github

@Injectable()
export class FAQService {
  constructor(private faqRepository: FAQRepository) {}

  async getFAQ(options: Options): Promise<GetFAQResponse> {
    const response = await paginate<FAQEntity>(this.faqRepository, options);
    return { data: response, statusCode: 200 };
  }
}

在我们做的项目中,return值的形状设置为{data: response, statusCode: 200},如上面的方法所示。 这种形式在所有模块的所有服务中重复出现,当 response 从服务方法中 return 时,拦截器更改为 {data: response, statusCode: 200}。 在这里,我有一个问题,如果拦截器进行相应的处理,它不适用于swagger。 为了解决这个问题,我创建了一个自定义装饰器,如下所示。

// http-method.decorator.ts
import { ApiOkResponse, ApiOperation, ApiProperty } from '@nestjs/swagger';
import { Get as NestGet, applyDecorators } from '@nestjs/common';

class BaseResponse {
  @ApiProperty()
  data: any;

  @ApiProperty()
  statusCode: number;
}

export interface IEndpointParams {
  route: string;
  summary: string;
  type?: Type<unknown> | Function | [Function] | string;
}

const mapResponse = <T>(type: T) => {
  class Response extends BaseResponse {
    @ApiProperty({ type })
    data: T;
  }
  return Response;
};

export function Get(params: IEndpointParams) {
  const { route, summary, type } = params;
  return applyDecorators(NestGet(route), ApiOperation({ summary }), ApiOkResponse({ type: mapResponse(type) }));
}

它被应用到控制器如下。

@Controller('faq')
@ApiTags('FAQ API')
@ApiSecurity('Authorization')
export class FAQController {
  constructor(private faqService: FAQService) {}

  @Get({ route: '', summary: 'List Of FAQ', type: GetFAQResponse })
  async faqs(@PaginationQuery() query: PaginationQuery): Promise<GetFAQResponse> {
    return await this.faqService.getFAQ(query);
  }
}

然后我创建了一个拦截器并将其应用于应用程序。

// http-response.interceptor.ts
@Injectable()
export class HttpResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<BaseResponse> {
    const ctx = context.switchToHttp();
    const response = ctx.getResponse<Response>();
    return next.handle().pipe(map((data) => ({ data, statusCode: response.statusCode })));
  }
}

// app.module.ts
import { APP_INTERCEPTOR } from '@nestjs/core';
...
import { HttpResponseInterceptor } from './common/http-response.interceptor';

@Module({
  imports: [
    ...
    FaqModule,
    TradeModule,
    BannersModule,
    NoticeModule,
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: HttpResponseInterceptor,
    },
  ],
})
export class AppModule {
  ...
}

如上所述,swagger 也显示了 {data: response, statusCode: 200},但问题是 swagger 中的所有端点都应用了 AppModule 中最后导入模块控制器的最后一个方法的类型(这里,NoticeModule)。

// faq.controller.ts
@Controller('faq')
@ApiTags('FAQ API')
@ApiSecurity('Authorization')
export class FAQController {
  constructor(private faqService: FAQService) {}

  @Get({ route: '', summary: 'List Of FAQ', type: GetFAQResponse })
  async faqs(@PaginationQuery() query: PaginationQuery): Promise<GetFAQResponse> {
    return await this.faqService.getFAQ(query);
  }
}

// notice.controller.ts
@Controller('notice')
@ApiTags('Notice API')
@ApiSecurity('Authorization')
export class NoticeController {
  constructor(private noticeService: NoticeService) {}

  @Get({ route: '', summary: 'List Of FAQ', type: GetNoticeResponse })
  async notices(@PaginationQuery() query: PaginationQuery): Promise<GetNoticeResponse> {
    return await this.noticeService.getNotice(query);
  }
}

假设FAQ ControllerNotice Controller如上所述。现在 Notice Module 是在 AppModule 中最后导入的,GetNoticeResponse 反映在 faq endpoint 的 swagger 文档中,而不是 GetFAQResponse。问题好像出在mapResponse函数中,但我不知道为什么。

我找到了答案。

通过mapResponse函数生成的class名称重叠,最后一个class反映在Swagger

因此,将代码修改如下,以防止名称重叠。

const mapResponse = <T>(type: T) => {
  if (!type) return type;
  const descriptor = Object.getOwnPropertyDescriptor(type, 'name');
  class WrappedResponse extends BaseResponse {
    @ApiProperty({ type })
    data: T;
  }
  Object.defineProperty(WrappedResponse, 'name', {
    value: `Wrapped${descriptor.value}`,
  });
  return WrappedResponse;
};