OpenAPITools / OpenAPITools/openapi-generator
[BUG][core] splitOperationsByContentType: a variant sends the Accept of another media-type when the operation has JSON error responses
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
Bug Report Checklist
- [ X ] Have you provided a full/minimal spec to reproduce the issue?
- [ X ] Have you validated the input using an OpenAPI validator?
- [ X ] Have you tested with the latest master to confirm the issue still exists?
- [ X ] Have you searched for related issues/PRs?
- [ X ] What's the actual output vs expected output?
Description
The problem. With splitOperationsByContentType=true (#23935, 7.25.0), an operation whose 200 is application/json | text/csv is generated as two methods, getReportAsJson and getReportAsCsv, each typed on its media-type. But as soon as the operation also declares a JSON error response (400: application/json), the generated getReportAsCsv sends Accept: application/json, not text/csv. A server that negotiates on Accept answers JSON (or 406) to a method that is typed to parse CSV. The option exists to make each media-type reachable, and the variant it generates cannot request its own media-type.
The same happens for java, python and spring (library: spring-cloud); details and generated code below.
Why. The split narrows only the success response of a variant; the error responses stay on every variant. produces of a CodegenOperation is the union of every response's content, so on the CSV variant it is [text/csv, application/json]. Each generator then derives the Accept header from that list with a rule that predates variants and prefers JSON:
java(okhttp-gson, resttemplate, …):selectHeaderAcceptreturns the first JSON entry;python:select_header_acceptdoes the same;spring-cloud(Feign):producesis rendered fromx-accepts, whichAbstractJavaCodegen.preprocessOpenAPIcomputes on the original operation before the split (both variants inherit the same sorted list) andSpringMvcContractsendsproduces[0].
typescript-fetch is not affected: it merges the variants into overloads and reads the media-type from the x-content-type-variant-* extensions.
openapi-generator version
7.25.0 (and current master). Regression only in the sense that the feature is new: the option did not exist before 7.25.0.
OpenAPI declaration file content or url
https://gist.github.com/AntoineDuComptoirDesPharmacies/c80358f886afcae2e63613b6c09ed8af
Generation Details
java -jar openapi-generator-cli-7.25.0.jar generate -g spring --library spring-cloud \
-i report.yaml -o out/spring --global-property splitOperationsByContentType=true
java -jar openapi-generator-cli-7.25.0.jar generate -g java \
-i report.yaml -o out/java --global-property splitOperationsByContentType=true
java -jar openapi-generator-cli-7.25.0.jar generate -g python \
-i report.yaml -o out/python --global-property splitOperationsByContentType=true
Actual output
spring / spring-cloud, both variants carry the same produces, and Feign sends its first element:
@RequestMapping(method = RequestMethod.GET, value = ReportsApi.PATH_GET_REPORT_AS_CSV,
produces = { "application/json", "text/csv" })
ResponseEntity<String> getReportAsCsv(...); // sends Accept: application/json
@RequestMapping(method = RequestMethod.GET, value = ReportsApi.PATH_GET_REPORT_AS_JSON,
produces = { "application/json", "text/csv" })
ResponseEntity<Report> getReportAsJson(...);
java (okhttp-gson), the CSV variant lists JSON too, and selectHeaderAccept picks it:
// getReportAsCsvCall
final String[] localVarAccepts = { "text/csv", "application/json" }; // -> Accept: application/json
python:
# _get_report_as_csv_serialize
_header_params['Accept'] = self.api_client.select_header_accept(['text/csv', 'application/json']) # -> application/json
Expected output
Each variant produces and asks for the single media-type it was narrowed to; the error responses keep typing their body as before:
produces = { "application/json" } ResponseEntity<Report> getReportAsJson(...)
produces = { "text/csv" } ResponseEntity<String> getReportAsCsv(...)
final String[] localVarAccepts = { "text/csv" };
_header_params['Accept'] = self.api_client.select_header_accept(['text/csv'])
Operations the split leaves alone must keep the union of every response in produces, as they always had.
Related issues/PRs
- #6708 (feature request), #23935 (the option)
- https://github.com/OpenAPITools/openapi-generator/pull/23935#issuecomment-5435486061
Suggest a fix
A PR follows. The idea: a variant's produces is the single media-type it was narrowed to, nothing else.
- Core :
DefaultCodegen.fromOperationbuilds a variant'sproducesfrom its method response alone (the one the split narrowed), andgetProducesInforeturns that same media-type. Every client then asks for the media-type the variant is typed on, and every server generator maps each variant on its own media-type. - Java :
AbstractJavaCodegenstampsx-accepts/x-content-typeon the variants asdivideOperationsByContentTypecreates them. Today they are computed once inpreprocessOpenAPI, before the split, so the variants inherit the values of the operation they came from. - Unchanged : the error responses keep their own content and typing; operations the split leaves alone generate byte-identical output; the
typescript-fetchsplit sample regenerates without a change.
One point I would like other opinions on: for the Java Spring server, a variant then declares only its own media-type in produces (getReportAsCsv → produces = "text/csv"), while the error responses of that operation are JSON. That is what lets Spring route Accept: text/csv and Accept: application/json to the right variant without an ambiguous mapping, and the JSON error bodies still go through @ExceptionHandler / @ControllerAdvice, which Spring negotiates on the client's Accept regardless of the handler's produces. But it does mean the annotation no longer lists the error media-types. Is that acceptable, or would you rather keep them in produces some other way?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with DefaultCodegen.fromOperation and getProducesInfo, then trace divideOperationsByContentType and AbstractJavaCodegen.preprocessOpenAPI, including x-accepts and x-content-type handling. Compare the Java, Python, and spring-cloud generated outputs using the report.yaml generation commands; done means each narrowed variant requests and declares only its own success media type while unsplit operations remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, python, spring
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100