如何正确使用 OpenAPI 使用补丁方法更新资源?

Dri*_*leX 8 api rest swagger openapi

我最近在学习 OpenAPI,想了解最佳实践。假设我有一个名为 的资源Person,它的定义components/schemas如下:

Person:
  type: object
  required:
    - id
    - name
    - age
  properties:
    id:
      readOnly: true
      type: integer
    name:
      type: string
    age:
      type: integer
Run Code Online (Sandbox Code Playgroud)

我已将idreadOnly设为只读,因为当我执行postor 时patch,ID 将作为 URL 的一部分传递。请参阅https://swagger.io/docs/specification/data-models/data-types/

name并且age必须在客户端尝试使用post方法创建新人员或get人员时出现,因此它们被定义为required

我的问题是patch:如果我只想更新一个人的agename独立的怎么办?理想情况下,我想做类似的事情

PATCH /person/1

{"age": 40}
Run Code Online (Sandbox Code Playgroud)

但是,由于我已name按要求进行了定义,因此我无法做到。我可以想到几种解决方案,但它们都有缺陷:

  1. 解除required限制。但如果我这样做,我将失去对post和的验证get
  2. 为 使用单独的模式patch,例如PersonUpdate,使用required删除。显然,这会导致冗余。
  3. 当我这样做时patch,我确实传递了所有字段,但是对于那些我不想更新的字段,我传递了一个无效值,例如
PATCH /Person/1

{"age": 40, "name": null}
Run Code Online (Sandbox Code Playgroud)

并制作所有字段nullable,并让服务器忽略这些值。但是如果我想null在 DB 中设置 name呢?

  1. PUT用于更新,并始终传递所有必填字段。但是如果我的数据已经过时怎么办?例如当我做
PUT /Person/1

{"age": 40, "name": "Old Name"}
Run Code Online (Sandbox Code Playgroud)

其他一些客户端已经更改name为“新名称”,我正在覆盖它。

  1. 与方法 3 类似,但我在执行时传递了额外的字段patch以指示服务器应该关心的字段,无论是使用像 的查询参数?fields=age,还是将其添加到 JSON 正文中。所以我可以把它改成requestBody类似的东西
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/Person'
                - type: object
                  properties:
                    _fields:
                      type: array
                      items:
                        type: string
Run Code Online (Sandbox Code Playgroud)

然后我可以这样做

PATCH /Person/1

{"age": 40, "name": null, _fields: ["age"]}
Run Code Online (Sandbox Code Playgroud)

这样一来,我可以更新namenull以及

PATCH /Person/1

{"age": 40, "name": null, _fields: ["age", "name"]}
Run Code Online (Sandbox Code Playgroud)

这种方法似乎可行,但有没有更好或被广泛接受的做法?

mul*_*008 13

我想出了以下解决方案:

Person:
  type: object
  allOf:
    - $ref: '#/components/schemas/PersonProperties'
    - required:
      - id
      - name
      - age
UpdatePerson:
  type: object
  allOf:
    - $ref: '#/components/schemas/PersonProperties'
PersonProperties:
  type: object
  properties:
    id:
      readOnly: true
      type: integer
    name:
      type: string
    age:
      type: integer
Run Code Online (Sandbox Code Playgroud)

PersonProperties充当构成人员模型的简单属性集合。它没有指定任何required字段。

Person重用 的所有属性PersonProperties,此外还指定需要哪些属性。PersonUpdate重用 的所有属性PersonProperties并允许部分更新。

这个解决方案仍然感觉很hacky。此外,它不适用于嵌套对象和可为空属性的部分更新。

(事实上​​,由于idis readOnly,您还可以required: [id]在 上添加和从列表中PersonProperties删除)idrequiredPerson


chr*_*erm 9

通过将模式拆分为多个可组合的模式,我们可以将模式分层,而无需重复。我们甚至可以通过将可选属性设置为空来支持通过 PATCH 删除属性。

示例 OpenAPI 定义允许通过 PATCH 端点进行部分更新(已验证):

openapi: 3.0.0

info:
  title: Sample API with reusable schemas and partial updates (PATCH)
  version: 1.0.0

paths:
  /customers:
    post:
      tags:
        - Customer
      requestBody:
        $ref: '#/components/requestBodies/CreateCustomer'
      responses:
        201:
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerId'
    get:
      tags:
        - Customer
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Customer'
                  
  /customers/{CustomerId}:
    get:
      tags:
        - Customer
      parameters:
        - $ref: '#/components/parameters/CustomerId'
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
                  
    put:
      tags:
        - Customer
      requestBody:
        $ref: '#/components/requestBodies/CreateCustomer'
      parameters:
        - $ref: '#/components/parameters/CustomerId'
      responses:
        204:
          description: Updated
          
    patch:
      tags:
        - Customer
      requestBody:
        description: Update customer with properties to be changed
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/CustomerProperties'
                - type: object
                  properties:
                    Segment:
                      nullable: true
      parameters:
        - $ref: '#/components/parameters/CustomerId'
      responses:
        204:
          description: Updated

components:
  schemas:
    CustomerProperties:
      type: object
      properties:
        FirstName:
          type: string
        LastName:
          type: string
        DOB:
          type: string
          format: date-time
        Segment:
          type: string
          enum:
            - Young
            - MiddleAged
            - Old
            - Moribund

    CustomerRequiredProperties:
      type: object
      required:
        - FirstName
        - LastName
        - DOB

    Id:
      type: integer
      
    CustomerId:
      type: object
      properties:
        Id:
          $ref: '#/components/schemas/Id'
          
    Customer:
      allOf:
        - $ref: '#/components/schemas/CustomerId'
        - $ref: '#/components/schemas/CustomerProperties'
        - $ref: '#/components/schemas/CustomerRequiredProperties'
   
  parameters:
    CustomerId:
      name: CustomerId
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/Id'
        
  requestBodies:
    CreateCustomer:
      description: Create a new customer
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/CustomerProperties'
              - $ref: '#/components/schemas/CustomerRequiredProperties'
Run Code Online (Sandbox Code Playgroud)

改编自: https: //stoplight.io/blog/reuse-openapi-descriptions/