Skip to content

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

@jose-hernandez-001

Description

@jose-hernandez-001
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}} })
:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions