概述

在这篇文章中,我们将编写一个API规范,允许对同一个响应代码返回两种不同的对象。我们将演示如何使用这个规范来生成Java代码和Swagger文档。

问题描述

首先,我们定义两个对象:一辆汽车(Car)有车主和车牌,两者都是字符串(String)。另一方面,一辆自行车(Bike)有车主和速度,速度是一个整数(Integer)。

在OpenAPI中,这些定义对应于以下描述:

Car:
  type: object
  properties:
    owner:
      type: string
    plate:
      type: string
Bike:
  type: object
  properties:
    owner:
      type: string
    speed:
      type: integer

我们的目标是描述一个名为/vehicle的端点,它将接受GET请求,并能够返回汽车或自行车。换句话说,我们要完成以下描述:

paths:
  /vehicle:
    get:
      responses:
        '200':
          # return Car or Bike

我们将讨论OpenAPI 2和3规范下的这个话题。

使用OpenAPI 3实现两种不同的响应

OpenAPI 3版本引入了oneOf,这正是我们需要的。

3.1. 构建描述文件

在OpenAPI 3规范中,oneOf期望一个对象数组,表示提供的值应精确匹配给定对象之一:

schema:
  oneOf:
    - $ref: '#/components/schemas/Car'
    - $ref: '#/components/schemas/Bike'

此外,OpenAPI 3还引入了展示各种响应示例的可能性。为了清晰,我们肯定希望至少提供一个带有汽车的响应示例和一个带有自行车的示例:

examples:
  car:
    summary: an example of car
    value:
      owner: baeldung
      plate: AEX305
  bike:
    summary: an example of bike
    value:
      owner: john doe
      speed: 25

最后,让我们看看完整的描述文件:

openapi: 3.0.0
info:
  title: Demo api
  description: Demo api for the article 'specify two responses with same code based on optional parameter'
  version: 0.1.0
paths:
  /vehicle:
    get:
      responses:
        '200':
          description: Get a vehicle 
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Car'
                  - $ref: '#/components/schemas/Bike'
              examples:
                car:
                  summary: an example of car
                  value:
                    owner: baeldung
                    plate: AEX305
                bike:
                  summary: an example of bike
                  value:
                    owner: john doe
                    speed: 25
components:
  schemas:
    Car:
      type: object
      properties:
        owner:
          type: string
        plate:
          type: string
    Bike:
      type: object
      properties:
        owner:
          type: string
        speed:
          type: integer

3.2. 生成Java类

现在,我们将使用YAML文件来生成我们的API接口。 可以使用两个Maven插件,swagger-codegenopenapi-generator,从api.yaml文件生成Java代码。由于版本6.0.1,openapi-generator不支持oneOf,因此本文我们将使用swagger-codegen

我们将使用以下swagger-codegen插件配置:

<plugin>
    <groupId>io.swagger.codegen.v3</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>3.0.52</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/static/api.yaml</inputSpec>
                <language>spring</language>
                <configOptions>
                    <java8>true</java8>
                    <interfaceOnly>true</interfaceOnly>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

请注意,我们决定只生成接口,以避免生成大量对我们不感兴趣的文件。

现在执行插件:

mvn clean compile

我们可以看到生成的文件:

  • 汽车和自行车对象被生成
  • 使用@JsonSubTypes注解,OneOfinlineResponse200接口被生成,用于表示可以是汽车或自行车的对象
  • InlineResponse200OneOfinlineResponse200的基础实现
  • VehicleApi定义了端点:向此端点发送GET请求将返回一个InlineResponse200

3.3. 生成Swagger UI文档

要从我们的YAML描述文件生成Swagger UI文档,我们将使用springdoc-openapi。在pom.xml中添加springdoc-openapi-ui依赖:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.6.10</version>
</dependency

版本1.6.10的springdoc-openapi-ui依赖于Swagger UI 4.13.2,它能正确处理oneOf和各种响应示例。

要从YAML文件生成Swagger UI文档,我们需要声明一个SpringBootApplication并添加三个bean:

@Bean
SpringDocConfiguration springDocConfiguration() {
    return new SpringDocConfiguration();
}

@Bean
SpringDocConfigProperties springDocConfigProperties() {
    return new SpringDocConfigProperties();
}

@Bean
ObjectMapperProvider objectMapperProvider(SpringDocConfigProperties springDocConfigProperties) {
    return new ObjectMapperProvider(springDocConfigProperties);
}

最后,我们需要确保YAML描述文件位于resources/static目录下,并更新application.properties,指定我们不想从控制器生成Swagger UI,而是从YAML文件:

springdoc.api-docs.enabled=false
springdoc.swagger-ui.url=/api.yaml

现在我们可以启动应用程序:

mvn spring-boot:run

通过http://localhost:8080/swagger-ui/index.html可以访问Swagger UI。

我们可以看到有一个下拉菜单,可以在汽车和自行车示例之间切换:

swaggerResponse1

响应的Schema也正确显示:

swaggerResponse2

使用OpenAPI 2实现两种不同的响应

在OpenAPI 2中,没有oneOf。让我们找一个替代方案。

4.1. 构建描述文件

最好的做法是定义一个包含汽车和自行车所有属性的包装对象。 公共属性将是必需的,而只属于其中一个的属性将保持可选:

CarOrBike:
  description: a car will have an owner and a plate, whereas a bike has an owner and a speed
  type: object
  required:
    - owner
  properties:
    owner:
      type: string
    plate:
      type: string
    speed:
      type: integer

我们的API响应将是CarOrBike对象。我们会增加更多描述。遗憾的是,我们不能添加多个示例,所以我们决定只给出一个汽车的例子。

让我们看看最终的api.yaml

swagger: 2.0.0
info:
  title: Demo api
  description: Demo api for the article 'specify two responses with same code based on optional parameter'
  version: 0.1.0
paths:
  /vehicle:
    get:
      responses:
        '200':
          description: Get a vehicle. Can contain either a Car or a Bike
          schema:
            $ref: '#/definitions/CarOrBike'
          examples:
            application/json:
              owner: baeldung
              plate: AEX305
              speed:
definitions:
  Car:
    type: object
    properties:
      owner:
        type: string
      plate:
        type: string
  Bike:
    type: object
    properties:
      owner:
        type: string
      speed:
        type: integer
  CarOrBike:
    description: a car will have an owner and a plate, whereas a bike has an owner and a speed
    type: object
    required:
      - owner
    properties:
      owner:
        type: string
      plate:
        type: string
      speed:
        type: integer

4.2. 生成Java类

让我们调整swagger-codegen插件配置以解析OpenAPI 2文件。为此,我们需要使用该插件的2.x版本。 它位于另一个包中:

<plugin>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>2.4.27</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/static/api.yaml</inputSpec>
                <language>spring</language>
                <configOptions>
                    <java8>true</java8>
                    <interfaceOnly>true</interfaceOnly>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

现在看看生成的文件:

  • CarOrBike对象包含预期字段,车主字段带有@NotNull注解
  • VehicleApi定义了端点:向此端点发送GET请求将返回一个CarOrBike对象

4.3. 生成Swagger UI文档

我们可以通过与3.3节相同的方式生成文档。

我们可以看到描述内容:

swaggerResponse3

以及我们预期的CarOrBike模型描述:

SwaggerResponse4

结论

在这个教程中,我们了解了如何为一个可以返回一个对象或另一个对象的端点编写OpenAPI规范。我们利用YAML描述文件通过swagger-codegen生成Java代码,并使用springdoc-openapi-ui生成Swagger UI文档。

如往常一样,代码可在GitHub上找到