NestJs / swagger:定义没有DTO类的引用架构

问题描述

我有一个应用程序,根据open-api规范,我将API响应模式定义为纯javascript对象。目前,我将其传递给@ nestjs / swagger中的ApiResponse装饰器,如下所示:

class CatsController {

  @Get()
  @ApiResponse({
    status: 200,schema: catSchema // plain js object imported from another file
  })
  getAll() {}
}

这很好。但是,输出的open-api规范包含每个使用catSchema的端点的详细架构。 相反,我希望输出的swagger文件components部分下具有catSchema,在paths部分中具有相应的$ref

components:
  schemas:
    Cat:
      properties:
        name:
          type: string
paths:
  /cats/{id}:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cat'

到目前为止,似乎唯一的方法是将架构定义为DTO类,并对每个类属性使用ApiProperty装饰器。就我而言,这意味着我必须将open-api规范中的所有纯对象模式重构为DTO类。

是否可以将原始模式馈送到库并获得预期的结果?

// instead of this:
class CatDto {
  @Apiproperty()
  name: string;
}

// I want to do:
const catSchema = {
  type: 'object',properties: {
    name: { type: 'string' }
  }
}

解决方法

经过数天的反复试验,我能够使用Javascript中的一个有趣技巧来实现这一目标。

首先,我将open-api规范创建为一个普通对象(如问题中所述)。然后将其传递给新的装饰器,神奇的地方发生。

在装饰器中,我创建一个具有预定义名称的DTO类,并将属性从普通对象映射到DTO类。棘手的部分是动态为其命名。这可以通过以下技术来实现。

const dynamicName = 'foo'; // passed as a parameter to the decorator

class IntermediateDTO {
  @ApiProperty(schema) // schema as a plain object
  data: any;
}

const proxyObject = {
  [dynamicName] = class extends IntermediateDTO {}
}

通过使用代理对象,并将class extends IntermediateDTO {}分配给其中的属性,该条目将动态获得名称。现在,可以将具有动态名称的新DTO传递给ApiResponse的{​​{1}}装饰器,以达到预期的结果。