OpenAPITools / OpenAPITools/openapi-generator

[REQ] Media type versioning

Open
#3,569 4 comments 21 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Enhancement: Feature
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Problem

It is not possible to use the Accept Header in the request for versioning endpoint rest services (instead of URI Versioning).

by example, We want versioning the "findStock" method using the content-type for the same path "/stock" in this contract first:

  /stock:
    get:
      tags:
        - stock
      summary: Get stock
      operationId: findStock
      responses:
        '200':
          description: A paged array of Inventory Items
          content:
            "application/vnd.mecstkac.v1.0.0+json":
              schema:
                $ref: "#/components/schemas/InventoryItemV1"
            "application/vnd.mecstkac.v1.0.1+json":
              schema:
                $ref: "#/components/schemas/InventoryItemV2"

When we generate the code from the contract described above, it generates an API with a single "findStock" method that returns only one type of response:

    @ApiResponses(value = { 
        @ApiResponse(code = 200, message = "A paged array of Inventory Items", response = InventoryItemV1.class) })
    @RequestMapping(value = "/stock",
        produces = { "application/vnd.mecstkac.v1.0.0+json", "application/vnd.mecstkac.v1.0.1+json" }, 
        method = RequestMethod.GET)
    default ResponseEntity<InventoryItemV1> findStock() {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf(""))) {
                    ApiUtil.setExampleResponse(request, "", "");
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

    }

Solution

Use the Accept Header in the request for versioning endpoint rest. By example, We want versioning the "findStock" method using the content-type for the same path "/stock" in this contract first:

  /stock:
    get:
      tags:
        - stock
      summary: Get stock
      operationId: findStock
      responses:
        '200':
          description: A paged array of Inventory Items
          content:
            "application/vnd.mecstkac.v1.0.0+json":
              schema:
                $ref: "#/components/schemas/InventoryItemV1"
            "application/vnd.mecstkac.v1.0.1+json":
              schema:
                $ref: "#/components/schemas/InventoryItemV2"

-> if content-type is "application/vnd.mecstkac.v1.0.0+json" return response "InventoryItemV1"
-> if content-type is "application/vnd.mecstkac.v1.0.1+json" return response "InventoryItemV2"

the self-generated code for server interface would be something like :

  @GetMapping(value = "/stock", produces = "application/vnd.mecstkac.v1.0.0+json")
  public ResponseEntity<InventoryItemV1> findStockV1_0_0() {
    getRequest().ifPresent(request -> {
        for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
            if (mediaType.isCompatibleWith(MediaType.valueOf(""))) {
                ApiUtil.setExampleResponse(request, "", "");
                break;
            }
        }
    });
    return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
  }

  @GetMapping(value = "/stock", produces = "application/vnd.mecstkac.v1.0.0+json")
  public ResponseEntity<InventoryItemV2> findStockV1_0_1() {
    getRequest().ifPresent(request -> {
        for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
            if (mediaType.isCompatibleWith(MediaType.valueOf(""))) {
                ApiUtil.setExampleResponse(request, "", "");
                break;
            }
        }
    });
    return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
  }

the self-generated code for client interface would be something like :

    public InventoryItemV1 findStockV1_0_0() throws RestClientException {
        Object postBody = null;
        
        String path = UriComponentsBuilder.fromPath("/stock").build().toUriString();

        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders headerParams = new HttpHeaders();
        final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();

        final String[] accepts = { "application/vnd.mecstkac.v1.0.0+json" };
        final List<MediaType> accept = apiClient.selectHeaderAccept(accepts);
        final String[] contentTypes = { };
        final MediaType contentType = apiClient.selectHeaderContentType(contentTypes);

        String[] authNames = new String[] {  };

        ParameterizedTypeReference<InventoryItemV1> returnType = new ParameterizedTypeReference<InventoryItemV1>() {};
        return apiClient.invokeAPI(path, HttpMethod.GET, queryParams, postBody, headerParams, formParams, accept, contentType, authNames, returnType);
    }

    public InventoryItemV2 findStockV1_0_1() throws RestClientException {
        Object postBody = null;
        
        String path = UriComponentsBuilder.fromPath("/stock").build().toUriString();

        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders headerParams = new HttpHeaders();
        final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();

        final String[] accepts = { "application/vnd.mecstkac.v1.0.1+json" };
        final List<MediaType> accept = apiClient.selectHeaderAccept(accepts);
        final String[] contentTypes = { };
        final MediaType contentType = apiClient.selectHeaderContentType(contentTypes);

        String[] authNames = new String[] {  };

        ParameterizedTypeReference<InventoryItemV1> returnType = new ParameterizedTypeReference<InventoryItemV1>() {};
        return apiClient.invokeAPI(path, HttpMethod.GET, queryParams, postBody, headerParams, formParams, accept, contentType, authNames, returnType);
    }

for both reactive and non-reactive implementation.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the Java generator entry points for Spring server and client interfaces from the OpenAPI response content definitions. Compare reactive and non-reactive generation paths, then verify that media-type versions produce distinct methods and response types for both server and client interfaces.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi, spring
Domain
api, backend, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.