OpenAPI的详细指南

671 阅读9分钟

自Swagger诞生以来,OpenAPI已经走过了漫长的道路。它有很好的工具,在所有的会议上被谈论,被政府、主要银行、医疗机构、GitHubStripe等各种机构使用。每个人都在用它为REST的世界带来类型系统的好处,多年来,很多人把REST与Rando-JSON-over-HTTP混淆了。尽管如此,仍有一些奇怪的问题需要解决,其中一个例子就是例子。

OpenAPI v3.0规范中关于如何添加实例的信息相当简短,在文件的几个部分提到了各种类型的实例对象。作为OpenAPI的用户,我在尝试学习如何添加示例的过程中有些困惑,因为SmartBear的这个教程只是让我更清楚一些。

在模式对象、参数和媒体类型对象(请求和响应)中都有例子。有时有例子,有时有例子,这些看起来也不一样,这取决于它们的位置。 OpenAPI v2(OAS2)和OpenAPI v3(OAS3)处理例子的方式不同。OAS2缺少一些类型,OAS3增加了新的方式,在某些步骤中保留了旧的方式,并改变了OAS2中一些事情的方式。😳

我在Stoplight从事工具工作的时间,以及对OpenAPI本身的贡献,只是让这个混乱的问题更加明显。

媒体类型

在OAS3媒体类型对象中添加例子,允许你创建一个完整的请求或响应例子。你可以展示一些不同类型的成功,如果你支持多态性,你可以创建一只猫和一只狗,有不同的猫或狗的相关属性。

有两个关键词可以创建媒体类型的例子:exampleexamples 。这些关键字之间不仅有s ,它们的形状也不同。example 是单数的例子,它只包含实际的例子值:

# OpenAPI v3 

responses:
  "200":
    description: OK
    content:
      application/json:
        example:
          id: 1
          name: get food
          completed: false
        schema:
          properties:
            id:
              type: integer
            name:
              type: string
            completed:
              type: boolean
            completed_at:
              type: string
              format: date-time
              nullable: true
          required:
            - id
            - name
            - completed

examples 是一个数组对象,它有一个任意的字符串,作为该例子的昵称,该属性是另一个对象,它包含几个可选的属性,包括一个 属性,然后包含实际的例子。value

# OpenAPI v3.0

responses:
  "200":
    content:
      application/json:
        examples:
          Incomplete Task:
            value:
              id: 1
              name: get food
              completed: false
          Complete Task:
            value:
              id: 2
              name: get cider
              completed: true
              completed_at: 2020-08-23T13:22:52.685Z
        schema:
          properties:
            id:
              type: integer
            name:
              type: string
            completed:
              type: boolean
            completed_at:
              type: string
              format: date-time
              nullable: true
          required:
            - id
            - name
            - completed

在OAS3中,像 "未完成的任务 "或 "完整的任务 "这样的例子名称是任意的,大多数文档工具会显示它,以帮助用户挑选他们想看的例子。 Github用它来显示按路径请求版本库内容时的各种响应:可能是文件、目录、符号链接或子模块,所以他们对每种情况都有不同的例子:

# OpenAPI v3.0

responses:
  '200':
    description: response
    content:
      application/vnd.github.v3.object:
        schema:
          "$ref": "#/components/schemas/content-tree"
      application/json:
        schema:
          oneOf:
          - "$ref": "#/components/schemas/content-directory"
          - "$ref": "#/components/schemas/content-file"
          - "$ref": "#/components/schemas/content-symlink"
          - "$ref": "#/components/schemas/content-submodule"
        examples:
          response-if-content-is-a-file:
            "$ref": "#/components/examples/content-file-response-if-content-is-a-file"
          response-if-content-is-a-directory:
            "$ref": "#/components/examples/content-file-response-if-content-is-a-directory"
          response-if-content-is-a-symlink:
            "$ref": "#/components/examples/content-file-response-if-content-is-a-symlink"
          response-if-content-is-a-submodule:
            "$ref": "#/components/examples/content-file-response-if-content-is-a-submodule"

这看起来有点像这样:

来自GitHub OpenAPI描述的多个命名的例子,在ReDoc

中显示,这是一个免费的开源文档生成工具。

有这两种不同类型的例子,它们的形状相当不同,可能会让一些人感到困惑,但如果你看一下OAS2,就会更加困惑了:

# OpenAPI v2.0

responses:
  '200':
    description: 'OK'
    schema:
      properties: 
        id:
          type: integer
        name:
          type: string
        completed: 
          type: boolean
        completed_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - completed
    examples:
      application/json:
        id: 2
        name: get cider
        completed: true
        completed_at: 2020-08-23T13:22:52.685Z

尽管都使用了examples 这个关键字,但OAS2和OAS3在处理这个关键字的方式上有所不同,OAS2只为API被定义为生产/消费的每种mime类型处理一个单一的例子,而OAS3则允许有多个名字任意的例子。

请注意,这些例子都是在schema 关键字旁边定义的,而不是在它里面。如果一个例子被定义在模式对象里面,就会有完全不同的规则...

模式实例

模式对象在OAS2和OAS3中都有多处使用:请求和响应是最常见的两个。一个模式可以为整个对象、对象的一部分或该对象中的一个特定属性提供一个例子:

# OpenAPI v3 

responses:
  "200":
    description: OK
    content:
      application/json:
        schema:
          properties:
            id:
              type: integer
            name:
              type: string
            completed:
              type: boolean
            completed_at:
              type: string
              format: date-time
              nullable: true
          required:
            - id
            - name
            - completed
          example:
            id: 2
            name: get cider
            completed: true
            completed_at: 2020-08-23T13:22:52.685Z

这看起来和我们进一步看的媒体类型的例子很相似,没有什么有趣的,除了它在模式对象里面而不是在它旁边的事实。如果你维护OpenAPI工具,这可能会让你受挫,所以请检查你的工具是否支持它。

除了在模式内给整个模式提供例子外,你还可以在属性层面为单个属性创建例子:

responses:
  '200':
    description: 'OK'
    content:
      application/json:
          schema:
              properties: 
              id:
                  type: integer
              name:
                  type: string
                  example: get food
              completed: 
                  type: boolean
                  example: false
              completed_at:
                  type: string
                  format: date-time
                  example: '1955-04-23T13:22:52.685Z'
              required:
              - id
              - name
              - completed

OAS2和OAS3至少在这个逻辑上是一致的,但这对工具人员来说可能是相当混乱的。当你有这样的例子时,它们应该根据它们的模式或属性的例子进行验证。

如果正在渲染文档,就从相关的mime类型的例子开始--如果有多个媒体类型,就从该媒体类型的第一个相关例子开始--如果没有这些例子,就用openapi-sampler或类似的方法从属性例子中构建一个例子。我们也用它来生成Prism(HTTP模拟服务器)的模拟响应。

如果你只是想写OpenAPI,我一般喜欢使用属性例子的方法(example 在每个属性上),只要没有可以作为例子的东西。如果定义了一个default ,那就会被使用。如果定义了一个枚举,就会使用这些值中的一个。如果我想创建一些比从模式属性中生成的例子更实用的东西,我会定义一个媒体类型的例子,特别是当有效载荷是动态的和复杂的,并且我想显示字段的特定组合而不是所有的东西。

参数实例

OAS3参数对象描述了路径参数、查询参数、头文件等。在OAS3中,他们可以有examples 或一个example ,或者由于OAS3让参数对象有一个模式,他们可以有模式的例子,就像我们上面谈到的......那是三件事:

# OpenAPI v3

/params:
  get:
    parameters:
      - name: single-example-good
        description: Valid to its schema
        in: query
        schema: 
          type: string
          enum: [foo, bar]
        example: foo

      - name: single-schema-example-good
        description: Valid to its schema
        in: query
        schema: 
          type: string
          enum: [foo, bar]
          example: foo

      - name: multiple-examples
        description: Some valid to its schema some not
        in: query
        schema: 
          type: string
          enum: [foo, bar]
        examples: 
          the-good:
            value: foo
          the-bad:
            value: 123
          the-ugly:
            value: [an, array]

AGH那是一个很多地方可以检查的例子。

OAS2没有让参数有一个例子,至少不是正式的。OAS2规范没有定义参数如何工作,但 "供应商扩展 "来了,让任何人使用x-example 关键字。根据你所使用的工具,它将知道这个关键词的含义,或者完全忽略它:

# OpenAPI v2.0
parameters:
  - name: fruit
    description: Short name of the fruit you're trying to find
    in: query
    type: string
    x-example: apple

OAS2参数对象也没有schema 关键字,所以你不必像OAS3那样担心那个 "第三地"。

AGH

是的。这在Stoplight已经引起了一段时间的混乱,最近当我们试图确保Spectral 正确地验证所有的例子时,就出现了问题。我们主要是支持模式实例,即使如此,也主要是支持属性实例的方法,但我们已经投入了一大堆努力来支持所有的实例,以便未来版本的Spectral和Stoplight Studio能够让你知道你是否未能成功地穿越这个雷区。

OpenAPI v3.1也部分地解决了这个问题,并给他增加了一些燃料,因为JSON Schema有它自己的examples 关键字。这个多例关键词与OAS2或OAS3中的任何examples ,它只是一个模式或属性的可能值的裸阵列:

schema:
    properties:
        coordinates:
            description: We couldn't pick a format for coordinates so we support
            pretty much all of them.
            examples: 
            - "52.3667° N, 4.8945° E"
            - "52.377956, 4.897070"
            - [52.377956, 4.897070]
            - { lat: 52.377956, lon: 4.897070 }

如果这和媒体类型或参数examples 关键字一样,你需要给它一个mime类型的键,或一个任意的键,并将值嵌套在value ,但它是第三个完全不同类型的examples...

你不能在OAS2或OAS3中使用这种方法,但当OpenAPI v3.1发布时,你将能够使用它,因为它现在正确解决了JSON Schema的分歧。所以,支持JSON Schema是好的,但是......要用另一种方式来处理例子是很难的。

这些都是有效的,而且各种组合可以而且确实存在:

/infinite-examples:
  get:
    operationId: infinite-examples
    responses:
      "200":
        description: OK
        content:
          application/json:
            schema:
              properties:
                name:
                  type: string
                  # OpenAPI Schema Object Example
                  example: stowford
                coordinates:
                  description: We couldn't pick a format for coordinates so we support
                  pretty much all of them.
                  # JSON Schema Examples from 2019-09
                  examples:
                  - "52.377956, 4.897070"
                  - [52.377956, 4.897070]
                  - { lat: 52.377956, lon: 4.897070 }
              required:
                - name
                - coordinates
              
              # OpenAPI Schema Object Example (but for an object)
              example:
                name: freddy
                coordinates: "52.377956, 4.897070"

            # OpenAPI Media Type Example
            example:
              name: finn
              coordinates: "52.377956, 4.897070"

            # OpenAPI Media Type Examples
            # cannot have this and the OpenAPI Media Type Example together
            examples:
              arbitrary example name:
                value:
                  name: finns evil twin
                  coordinates: "52.377956, 4.897070"                  

如果我们要走出这个困境,我们需要终端用户和工具人员的参与:

  1. 现在就把你的OpenAPI描述升级到OpenAPI v3.0,如果你的OpenAPI工具是更现代的,就切换到

如果你的旧工具不支持OpenAPI,请切换到更现代的OpenAPI工具。这样,我们都有希望用火烧掉Ye Olden OAS2,并远离供应商的扩展黑客。

  1. 让我们简化OAS3.1,但删除其中一些多余的例子方法。也许从OAS3.1的参数对象中废除exampleexamples ,让模式实例接管?或者从所有地方删除example ,这样就只剩下OpenAPIexamples 和JSON Schemaexamples

  2. 如果你维护工具,请在OAS3.1仍是候选发布版时加入对它的支持。

候选版本。开始使用其他OAS 3.1的工具,以确保它的工作,这将加速每个人的迁移,当它最终完成。

  1. 最终,OAS2和OAS3将成为遥远的记忆,无论是用户还是工具供应商,都不需要弄清楚这一团糟。

如果你想在OAS2和OAS3的例子中搞清楚更多的例子,无论什么原因,都可以看看这个样本库