question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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!

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:10
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
chandra-ghcommented, Apr 13, 2020

I have been using OpenAPI for sometime and I feel that defining operation specific constraints in the API spec can be improved. Right now “operation constraints” need to be baked into the model leading to multiple copies of the same model - UserCreateModel, UserUpdateModel and UserReadModel. Each model has the same attributes but different requirements. Rather than baking the operation specific requirements into the model, it will be cleaner if the spec is extended to decouple operation specific requirements from the model.

My proposal is something along the following lines:

operation_foo:
  - input (or request)
      - model: user
      - constraints: user with an ID
  - output (or response)
      - model: user
      - constraints: user with an ID and email

operation_bar:
  - input (or request)
      - number: user_id
  - output (or response)
      - model: user
      - constraints: user with an ID, email and status

The model is defined once but the constraints vary by the operation.

0reactions
philsturgeoncommented, Apr 28, 2020

This sounds like a DSL, of which there are quite a few. There’s visual editors too. There’s no reason to write OpenAPI by hand.

Read more comments on GitHub >

github_iconTop Results From Across the Web

solidity - Upgradable contract with new inheritance
Although this thread says that a new inheritance gets its slots before the inheriting class (A) and after the last inheritance (D). So...
Read more >
DRY vs. "prefer containment over inheritance" [closed]
My own take on the issues of inheritance is that there is unintended side effects by inheriting from classes. By inheriting, dependencies are...
Read more >
Allow profiles to define a base/parent profile [#1356276] - Drupal
Yes, that post is how I learned about this issue. New features go into HEAD first though. I will admit that the existing...
Read more >
Understanding Inheritance in JavaScript Through Object ...
Everything appeared to be very black and white, cut and dry, yes or no. ... monkey patch the Object prototype, which gets messy...
Read more >
Medium-Chain Acyl-Coenzyme A Dehydrogenase Deficiency
Initial Posting: April 20, 2000; Last Update: June 27, 2019. ... MCAD deficiency is inherited in an autosomal recessive manner. At conception, the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found