DRY up the PATCH <- POST <- GET inheritance chain
See original GitHub issueI 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:
- Created 3 years ago
- Reactions:10
- Comments:5 (4 by maintainers)
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:
The model is defined once but the constraints vary by the operation.
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.