OpenAPITools / OpenAPITools/openapi-generator

[REQ][Java][Spring]: make versioning with content negotiation possible

Open
#11,000 0 comments 11 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

Is your feature request related to a problem? Please describe.

Currently it is not possible to realize REST API versioning with content negotiation using the java spring generator.

Normally this would be as simple as this:

    @GetMapping(value = "/person/produces", produces = "application/vnd.company.app-v1+json")
    public PersonV1 producesV1() {
      return new PersonV1("Peter Shaw");
    }

    @GetMapping(value = "/person/produces", produces = "application/vnd.company.app-v2+json")
    public PersonV2 producesV2() {
      return new PersonV2(new Name("Bob", "Andrews"));
    }

But by using the spring generator it is not as simple.
By specifying an additional version/content type inside the openapi specification (InputParameterV1 and V2 are unrelated objects) ...

/resource:
  post:
    requestBody:
      content:
        application/vnd.company.project.api.v1+json:
          schema:
            $ref: "#/components/schemas/InputParameterV1"
        application/vnd.company.project.api.v2+json:
          schema:
            $ref: "#/components/schemas/InputParameterV2"
      responses:
        200:
          content:
            application/vnd.company.project.api.v1+json:
              schema:
                $ref: "#/components/schemas/ResponseV1"
            application/vnd.company.project.api.v2+json:
              schema:
                $ref: "#/components/schemas/ResponseV2"

... an interface with the following method is generated:

 // Generated interface method
@PostMapping(
        value = "/resource",
        produces = {
            "application/vnd.company.project.api.v1+json",
            "application/vnd.company.project.api.v2+json"
        },
        consumes = {
            "application/vnd.company.project.api.v1+json",
            "application/vnd.company.project.api.v2+json"
        })
default ResponseEntity<ResponseV1> doStuff(
        @ApiParam(value = "Input parameter", required = true) @Valid @RequestBody
                InputParameterV1 inputParameterV1) {        
   
     . . . . . . . . .

    return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}

The generator does not seem to be able to handle versioning with content negotiation. As you can see produces and consumes have both version content-types. Furthermore only ResponseV1 is returned while the only parameter possible is InputParameterV1.

Describe the solution you'd like

The generation above would be alright if you have something like produces = {"application/json", "application/xml"}, but for versioning with content negotiation it would be better if we have a new method for each version:

@PostMapping(value = "/resource",
            produces = {"application/vnd.company.project.api.v1+json"},
            consumes = {"application/vnd.company.project.api.v1+json"})
    default ResponseEntity<ResponseV1> doStuff(
            @ApiParam(value = "Input parameter", required = true) @Valid @RequestBody
                    InputParameterV1 inputParameterV1) {
					....
		}        				
					
	@PostMapping(value = "/resource",
            produces = {"application/vnd.company.project.api.v2+json"},
            consumes = {"application/vnd.company.project.api.v2+json"})
    default ResponseEntity<ResponseV2> doStuff(
            @ApiParam(value = "Input parameter", required = true) @Valid @RequestBody
                    InputParameterV2 inputParameterV2) {
					....
		}        

Describe alternatives you've considered

Since versioning with content negotiation was not possible in combination with the generated interfaces, we tried to find workarounds like manually adding/implementing V2-method inside the controller-class (+annotations) which implements the interface while v1 is overridden. It wouldn't be optimal, but at least we would be still able to use the generated interfaces. But this approach obviously doesn't work, because the generated interface-method still has both content-types (v1 and v2), so a request with content-type application/vnd.company.project.api.v2+json would lead to an error (ambiguous mapping). In the end a workaround with the currently generated interfaces couldn't be found.

To sum up it would be ideal, if the generator could generate additional interface methods for each version.

Something similar was requested in 2019 (#3569), but it was not discussed further .

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 with the Java Spring generator and its handling of OpenAPI request and response content types, then inspect the generated interface for the specification shown in the issue. Determine how separate methods could be generated for each version while retaining matching produces, consumes, parameters, and responses. Done means the generated interface supports the described v1 and v2 content-negotiation methods without ambiguous mappings.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.