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

Using Java Spring Server with oas2 doesn't compile due error in @ApiResponse annotation (content field not array)

Open
#1,024 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

How to reproduce

Using programmatically approach to create Java Spring Server with oas2 generates code:

new OpenApiGenerator(new v3.SpringCodegen() {
    setJava8(true)
    setInterfaceOnly(true)
    setDateLibrary("java8")
    //  setUseOas2(false)
    typeMapping.put("binary", "MultipartFile")
  })

The generated API interface code looks like this:

package smsgw.api.openapi.generated.api;

import smsgw.api.openapi.generated.model.ArrayOfCodewordAvailableResponse;
import smsgw.api.openapi.generated.model.CodewordReservationResponse;
import smsgw.api.openapi.generated.model.CodewordReserve;
import smsgw.api.openapi.generated.model.InternalServerResponse;
import smsgw.api.openapi.generated.model.UnauthorizedResponse;
import smsgw.api.openapi.generated.model.ValidationResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.bind.annotation.CookieValue;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@javax.annotation.Generated(value = "SwaggerCodegenPluginX$$anon$3", date = "2022-03-13T23:19:15.833689+01:00[Europe/Oslo]")
@Validated
public interface CodewordApi {

    Logger log = LoggerFactory.getLogger(CodewordApi.class);

    default Optional<ObjectMapper> getObjectMapper(){
        return Optional.empty();
    }

    default Optional<HttpServletRequest> getRequest(){
        return Optional.empty();
    }

    default Optional<String> getAcceptHeader() {
        return getRequest().map(r -> r.getHeader("Accept"));
    }

    @Operation(summary = "Check if codewords are available", description = "Check if codewords are available.", security = {
        @SecurityRequirement(name = "tokenAuth")    }, tags={ "Codeword" })
    @ApiResponses(value = { 
        @ApiResponse(responseCode = "200", description = "The availability of the codewords", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ArrayOfCodewordAvailableResponse.class))),
        
        @ApiResponse(responseCode = "400", description = "Validation error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ValidationResponse.class))),
        
        @ApiResponse(responseCode = "401", description = "Authentication error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UnauthorizedResponse.class))) })
    @RequestMapping(value = "/codeword",
        produces = { "application/json" }, 
        method = RequestMethod.GET)
    default ResponseEntity<ArrayOfCodewordAvailableResponse> availableCodeword(@NotNull @Size(min=1) @Parameter(in = ParameterIn.QUERY, description = "Comma seperated value (csv) list of codewords the organization wants to reserve. At least one codeword is required. Example: ?codeword=company,trademark " ,required=true,schema=@Schema()) @Valid @RequestParam(value = "codeword", required = true) List<String> codeword, @NotNull @Parameter(in = ParameterIn.QUERY, description = "Comma seperated value (csv) list of codewords the organization wants to reserve. At least one codeword is required. Example: ?moDestination=45345345 " ,required=true,schema=@Schema()) @Valid @RequestParam(value = "moDestination", required = true) String moDestination) {
 // More generated code......

But the problem is that this code doesn't compile due to

@ApiResponse(content = @Content(mediaType = "application/json", schema = @Schema(implementation = UnauthorizedResponse.class))) }
Reason

The thing is that content is an array:

public @interface ApiResponse {
    /**
     * An array containing descriptions of potential response payloads, for different media types.
     *
     * @return array of content
     **/
    Content[] content() default {};

Solution

Adding brackets around the content solves the problem:

        @ApiResponse(responseCode = "200", description = "The availability of the codewords", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ArrayOfCodewordAvailableResponse.class))}),
Fix

The problem can be fixed in src/main/resources/handlebars/JavaSpring/api.mustache
by changing the following line:

@ApiResponse(responseCode = "{{{code}}}", description = "{{{message}}}"{{^vendorExtensions.x-java-is-response-void}}{{#baseType}}, content = {@Content({{#schema.extensions.x-content-type}}mediaType = "{{schema.extensions.x-content-type}}", {{/schema.extensions.x-content-type}}{{^containerType}}schema = @Schema(implementation = {{{baseType}}}.class))}{{/containerType}}{{#containerType}}array = @ArraySchema(schema = @Schema(implementation = {{{baseType}}}.class)))
Sidenote

For some reason it doesn't make any difference if setUseOas2() is true or false... It's expected that the problematic code should be ignored when useOas2=false due to {{^useOas2}}in the mustache file

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 in src/main/resources/handlebars/JavaSpring/api.mustache and inspect the ApiResponse content generation shown in the issue. Regenerate a Java Spring server from an OAS2 definition using the programmatic setup, then confirm the generated annotations compile with content represented as an array.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
tooling
Issue type
Bug
Difficulty
1/5
Estimated time
Under an hour
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.