Skip to content

Commit

Permalink
refs #3870 - enhanced response schema resolving
Browse files Browse the repository at this point in the history
  • Loading branch information
frantuma committed Feb 22, 2022
1 parent f4025e6 commit 4fcaaf2
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,11 @@
**/
String ref() default "";

/**
* Set to true to resolve the response schema from method return type
*
* @since 2.2.0
**/
boolean useReturnTypeSchema() default false;

}
Original file line number Diff line number Diff line change
Expand Up @@ -1076,10 +1076,11 @@ protected Operation parseMethod(
}

final Class<?> subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method);
Schema returnTypeSchema = null;
if (!shouldIgnoreClass(returnType.getTypeName()) && !method.getGenericReturnType().equals(subResource)) {
ResolvedSchema resolvedSchema = ModelConverters.getInstance().resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation));
if (resolvedSchema.schema != null) {
Schema returnTypeSchema = resolvedSchema.schema;
returnTypeSchema = resolvedSchema.schema;
Content content = new Content();
MediaType mediaType = new MediaType().schema(returnTypeSchema);
AnnotationsUtils.applyTypes(classProduces == null ? new String[0] : classProduces.value(),
Expand Down Expand Up @@ -1112,18 +1113,62 @@ protected Operation parseMethod(
}
}
if (operation.getResponses() == null || operation.getResponses().isEmpty()) {
Content content = new Content();
MediaType mediaType = new MediaType();
AnnotationsUtils.applyTypes(classProduces == null ? new String[0] : classProduces.value(),
methodProduces == null ? new String[0] : methodProduces.value(), content, mediaType);
Content content = resolveEmptyContent(classProduces, methodProduces);

ApiResponse apiResponseObject = new ApiResponse().description(DEFAULT_DESCRIPTION).content(content);
operation.setResponses(new ApiResponses()._default(apiResponseObject));
}
if (returnTypeSchema != null) {
resolveResponseSchemaFromReturnType(operation, classResponses, returnTypeSchema, classProduces, methodProduces);
if (apiResponses != null) {
resolveResponseSchemaFromReturnType(
operation,
apiResponses.stream().toArray(io.swagger.v3.oas.annotations.responses.ApiResponse[]::new),
returnTypeSchema,
classProduces,
methodProduces);
}
}


return operation;
}

protected Content resolveEmptyContent(Produces classProduces, Produces methodProduces) {
Content content = new Content();
MediaType mediaType = new MediaType();
AnnotationsUtils.applyTypes(classProduces == null ? new String[0] : classProduces.value(),
methodProduces == null ? new String[0] : methodProduces.value(), content, mediaType);
return content;
}

protected void resolveResponseSchemaFromReturnType(
Operation operation,
io.swagger.v3.oas.annotations.responses.ApiResponse[] responses,
Schema schema,
Produces classProduces, Produces methodProduces) {
if (responses != null) {
for (io.swagger.v3.oas.annotations.responses.ApiResponse response: responses) {
if (response.useReturnTypeSchema()) {
ApiResponse opResponse = operation.getResponses().get(response.responseCode());
if (opResponse != null) {
if (opResponse.getContent() != null) {
for (MediaType mediaType : opResponse.getContent().values()) {
mediaType.schema(schema);
}
} else {
Content content = resolveEmptyContent(classProduces, methodProduces);
for (MediaType mediaType : content.values()) {
mediaType.schema(schema);
}
opResponse.content(content);
}
}
}
}
}
}

private boolean shouldIgnoreClass(String className) {
if (StringUtils.isBlank(className)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.swagger.v3.core.model.ApiDescription;
import io.swagger.v3.core.util.PrimitiveType;
import io.swagger.v3.jaxrs2.matchers.SerializationMatchers;
import io.swagger.v3.jaxrs2.resources.ResponseReturnTypeResource;
import io.swagger.v3.jaxrs2.resources.SchemaPropertiesResource;
import io.swagger.v3.jaxrs2.resources.SingleExampleResource;
import io.swagger.v3.jaxrs2.resources.BasicFieldsResource;
Expand Down Expand Up @@ -2962,4 +2963,71 @@ public void testSchemaProperties() {
" format: int32\n";
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
}

@Test(description = "Responses schema resolved from return type")
public void testResponseReturnType() {
Reader reader = new Reader(new OpenAPI());

OpenAPI openAPI = reader.read(ResponseReturnTypeResource.class);
String yaml = "openapi: 3.0.1\n" +
"paths:\n" +
" /sample/{id}:\n" +
" get:\n" +
" summary: Find by id\n" +
" description: Find by id operation\n" +
" operationId: find\n" +
" parameters:\n" +
" - name: id\n" +
" in: path\n" +
" description: ID\n" +
" required: true\n" +
" schema:\n" +
" type: integer\n" +
" format: int32\n" +
" responses:\n" +
" \"200\":\n" +
" description: Ok\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/TestDTO'\n" +
" \"201\":\n" +
" description: \"201\"\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/TestDTO'\n" +
" \"204\":\n" +
" description: No Content\n" +
" content:\n" +
" application/json: {}\n" +
" /sample/{id}/default:\n" +
" get:\n" +
" summary: Find by id (default)\n" +
" description: Find by id operation (default)\n" +
" operationId: findDefault\n" +
" parameters:\n" +
" - name: id\n" +
" in: path\n" +
" description: ID\n" +
" required: true\n" +
" schema:\n" +
" type: integer\n" +
" format: int32\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/TestDTO'\n" +
"components:\n" +
" schemas:\n" +
" TestDTO:\n" +
" type: object\n" +
" properties:\n" +
" foo:\n" +
" type: string";
SerializationMatchers.assertEqualsToYaml(openAPI, yaml);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.swagger.v3.jaxrs2.resources;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

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

@GET
@Path("/{id}")
@Operation(summary = "Find by id", description = "Find by id operation")
@ApiResponse(responseCode = "200", description = "Ok", useReturnTypeSchema = true)
@ApiResponse(responseCode = "201",
description = "201",
useReturnTypeSchema = true,
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);

class TestDTO {
public String foo;
}
}

0 comments on commit 4fcaaf2

Please sign in to comment.