diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java index 03ffd9af5..09a6bb385 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java @@ -356,10 +356,11 @@ private void buildGenericApiResponses(Components components, MethodParameter met // Use response parameters with no description filled - No documentation // available String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), true); - ApiResponse apiResponse = methodAttributes.getGenericMapResponse().containsKey(httpCode) ? methodAttributes.getGenericMapResponse().get(httpCode) - : new ApiResponse(); - if (httpCode != null) + if (Objects.nonNull(httpCode)) { + ApiResponse apiResponse = methodAttributes.getGenericMapResponse().containsKey(httpCode) ? methodAttributes.getGenericMapResponse().get(httpCode) + : new ApiResponse(); buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse, true); + } } } @@ -384,14 +385,19 @@ private void buildApiResponses(Components components, MethodParameter methodPara buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse, false); } } - } - else { - // Use response parameters with no description filled - No documentation - // available + + if (AnnotatedElementUtils.hasAnnotation(methodParameter.getMethod(), ResponseStatus.class)) { + // Handles the case with @ResponseStatus, if the specified response is not already handled explicitly + String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), false); + if (Objects.nonNull(httpCode) && !apiResponsesOp.containsKey(httpCode) && !apiResponsesOp.containsKey(ApiResponses.DEFAULT)) { + buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, new ApiResponse(), false); + } + } + + } else { String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), false); - ApiResponse apiResponse = new ApiResponse(); - if (httpCode != null) - buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse, false); + if (Objects.nonNull(httpCode)) + buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, new ApiResponse(), false); } } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/ErrorResponse.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/ErrorResponse.java new file mode 100644 index 000000000..5528d1e0d --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/ErrorResponse.java @@ -0,0 +1,45 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app192; + +public class ErrorResponse { + Object data = null; + String message; + Object pageable = null; + + public ErrorResponse(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + public String getMessage() { + return message; + } + + public Object getPageable() { + return pageable; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/HelloController.java new file mode 100644 index 000000000..8c89aca6e --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/HelloController.java @@ -0,0 +1,119 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app192; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + @Operation(description = "Adds 200 as api response, because there are nothing defined to get another response") + @RequestBody(description = "test value", required = true, content = @Content(schema = @Schema(implementation = String.class))) + @PostMapping(value = "/postWithoutAnyResponse", produces = MediaType.APPLICATION_JSON_VALUE) + public void postWithoutAnyResponse(String test) {} + + @Operation(description = "Adds 201 as api response, because it defined by @ResponseStatus") + @PostMapping(value = "/postWithResponseStatusOnly", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.CREATED) + public ResponseEntity postWithResponseStatusOnly() { + return ResponseEntity.ok("hello"); + } + + @Operation(description = "Adds 200 as additional api response, because it defined by @ResponseStatus", + responses = { + @ApiResponse(responseCode = "422", description = "Test") + }) + @ApiResponses({ + @ApiResponse(responseCode = "409", description = "Test 2") + }) + @GetMapping(value = "/withResponseStatus", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.OK) + public ResponseEntity withResponseStatus() { + return ResponseEntity.ok("hello"); + } + + @Operation(description = "Adds 200 as api response, because it defined by @ResponseStatus") + @GetMapping(value = "/withResponseStatusOnly", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.OK) + public ResponseEntity withResponseStatusOnly() { + return ResponseEntity.ok("hello"); + } + + @Operation(description = "Doesn't creates the default 200 Response, because there are explicit declared api responses." + + "This test ensures that the current default handling is not changed, because otherwise very many tests will fail.", + responses = { + @ApiResponse(responseCode = "422", description = "Test") + }) + @ApiResponses({ + @ApiResponse(responseCode = "409", description = "Test 2") }) + @GetMapping(value = "/withoutResponseStatus", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity withoutResponseStatus() { + return ResponseEntity.ok("hello"); + } + + + @Operation(description = "Results in the default handling like before") + @GetMapping(value = "/withoutAnyResponseInformation", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity withoutAnyResponseInformation() { + return ResponseEntity.ok("hello"); + } + + @Operation(description = "Overwrites the 200 @ResponseStatus-Information by the explicit declared @ApiResponse", + responses = { + @ApiResponse(responseCode = "200", description = "Test2", content = @Content), + @ApiResponse(responseCode = "422", description = "Test") + }) + @ApiResponses({ + @ApiResponse(responseCode = "409", description = "Test 2") + }) + @GetMapping(value = "/overwrite200InOperation", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.OK) + public ResponseEntity overwrite200InOperation() { + return ResponseEntity.ok("hello"); + } + + @Operation(description = "Overwrites the 200 @ResponseStatus-Information by the explicit declared @ApiResponse", + responses = { + @ApiResponse(responseCode = "422", description = "Test") + }) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Test2", content = @Content), + @ApiResponse(responseCode = "409", description = "Test 2") + }) + @GetMapping(value = "/overwrite200InDoc", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(value = HttpStatus.OK) + public ResponseEntity overwrite200InDoc() { + return ResponseEntity.ok("hello"); + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/SpringDocApp192Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/SpringDocApp192Test.java new file mode 100644 index 000000000..61153491e --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/SpringDocApp192Test.java @@ -0,0 +1,29 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app192; + + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + + +public class SpringDocApp192Test extends AbstractSpringDocV30Test { } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/SpringDocTestApp.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/SpringDocTestApp.java new file mode 100644 index 000000000..4a9ed139b --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app192/SpringDocTestApp.java @@ -0,0 +1,34 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app192; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringDocTestApp { + + public static void main(String[] args) { + SpringApplication.run(SpringDocTestApp.class, args); + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app192.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app192.json new file mode 100644 index 000000000..5f13b5f84 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app192.json @@ -0,0 +1,244 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/postWithoutAnyResponse": { + "post": { + "tags": [ + "hello-controller" + ], + "description": "Adds 200 as api response, because there are nothing defined to get another response", + "operationId": "postWithoutAnyResponse", + "requestBody": { + "description": "test value", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/postWithResponseStatusOnly": { + "post": { + "tags": [ + "hello-controller" + ], + "description": "Adds 201 as api response, because it defined by @ResponseStatus", + "operationId": "postWithResponseStatusOnly", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/withoutResponseStatus": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "Doesn't creates the default 200 Response, because there are explicit declared api responses.This test ensures that the current default handling is not changed, because otherwise very many tests will fail.", + "operationId": "withoutResponseStatus", + "responses": { + "422": { + "description": "Test", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "409": { + "description": "Test 2", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/withoutAnyResponseInformation": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "Results in the default handling like before", + "operationId": "withoutAnyResponseInformation", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/withResponseStatus": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "Adds 200 as additional api response, because it defined by @ResponseStatus", + "operationId": "withResponseStatus", + "responses": { + "422": { + "description": "Test", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "409": { + "description": "Test 2", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/withResponseStatusOnly": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "Adds 200 as api response, because it defined by @ResponseStatus", + "operationId": "withResponseStatusOnly", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/overwrite200InOperation": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "Overwrites the 200 @ResponseStatus-Information by the explicit declared @ApiResponse", + "operationId": "overwrite200InOperation", + "responses": { + "200": { + "description": "Test2" + }, + "422": { + "description": "Test", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "409": { + "description": "Test 2", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/overwrite200InDoc": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "Overwrites the 200 @ResponseStatus-Information by the explicit declared @ApiResponse", + "operationId": "overwrite200InDoc", + "responses": { + "422": { + "description": "Test", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "409": { + "description": "Test 2", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "200": { + "description": "Test2" + } + } + } + } + }, + "components": {} +}