OpenAPI-Specification
OpenAPI-Specification copied to clipboard
Allow versioning at path:method level
To facilitate the option of media-type versioning, it would be helpful to version at the path:method level. Here is the gist of my proposal: move version from root level to path:method level and represent as an array of versions. In making the change as described, my hope is that none of the existing functionality is sacrificed.
Single version example
/pets/{id}:
put:
tags:
- "pets"
versions:
- version: "default"
summary: "..."
description: "..."
... all other properties formerly defined in the path-item
Multiple version example
/pets/{id}:
put:
tags:
- "pets"
versions:
- version: "1"
summary: "..."
description: "..."
operationId: "updatePet_v1"
consumes:
- "application/json"
- "application/vnd.vendor.v1+json"
deprecated: true
... all other properties currently defined in the path-item
- version: "2"
summary: "..."
description: "..."
operationId: "updatePet_v2"
consumes:
- "application/vnd.vendor.v2+json"
... all other properties currently defined in the path-item
Your use of consumes leads me to think you are using / suggesting a change to OAS 2.0.
The 2.0 specification is no longer being worked on (for nearly 3 years), and OAS 3.0 should address your use-case for media-type versioning of request and response bodies.
Converters are available to ease the transition to 3.0.
If this answers your question, please could you close this issue, thanks.
@MikeRalphson, you are correct, I used the wrong version in my initial description, however, the problem definitely exists in OAS3 (and is what I initially meant). Let me try and redeem myself:
Consider the following OAS3 configuration of a media-type versioned endpoint:
openapi: 3.0.1
info:
title: Swagger Petstore
description: '...'
version: 1.0.0
tags:
- name: pets
description: Everything about your Pets
paths:
/pets/{id}:
put:
tags:
- pets
summary: Update an existing pet
operationId: updatePet
requestBody:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v1+json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v2+json:
schema:
$ref: '#/components/schemas/Pet_v2'
required: true
responses:
400:
description: Invalid ID supplied
content: {}
404:
description: Pet not found
content: {}
405:
description: Validation exception
content: {}
security:
- petstore_auth:
- write:pets
In the above yaml code block, the operation detailed shows three media-types listed. The first two are in support of version 1 of the endpoint, the last is for version 2 of the endpoint. The following issues become readily apparent:
- deprecation: How do we deprecate the v1 media-types? In the OAS3 specification, deprecation occurs at the operation level, not within a content block.
- documentation: How do we add documentation specific to version 2 of the endpoint? The OAS3 specification does not provide a method of augmenting/replacing the operation-level documentation.
- security: How do we specify different security requirements for different versions of an endpoint? Again, because OAS3 targets the operation as the unit of focus, there is no way to accomplish this.
- ... and on an on for each element of an operation.
Although initially it seems reasonable to consider a single operation the focus, when you apply the OAS3 specification to common web framework specifications, e.g. jax-rs, the problem becomes even more apparent. Consider this Jersey code:
@Path("pets")
public class PetsResource {
@Path("{id}")
@PUT
@Consumes({"application/json", "application/vnd.vendor.v1+json"})
@Operation(summary = "Updates an existing pet.",
deprecated = true,
responses = {
@ApiResponse(responseCode = "400", description = "Invalid ID supplied."),
@ApiResponse(responseCode = "404", description = "Pet not found.") },
security = @SecurityRequirement(name="petstore_auth")
)
public void updatePet(@PathParam("id") int id, Pet pet) {
// ...
}
@Path("{id}")
@PUT
@Consumes({"application/vnd.vendor.v2+json"})
@Operation(summary = "Updates an existing pet with new pet extensions.",
responses = {
@ApiResponse(responseCode = "400", description = "Invalid ID supplied."),
@ApiResponse(responseCode = "404", description = "Pet not found.") },
security = @SecurityRequirement(name="petstore_auth")
)
public void updatePetV2(@PathParam("id") int id, PetV2 pet) {
// ...
}
}
In the above Jersey/JAX-RS code block, Jersey will route to the appropriate method, but Swagger-UI has the impossible task of determining which operation to extract into it's open-api.json file on build (currently, Swagger-UI only retains the last read path:method operation). Also, notice the discrepancies in the operation definition: summary and deprecated.
To address these issues, I propose:
- Making the API version in the root of the OpenAPI configuration the default version of each endpoint instead of the overall API version (overridden when the operation-details version is not specified - see next bullet item for details).
- Adding an array of operation-details to a path:operation. I'm defining an operation-detail object almost equivalent to the existing operation, with two exceptions: the tag element should remain on the parent object, and a version element should be added such that the version element is the unique identifier for the set of operation-details within a path:operation.
- Remove the current operation as the definition of a method in favor of a new object holding the tag element and versions element for the operation-details array.
With the above changes, the beginning yaml configuration would become:
openapi: 4.0.0 (I see no way for this not to be a breaking change)
info:
title: Swagger Petstore
description: '...'
tags:
- name: pets
description: Everything about your Pets
paths:
/pets/{id}:
put:
tags:
- pets
versions:
- version: 1
summary: Update an existing pet
deprecated: true
operationId: updatePet
requestBody:
description: Pet object that needs to be updated in the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v1+json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
400:
description: Invalid ID supplied
content: {}
404:
description: Pet not found
content: {}
security:
- petstore_auth:
- write:pets
- version: 2
summary: Update an existing pet with new extensions
operationId: updatePet_v2
requestBody:
description: Pet object, with newly defined extensions, that needs to be updated in the store.
content:
application/vnd.vendor.v2+json:
schema:
$ref: '#/components/schemas/Pet_v2'
required: true
responses:
404:
description: Pet not found
content: {}
security:
- petstore_auth:
- write:pets
As the OAS3 specification currently stands, media-type versioning is implicitly discouraged in favor of path-versioning. I do not believe this was the intent, but it does have that affect.
Sorry for the initial confusion, I hope this clarifies things.
Please don't forget to put the version of the request in a relation to the version of the response. A common case is that a service receiving a v1 request is only able to reply with a v1 response and v2 -> v2 (but not v1). Versions at path:method level would address this perfectly.
@gadton Thanks for pointing out another deficiency in the current 3.x spec with regards to media type versioning. To ensure I completely understand, let me restate.
Consider media-type versioning with the current 3.x spec:
openapi: 3.0.1
...
paths:
/pets:
post:
tags:
- pets
summary: Create a new pet
operationId: createPet
requestBody:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v1+json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v2+json:
schema:
$ref: '#/components/schemas/Pet_v2'
required: true
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v1+json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v2+json:
schema:
$ref: '#/components/schemas/Pet_v2'
...
In the above scenario, it is unclear what request content-type/response content-type combinations are legal. The best we could do is add a 406 response code with documentation indicating valid combinations. However, a move to versioning at the path:method level, we get this:
openapi: 4.0.0
...
paths:
/pets:
post:
tags:
- pets
versions:
- version: 1
summary: Create a new pet
operationId: createPet
requestBody:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v1+json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/vnd.vendor.v1+json:
schema:
$ref: '#/components/schemas/Pet'
- version: 2
summary: Create a new version 2 pet
operationId: createPet_v2
requestBody:
description: Pet object that needs to be added to the store
content:
application/vnd.vendor.v2+json:
schema:
$ref: '#/components/schemas/Pet_v2'
required: true
responses:
200:
description: successful operation
content:
application/vnd.vendor.v2+json:
schema:
$ref: '#/components/schemas/Pet_v2'
...
The intent is clear.
@MikeRalphson, when you have a moment, will you review my follow-up comments on this issue (#2142)? If I am still missing something, I am happy to close the issue. Thank you for your consideration.
Versioning alone would not cut it.
Multiple mapping can be done on a single path, which are not simply different content types (e.g. application/json vs application/xml) nor different versions through content negotiation (e.g. application/vnd.something.v1+json vs application/vnd.something.v2+json).
A simple scenario where two (or more) functionalities reside on the same path and http method. Besides all other metadata (description, deprecated, etc), even items like tags should be moved into the subsections.
Below a quick example, where two GET operations delivery different data types and different data type versions on the same path and method:
- User summary v1 (json and xml) (tagged as deprecated)
- User summary v2 (json)
- User detail v1 (json)
openapi: 4.0.0
...
paths:
/users:
get:
- description: retrieves the user summaries (deprecated)
tags:
- user summary
operationId: user summaries v1
deprecated: true
responses:
200:
content:
application/vnd.vendor.user-summary.v1+json:
schema:
$ref: '....'
application/vnd.vendor.user-summary.v1+xml:
schema:
$ref: '....'
- description: retrieves the user summaries and adds this fancy new extra description information
tags:
- user summary
operationId: user summaries v2
responses:
200:
content:
application/vnd.vendor.user-summary.v2+json:
schema:
$ref: '....'
- description: retrieves the user details of multiple users and this has a response with way more fields and also a different description and even a nice query param that the others don't have
tags:
- user detail
operationId: user details v1
parameters:
- name: system
in: query
required: true
schema:
type: string
responses:
200:
content:
application/vnd.vendor.user-detail.v1+json:
schema:
$ref: '....'
deprecation: How do we deprecate the v1 media-types? In the OAS3 specification, deprecation occurs at the operation level, not within a content block.
I would:
- deprecate the schema, not the operation. see http://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3
- return a content warning (eg. see https://tools.ietf.org/id/draft-cedik-http-warning-02.html it's not yet a standard)
documentation: How do we add documentation specific to version 2 of the endpoint There's no v2 endpoint imho, but a v2 media-type. The operation is the same.
it is unclear what request content-type/response content-type combinations are legal. The best we could do is add a 406 response code with documentation indicating valid combinations
This is the correct way to handle it, and the one which will better describe the endpoint.
How do we specify different security requirements for different versions of an endpoint
If you need different operations and security requirements, I'd use different paths or path-versioning then, because those are different operations.
My2¢, R
I understand that this issue/feature request is still unresolved.
Still, I'm wondering: what is the current best practice? Path versioning should be quite common when your API starts to grow, so perhaps I'm misunderstanding some part of the specification, while the solution is obvious to others?