diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java index 98b165ee9..6ad44e85f 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java @@ -102,6 +102,15 @@ public static boolean isResponseTypeWrapper(Class rawClass) { return RESULT_WRAPPERS_TO_IGNORE.stream().anyMatch(clazz -> clazz.isAssignableFrom(rawClass)); } + /** + * Is exact class + * @param rawClass the raw class + * true if the class is in the list of classes to ignore + */ + public static boolean isExactClass(Class rawClass) { + return RESULT_WRAPPERS_TO_IGNORE.stream().anyMatch(clazz -> clazz == rawClass); + } + /** * Is response type to ignore boolean. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ResponseSupportConverter.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ResponseSupportConverter.java index 6ce3e79e5..4c651d225 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ResponseSupportConverter.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/ResponseSupportConverter.java @@ -35,9 +35,7 @@ import io.swagger.v3.oas.models.media.StringSchema; import org.springdoc.core.providers.ObjectMapperProvider; -import static org.springdoc.core.converters.ConverterUtils.isFluxTypeWrapper; -import static org.springdoc.core.converters.ConverterUtils.isResponseTypeToIgnore; -import static org.springdoc.core.converters.ConverterUtils.isResponseTypeWrapper; +import static org.springdoc.core.converters.ConverterUtils.*; /** * The type Response support converter. @@ -65,7 +63,7 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato if (javaType != null) { Class cls = javaType.getRawClass(); if (isResponseTypeWrapper(cls) && !isFluxTypeWrapper(cls)) { - JavaType innerType = javaType.getBindings().getBoundType(0); + JavaType innerType = resolveInnerType(javaType); if (innerType == null) return new StringSchema(); return context.resolve(new AnnotatedType(innerType) @@ -79,4 +77,12 @@ else if (isResponseTypeToIgnore(cls)) return (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null; } + private JavaType resolveInnerType(JavaType javaType) { + while(!isExactClass(javaType.getRawClass())) { + javaType = javaType.getSuperClass(); + } + + return javaType.getBindings().getBoundType(0); + } + } diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/HelloController.java b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/HelloController.java new file mode 100644 index 000000000..08ffee138 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/HelloController.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2019-2024 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.app227; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import test.org.springdoc.api.app227.model.Item; +import test.org.springdoc.api.app227.wrapper.ResponseEntityWrapper; + +@RestController +public class HelloController { + + @GetMapping("/persons") + public ResponseEntityWrapper persons() { + return new ResponseEntityWrapper<>(Item.fromPayload("1", "OK", "String"), HttpStatus.OK); + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/SpringDocApp227Test.java b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/SpringDocApp227Test.java new file mode 100644 index 000000000..685411fea --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/SpringDocApp227Test.java @@ -0,0 +1,43 @@ +/* + * + * * Copyright 2019-2024 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.app227; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.utils.Constants; +import org.springframework.test.context.TestPropertySource; +import test.org.springdoc.api.AbstractSpringDocTest; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@TestPropertySource(properties = { + "management.endpoints.enabled-by-default=true", +}) +public class SpringDocApp227Test extends AbstractSpringDocTest { + + @Test + public void testApp() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.openapi", is("3.0.1"))) + .andExpect(content().json(getContent("results/app227.json"), true)); + } + +} diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/SpringDocTestApp.java b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/SpringDocTestApp.java new file mode 100644 index 000000000..f942819c8 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/SpringDocTestApp.java @@ -0,0 +1,11 @@ +package test.org.springdoc.api.app227; + +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); + } +} \ No newline at end of file diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/model/Item.java b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/model/Item.java new file mode 100644 index 000000000..99da09d97 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/model/Item.java @@ -0,0 +1,14 @@ +package test.org.springdoc.api.app227.model; + +/** + * Base item + */ +public record Item(String id, String type, T resource) { + + /** + * Create Item object + */ + public static Item fromPayload(String id, String type, T payload) { + return new Item<>(id, type, payload); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/wrapper/ResponseEntityWrapper.java b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/wrapper/ResponseEntityWrapper.java new file mode 100644 index 000000000..da22b8ee7 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/java/test/org/springdoc/api/app227/wrapper/ResponseEntityWrapper.java @@ -0,0 +1,11 @@ +package test.org.springdoc.api.app227.wrapper; + +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import test.org.springdoc.api.app227.model.Item; + +public class ResponseEntityWrapper extends ResponseEntity> { + public ResponseEntityWrapper(Item body, HttpStatusCode status) { + super(body, status); + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app227.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app227.json new file mode 100644 index 000000000..604fe4922 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app227.json @@ -0,0 +1,53 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ItemString" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ItemString": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "resource": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file