swagger-api / swagger-api/swagger-codegen-generators

[Spring] Incorrect code generated for endpoints with multiple request types

Open
#441 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Mustache
Stars
299
Forks
439
PR merge metrics
No merged PRs in 30d

Description

Description

The swagger code generator generates ambiguous code which compiles but does not run for endpoints with multiple content types. Take this example where the request body content type can be 'application/json' or 'multipart/form-data':

    ...
    put:
      summary: "Update a rule set with the supplied rule definitions."
      operationId: "updateRuleSetWithRules"
      parameters:
        - name: "id"
          in: "path"
          required: true
          style: "simple"
          explode: false
          schema:
            type: "integer"
            format: "int32"
      requestBody:
        description: "rules set with rules to be stored"
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RuleSetWithRulesVM"
          multipart/form-data:
            schema:
              type: "object"
              properties:
                ruleSetCsvFile:
                  type: "string"
                  description: "The CSV file containing the rules."
                  format: "binary"
        required: true
      responses:
        200:
    ...

The API file that gets generated contains the following to methods which the Java compiler can successfully compile:

    ...
    @RequestMapping(value = "/ruleSets/{id}/rules",
        produces = { "application/json" }, 
        consumes = { "application/json", "multipart/form-data" },
        method = RequestMethod.PUT)
    default ResponseEntity<RuleSetWithRulesVM> updateRuleSetWithRules(@ApiParam(value = "rules set with rules to be stored" ,required=true )  @Valid @RequestBody RuleSetWithRulesVM body,@ApiParam(value = "",required=true) @PathVariable("id") Integer id) {
        return getDelegate().updateRuleSetWithRules(body, id);
    }
   ...

and

    @RequestMapping(value = "/ruleSets/{id}/rules",
        produces = { "application/json" }, 
        consumes = { "application/json", "multipart/form-data" },
        method = RequestMethod.PUT)
    default ResponseEntity<RuleSetWithRulesVM> updateRuleSetWithRules(@ApiParam(value = "file detail") @Valid @RequestPart("file") MultipartFile ruleSetCsvFile,@ApiParam(value = "",required=true) @PathVariable("id") Integer id) {
        return getDelegate().updateRuleSetWithRules(ruleSetCsvFile, id);
    }

The problem arises when you start the resulting application because Spring is unable to distinguish between the two overloaded methods as they are missing the params option from the @RequestMapping annotation.

Swagger-codegen version

3.011-SNAPSHOT

Swagger declaration file content or url
  /ruleSets/{id}/rules:
    put:
      tags:
        - "rules"
      summary: "Update a rule set with the supplied rule definitions."
      operationId: "updateRuleSetWithRules"
      parameters:
        - name: "id"
          in: "path"
          required: true
          style: "simple"
          explode: false
          schema:
            type: "integer"
            format: "int32"
      requestBody:
        description: "rules set with rules to be stored"
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RuleSetWithRulesVM"
          multipart/form-data:
            schema:
              type: "object"
              properties:
                ruleSetCsvFile:
                  type: "string"
                  description: "The CSV file containing the rules."
                  format: "binary"
        required: true
      responses:
        200:
          description: "success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RuleSetWithRulesVM"
Command line used for generation

The code is generated using Maven with the following configuration:

<configuration>
    <ignoreFileOverride>${project.basedir}/src/main/resources/.swagger-codegen-ignore</ignoreFileOverride>
    <inputSpec>${project.basedir}/src/main/resources/public/swagger-ml-classification-1.0.0.yaml</inputSpec>
    <language>spring</language>
    <configOptions>
        <sourceFolder>src/main/java</sourceFolder>
        <apiPackage>${default-server-package}.api</apiPackage>
        <configPackage>${default-server-package}.configuration</configPackage>
        <invokerPackage>${default-server-package}</invokerPackage>
        <modelPackage>${default-server-package}.model</modelPackage>
        <delegatePattern>true</delegatePattern>
        <java8>true</java8>
        <dateLibrary>java8</dateLibrary>
    </configOptions>
</configuration>
Steps to reproduce
  1. Create an endpoint with two content types in the request body as shown above
  2. Generate the code for Spring Boot
  3. Run the resulting application to see the Spring startup errors
Related issues/PRs

None found

Suggest a fix/enhancement

To fix the problem, the code generator should generate code that includes the params option in the @RequestMapping annotation as shown in this example:

...
    @RequestMapping(value = "/ruleSets/{id}/rules",
        produces = { "application/json" }, 
        consumes = { "application/json", "multipart/form-data" },
        method = RequestMethod.PUT, params = {"body", "id"})
    default ResponseEntity<RuleSetWithRulesVM> updateRuleSetWithRules(@ApiParam(value = "rules set with rules to be stored" ,required=true )  @Valid @RequestBody RuleSetWithRulesVM body,@ApiParam(value = "",required=true) @PathVariable("id") Integer id) {
        return getDelegate().updateRuleSetWithRules(body, id);
    }
...
    @RequestMapping(value = "/ruleSets/{id}/rules",
        produces = { "application/json" }, 
        consumes = { "application/json", "multipart/form-data" },
        method = RequestMethod.PUT, params = {"ruleSetCsvFile", "id"})
    default ResponseEntity<RuleSetWithRulesVM> updateRuleSetWithRules(@ApiParam(value = "file detail") @Valid @RequestPart("file") MultipartFile ruleSetCsvFile,@ApiParam(value = "",required=true) @PathVariable("id") Integer id) {
        return getDelegate().updateRuleSetWithRules(ruleSetCsvFile, id);
    }
...

Please note the following changes in the suggested solution:

        method = RequestMethod.PUT, params = {"body", "id"})
        method = RequestMethod.PUT, params = {"ruleSetCsvFile", "id"})

Instead of the original:

        method = RequestMethod.PUT)
        method = RequestMethod.PUT)

The following patch could a be way of possibly fixing the issue but it will need work further work the params code is only generated under the right conditions:

diff --git a/src/main/resources/handlebars/JavaSpring/api.mustache b/src/main/resources/handlebars/JavaSpring/api.mustache
index 7551be6..bed2f10 100644
--- a/src/main/resources/handlebars/JavaSpring/api.mustache
+++ b/src/main/resources/handlebars/JavaSpring/api.mustache
@@ -101,7 +101,8 @@ public interface {{classname}} {
         consumes = "{{{vendorExtensions.x-contentType}}}",{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}
         produces = { {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} }, {{/hasProduces}}{{#hasConsumes}}
         consumes = { {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} },{{/hasConsumes}}{{/singleContentTypes}}
-        method = RequestMethod.{{httpMethod}})
+        method = RequestMethod.{{httpMethod}},
+        params = { {{#parameters}}"{{paramName}}"{{#hasMore}}, {{/hasMore}}{{/parameters}} })
     {{#jdk8}}default {{/jdk8}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{#delegate-method}}_{{/delegate-method}}{{operationId}}({{#parameters}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}},{{/hasMore}}{{/parameters}}){{^jdk8}};{{/jdk8}}{{#jdk8}} {
         {{#delegate-method}}
         return {{operationId}}({{#parameters}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/parameters}});
@@ -135,4 +136,4 @@ public interface {{classname}} {
 {{/contents}}
 {{/operation}}
 }
-{{/operations}}
\ No newline at end of file
+{{/operations}}
diff --git a/src/main/resources/mustache/JavaSpring/api.mustache b/src/main/resources/mustache/JavaSpring/api.mustache
index c03be38..bed2f10 100644
--- a/src/main/resources/mustache/JavaSpring/api.mustache
+++ b/src/main/resources/mustache/JavaSpring/api.mustache
@@ -101,8 +101,8 @@ public interface {{classname}} {
         consumes = "{{{vendorExtensions.x-contentType}}}",{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}
         produces = { {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} }, {{/hasProduces}}{{#hasConsumes}}
         consumes = { {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} },{{/hasConsumes}}{{/singleContentTypes}}
-        method = RequestMethod.{{httpMethod}}
-        params = { {{#parameters}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/parameters}} })
:

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 src/main/resources/handlebars/JavaSpring/api.mustache and src/main/resources/mustache/JavaSpring/api.mustache, then reproduce the Spring Boot generation with an endpoint containing both request content types. Check the generated @RequestMapping annotations and confirm the resulting application starts without ambiguous mappings; ensure the parameter conditions work for both generated methods.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.