diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/JavadocPropertyCustomizer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/JavadocPropertyCustomizer.java index 3f6aab7b6..62ee626a6 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/JavadocPropertyCustomizer.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/JavadocPropertyCustomizer.java @@ -3,7 +3,7 @@ * * * * * * * * * - * * * * * Copyright 2019-2022 the original author or authors. + * * * * * Copyright 2019-2023 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. @@ -105,7 +105,16 @@ private void setJavadocDescription(Class cls, List fields, Schema exis existingSchema.setDescription(javadocProvider.getClassJavadoc(cls)); } Map properties = existingSchema.getProperties(); - if (!CollectionUtils.isEmpty(properties)) + if (!CollectionUtils.isEmpty(properties)) { + if (cls.getSuperclass() != null && cls.isRecord()) { + Map recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls); + properties.entrySet().stream() + .filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription())) + .forEach(stringSchemaEntry -> { + if (recordParamMap.containsKey(stringSchemaEntry.getKey())) + stringSchemaEntry.getValue().setDescription(recordParamMap.get(stringSchemaEntry.getKey())); + }); + } properties.entrySet().stream() .filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription())) .forEach(stringSchemaEntry -> { @@ -116,7 +125,7 @@ private void setJavadocDescription(Class cls, List fields, Schema exis stringSchemaEntry.getValue().setDescription(fieldJavadoc); }); }); - + } } } } diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/JavadocProvider.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/JavadocProvider.java index 8ced34ed1..a53d58728 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/JavadocProvider.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/JavadocProvider.java @@ -3,7 +3,7 @@ * * * * * * * * * - * * * * * Copyright 2019-2022 the original author or authors. + * * * * * Copyright 2019-2023 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. @@ -42,6 +42,14 @@ public interface JavadocProvider { */ String getClassJavadoc(Class cl); + /** + * Gets param descripton of record class. + * + * @param cl the class + * @return map of field and param descriptions + */ + Map getRecordClassParamJavadoc(Class cl); + /** * Gets method description. * @@ -91,4 +99,3 @@ public interface JavadocProvider { */ String getFirstSentence(String text); } - diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/SpringDocJavadocProvider.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/SpringDocJavadocProvider.java index 1eb578d48..b2609861a 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/SpringDocJavadocProvider.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/providers/SpringDocJavadocProvider.java @@ -3,7 +3,7 @@ * * * * * * * * * - * * * * * Copyright 2019-2022 the original author or authors. + * * * * * Copyright 2019-2023 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. @@ -28,6 +28,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import com.github.therapi.runtimejavadoc.ClassJavadoc; import com.github.therapi.runtimejavadoc.CommentFormatter; @@ -65,6 +66,19 @@ public String getClassJavadoc(Class cl) { return formatter.format(classJavadoc.getComment()); } + /** + * Gets param descripton of record class. + * + * @param cl the class + * @return map of field and param descriptions + */ + @Override + public Map getRecordClassParamJavadoc(Class cl) { + ClassJavadoc classJavadoc = RuntimeJavadoc.getJavadoc(cl); + return classJavadoc.getRecordComponents().stream() + .collect(Collectors.toMap(ParamJavadoc::getName, record -> formatter.format(record.getComment()))); + } + /** * Gets method javadoc description. * diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java index af749dec9..6f1c8b1ab 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java @@ -364,7 +364,7 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque Type type = ReturnTypeParser.getType(methodParameter); if (type instanceof Class && !((Class) type).isEnum() && optionalWebConversionServiceProvider.isPresent()) { WebConversionServiceProvider webConversionServiceProvider = optionalWebConversionServiceProvider.get(); - if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type) && methodParameter.getParameterType().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class) == null){ + if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type) && methodParameter.getParameterType().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class) == null) { Class springConvertedType = webConversionServiceProvider.getSpringConvertedType(methodParameter.getParameterType()); if (!(String.class.equals(springConvertedType) && ((Class) type).isEnum())) type = springConvertedType; @@ -727,17 +727,28 @@ public boolean isRequestBodyPresent(ParameterInfo parameterInfo) { String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter methodParameter) { String pName = methodParameter.getParameterName(); DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter; - final String paramJavadocDescription; - if (delegatingMethodParameter.isParameterObject()) { - String fieldName; - if (StringUtils.isNotEmpty(pName) && pName.contains(DOT)) - fieldName = StringUtils.substringAfterLast(pName, DOT); - else fieldName = pName; - Field field = FieldUtils.getDeclaredField(((DelegatingMethodParameter) methodParameter).getExecutable().getDeclaringClass(), fieldName, true); - paramJavadocDescription = javadocProvider.getFieldJavadoc(field); + if (!delegatingMethodParameter.isParameterObject()) { + return javadocProvider.getParamJavadoc(methodParameter.getMethod(), pName); + } + String fieldName; + if (StringUtils.isNotEmpty(pName) && pName.contains(DOT)) + fieldName = StringUtils.substringAfterLast(pName, DOT); + else fieldName = pName; + + String paramJavadocDescription = null; + Class cls = ((DelegatingMethodParameter) methodParameter).getExecutable().getDeclaringClass(); + if (cls.getSuperclass() != null && cls.isRecord()) { + Map recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls); + if (recordParamMap.containsKey(fieldName)) { + paramJavadocDescription = recordParamMap.get(fieldName); + } + } + + Field field = FieldUtils.getDeclaredField(cls, fieldName, true); + String fieldJavadoc = javadocProvider.getFieldJavadoc(field); + if (StringUtils.isNotBlank(fieldJavadoc)) { + paramJavadocDescription = fieldJavadoc; } - else - paramJavadocDescription = javadocProvider.getParamJavadoc(methodParameter.getMethod(), pName); return paramJavadocDescription; } -} \ No newline at end of file +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/RecordController.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/RecordController.java new file mode 100644 index 000000000..71a5cb9b1 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/RecordController.java @@ -0,0 +1,16 @@ +package test.org.springdoc.api.app169; + +import org.springdoc.core.annotations.ParameterObject; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("record") +public class RecordController { + @GetMapping + public SimpleOuterClass index(@ParameterObject SimpleOuterClass filter) { + return null; + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SimpleInnerClass.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SimpleInnerClass.java new file mode 100644 index 000000000..36a885ea3 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SimpleInnerClass.java @@ -0,0 +1,10 @@ +package test.org.springdoc.api.app169; + +/** + * simple inner class + * + * @param name the boolean name + * @param maxNumber the max number + */ +public record SimpleInnerClass(Boolean name, Integer maxNumber) { +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SimpleOuterClass.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SimpleOuterClass.java new file mode 100644 index 000000000..9223ef9dc --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SimpleOuterClass.java @@ -0,0 +1,10 @@ +package test.org.springdoc.api.app169; + +/** + * simple outer class + * + * @param name the name of the outer class + * @param simpleInnerClass the inner class + */ +public record SimpleOuterClass(String name, SimpleInnerClass simpleInnerClass) { +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SpringDocApp169Test.java b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SpringDocApp169Test.java new file mode 100644 index 000000000..ffcef38f2 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/java/test/org/springdoc/api/app169/SpringDocApp169Test.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2019-2023 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.app169; + +import test.org.springdoc.api.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * The type Spring doc app 169 test. + */ +public class SpringDocApp169Test extends AbstractSpringDocTest { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + } +} diff --git a/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app169.json b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app169.json new file mode 100644 index 000000000..55ec27218 --- /dev/null +++ b/springdoc-openapi-tests/springdoc-openapi-javadoc-tests/src/test/resources/results/app169.json @@ -0,0 +1,97 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/record": { + "get": { + "tags": [ + "record-controller" + ], + "operationId": "index", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "the name of the outer class", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "simpleInnerClass.name", + "in": "query", + "description": "the boolean name", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "simpleInnerClass.maxNumber", + "in": "query", + "description": "the max number", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/SimpleOuterClass" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SimpleInnerClass": { + "type": "object", + "properties": { + "name": { + "type": "boolean", + "description": "the boolean name" + }, + "maxNumber": { + "type": "integer", + "description": "the max number", + "format": "int32" + } + }, + "description": "simple inner class" + }, + "SimpleOuterClass": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name of the outer class" + }, + "simpleInnerClass": { + "$ref": "#/components/schemas/SimpleInnerClass" + } + }, + "description": "simple outer class" + } + } + } +}