Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When a method has one or more @ApiResponse the response schema definition is not generated #3870

Closed
arielcarrera opened this issue Feb 5, 2021 · 6 comments
Assignees

Comments

@arielcarrera
Copy link

In a Jax-rs project with Swagger, when a method has one or more @ApiResponse no schema definition is generated.
When there is no @ApiResponse, a default response is generated.

It is desirable to provide a way to generate the schema automatically even if there are defined responses (if the return type is a generic type, it is currently difficult to calculate / define it).

Related issues #3851 and #3723.

Expected behavior:

Given a resource class like this:

@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/sample")
public interface SampleResource {

    @GET
    @Path("/{id}")
    @Operation(summary = "Find by id", description = "Find by id operation")
    @ApiResponse(responseCode = "200", description = "Ok")
    @ApiResponse(responseCode = "201", description = "201", content = @Content(mediaType = "application/json"))
   @ApiResponse(responseCode = "204",
		 description = "No Content",
		 content = @Content(mediaType = "application/json", schema = @Schema(implementation = Void.class)))
    TestDTO find(@Parameter(description = "ID") @PathParam("id") Integer id);

   @GET
    @Path("/{id}/default")
    @Operation(summary = "Find by id (default)", description = "Find by id operation (default)")
    TestDTO findDefault(@Parameter(description = "ID") @PathParam("id") Integer id);
}

And a maven file (pom.xml) with a plugin like this:

       <plugins>
	      <build>
			<plugin>
				<groupId>io.swagger.core.v3</groupId>
				<artifactId>swagger-maven-plugin</artifactId>
				<version>2.1.6</version>
				<configuration>
					<outputFileName>openapi</outputFileName>
					<outputPath>${project.build.directory}/classes/META-INF</outputPath>
					<outputFormat>JSONANDYAML</outputFormat>
					<resourcePackages>
						<package>com.example.demo</package>
					</resourcePackages>
					<prettyPrint>TRUE</prettyPrint>
				</configuration>
				<executions>
					<execution>
						<phase>compile</phase>
                        <?m2e ignore?>
						<goals>
							<goal>resolve</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

Expected result:

openapi: 3.0.1
info:
  title: Demo API
  version: 1.0.0
paths:
  /sample/{id}:
    get:
      summary: Find by id
      description: Find by id operation
      operationId: find
      parameters:
            - name: id
              in: path
              description: ID
              required: true
              schema:
                type: integer
                format: int32
      responses:
        200:
          description: Ok
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestDTO'
        201:
          description: 201
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestDTO'
        204:
          description: No Content
 /sample/{id}/default:
    get:
      summary: Find by id (default)
      description: Find by id operation (default)
      operationId: find
      parameters:
            - name: id
              in: path
              description: ID
              required: true
              schema:
                type: integer
                format: int32
      responses:
        default:
          description: default response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestDTO'
components:
  schemas:
    TestDTO:
      type: object
      properties:
        test:
          type: string

Current result:

openapi: 3.0.1
info:
  title: Demo API
  version: 1.0.0
paths:
  /sample/{id}:
    get:
      summary: Find by id
      description: Find by id operation
      operationId: find
      parameters:
      - name: id
        in: path
        description: ID
        required: true
        schema:
          type: integer
          format: int32
      responses:
        "200":
          description: Ok
        "201":
          description: "201"
          content:
            application/json: {}
        "204":
          description: No Content
          content:
            application/json: {}
  /sample/default/{id}:
    get:
      summary: Find by id (default)
      description: Find by id operation (default)
      operationId: findDefault
      parameters:
      - name: id
        in: path
        description: ID
        required: true
        schema:
          type: integer
          format: int32
      responses:
        default:
          description: default response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestDTO'
components:
  schemas:
    TestDTO:
      type: object
      properties:
        test:
          type: string
@arielcarrera
Copy link
Author

Here is a reproducer project
demo.zip

@arielcarrera
Copy link
Author

I've implemented a workaround using instead of Void.class as an empty response marker, an Empty.class created in the annotations module.
I can submit a pull request if you want this kind of solution.

@arielcarrera
Copy link
Author

A PR has been submitted with a feasible implementation (#3872).

For a given Resource like this:

public class EmptyResponseResource {

    @Operation(
            summary = "Get a list of users",
            description = "Get a list of users registered in the system",
            responses = {@ApiResponse(
                    responseCode = "200",
                    description = "The response for the user request")
            }
    )
    @GET
    @Path("/user")
    public User getUser() {
        return null;
    }
    
    @Operation(
            summary = "Get a list of users",
            description = "Get a list of users registered in the system",
            responses = {@ApiResponse(
                    responseCode = "200",
                    description = "The response for the user request",
                    content = @Content(schema = @Schema(implementation = Empty.class)))
            }
    )
    @GET
    @Path("/user/noContent")
    public User getUserNoContent() {
        return null;
    }
    
    @Operation(
            summary = "Get a list of users",
            description = "Get a list of users registered in the system",
            responses = {@ApiResponse(
                    responseCode = "200",
                    description = "The response for the user request",
                    content = @Content(schema=@Schema(implementation = CustomUser.class)))
            }
    )
    @GET
    @Path("/user/customSchemaImpl")
    public User getUserCustomSchemaImpl() {
        return null;
    }    
    
    class User {
        public String foo;
    }
    
    class CustomUser extends User {
        public String foo2;
    }
}

this is the result:

openapi: 3.0.1
paths:
  /user:
    get:
      summary: Get a list of users
      description: Get a list of users registered in the system
      operationId: getUser
      responses:
        "200":
          description: The response for the user request
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/User'
  /user/customSchemaImpl:
    get:
      summary: Get a list of users
      description: Get a list of users registered in the system
      operationId: getUserCustomSchemaImpl
      responses:
        "200":
          description: The response for the user request
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/CustomUser'
  /user/noContent:
    get:
      summary: Get a list of users
      description: Get a list of users registered in the system
      operationId: getUserNoContent
      responses:
        "200":
          description: The response for the user request
components:
  schemas:
    User:
      type: object
      properties:
        foo:
          type: string
    CustomUser:
      type: object
      properties:
        foo:
          type: string
        foo2:
          type: string

In this example you can see how by default the same thing is done as when the default response is generated (generating the definition of the response based on its type).
For the case in which you do not want to include a response content and you are not returning a Response.class or void, you can simply specify it with something like this:
@ApiResponse (responseCode =" code ", description =" desc ", content = @Content (schema = @Schema (implementation = Empty.class)))

This adopted solution avoids to change annotations and add attributes to them but another alternatives can be adopted like add a new attribute to @content annotation that indicate that the schema is not wanted to be included or should be be calculated from return type.

Whatever the case, I guess some of the uploaded code can serve as a guide at least or for testing purposes.

@judos
Copy link

judos commented Jan 4, 2022

Did you try using @ApiResponses (mind the S, this is plural!)? See #3851

@arielcarrera
Copy link
Author

Hi @judos, I think so because this is just an example and I usually put multiple apiResponses for every method.

But this issue is a bit old, I don't remember.
I've never received any feedback from devs about the changes that I submitted at that moment.

I think that this kind of issue is not a priority for the team or maybe the project are surviving.

@frantuma
Copy link
Member

@arielcarrera thanks for reporting this and for your effort in this area.

#4129 addresses this by introducing field @ApiResponse.useReturnTypeSchema, which allows to mark a response to use the return type for its schema. Please see comment in PR for an example

I will be closing therefore #3872, as possibly the addition of the field above has less impact on existing behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants