Skip to content

DRY up the PATCH <- POST <- GET inheritance chain #2201

Closed
@am17torres

Description

@am17torres

I think it's fair to say that a pretty common API pattern might include the following resource routes for a hypothetical model schema. If we were to define the models using the minimum OpenApi 3.0 Specification it would look something like this:

openapi: 3.0.0
info:
  title: My API
  version: '1.0.0'
paths:

  /model:
    post:
      summary: Create Model
      requestBody:
        $ref: '#/components/requestBodies/ModelPost'
      responses:
        201:
          $ref: '#/components/responses/Model'

  /model/{id}/:
    parameters:
      - $ref: '#/components/parameters/id'

    get:
      summary: Get Model by ID
      responses:
        200:
          $ref: '#/components/responses/Model'

    patch:
      summary: Update Model by ID
      requestBody:
        $ref: '#/components/requestBodies/ModelPatch'
      responses:
        200:
          $ref: '#/components/responses/Model'
          

components:

  parameters:
    id: 
      name: id
      in: path
      description: The id of the resource
      schema:
        type: string
      required: true
      
  requestBodies:
    ModelPost:
      content:
          application/json:
            schema:
              $ref: '#/components/schemas/ModelPost'
              
    ModelPatch:
      content:
          application/json:
            schema:
              $ref: '#/components/schemas/ModelPatch'
    
  responses:
    Model:
      description: The Model
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Model'
            
  schemas:
    ModelPatch:
      required:
        - prop1
      properties:
        prop1:
          type: string
          description: required POST property, required PATCH property
        prop2:
          type: string
          description: required POST property, optional PATCH property
        prop3:
          type: string
          description: optional POST property, optional PATCH property
    
    ModelPost:
      allOf:
        - $ref: '#/components/schemas/ModelPatch'
      required: 
        - prop2
    
    Model:
      allOf:
        - $ref: '#/components/schemas/ModelPost'
      required: 
        - id
      properties:
        id:
          type: string

In the case of PATCH we need to mark most, if not all, properties as optional. For POST we override the list of required properties to include the minimum set necessary for object creation. Finally, we need to create a third model which contains the id field after a successful POST. The third model containing the id property is also used as the response object for GET requests.

As I see it, this issue stems from how required properties are handled. We could significantly reduce the number of models if the required list was augmented to accepted one or more http verbs.

Instead having the two separate request bodies and three schemata above, we could instead have a model that looks something like this.

 requestBodies:
    Model:
      content:
          application/json:
            schema:
              $ref: '#/components/schemas/Model'
schemas:
    Model:
      required:
        # this works as before, it is required in all cases
        - prop1 
        # this property is only required on GET and POST routes
        - prop2: [GET, POST]
        # this property is only required on GET routes.
        - id: GET
      properties:
        id:
          type: string
          description: required GET property
        prop1:
          type: string
          description: required GET, POST and PATCH property
        prop2:
          type: string
          description: required POST property, optional PATCH property
        prop3:
          type: string
          description: optional for all verbs

This approach is fully backwards compatible with the existing spec and offers spec writers to cut back on boilerplate!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Moved to MoonwalkIssues that can be closed or migrated as being addressed in Moonwalk

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions