Skip to content

Commit 0d32630

Browse files
authored
Merge pull request #2140 from uc4w6c/fix_javadoc_for_record_class
Fixed a bug that javadoc of record class parameters was not recognize…
2 parents 9eff91a + eda5e70 commit 0d32630

File tree

6 files changed

+392
-20
lines changed

6 files changed

+392
-20
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java

+23-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*
33
* *
44
* * *
5-
* * * * Copyright 2019-2022 the original author or authors.
5+
* * * * Copyright 2019-2023 the original author or authors.
66
* * * *
77
* * * * Licensed under the Apache License, Version 2.0 (the "License");
88
* * * * you may not use this file except in compliance with the License.
@@ -720,17 +720,29 @@ public boolean isRequestBodyPresent(ParameterInfo parameterInfo) {
720720
String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter methodParameter) {
721721
String pName = methodParameter.getParameterName();
722722
DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter;
723-
final String paramJavadocDescription;
724-
if (delegatingMethodParameter.isParameterObject()) {
725-
String fieldName;
726-
if (StringUtils.isNotEmpty(pName) && pName.contains(DOT))
727-
fieldName = StringUtils.substringAfterLast(pName, DOT);
728-
else fieldName = pName;
729-
Field field = FieldUtils.getDeclaredField(((DelegatingMethodParameter) methodParameter).getExecutable().getDeclaringClass(), fieldName, true);
730-
paramJavadocDescription = javadocProvider.getFieldJavadoc(field);
723+
if (!delegatingMethodParameter.isParameterObject()) {
724+
return javadocProvider.getParamJavadoc(methodParameter.getMethod(), pName);
731725
}
732-
else
733-
paramJavadocDescription = javadocProvider.getParamJavadoc(methodParameter.getMethod(), pName);
726+
String fieldName;
727+
if (StringUtils.isNotEmpty(pName) && pName.contains(DOT))
728+
fieldName = StringUtils.substringAfterLast(pName, DOT);
729+
else fieldName = pName;
730+
731+
String paramJavadocDescription = null;
732+
Class cls = ((DelegatingMethodParameter) methodParameter).getExecutable().getDeclaringClass();
733+
if (cls.getSuperclass() != null && "java.lang.Record".equals(cls.getSuperclass().getName())) {
734+
Map<String, String> recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls);
735+
if (recordParamMap.containsKey(fieldName)) {
736+
paramJavadocDescription = recordParamMap.get(fieldName);
737+
}
738+
}
739+
740+
Field field = FieldUtils.getDeclaredField(cls, fieldName, true);
741+
String fieldJavadoc = javadocProvider.getFieldJavadoc(field);
742+
if (StringUtils.isNotBlank(fieldJavadoc)) {
743+
paramJavadocDescription = fieldJavadoc;
744+
}
745+
734746
return paramJavadocDescription;
735747
}
736748
}

springdoc-openapi-common/src/main/java/org/springdoc/core/providers/JavadocProvider.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*
33
* *
44
* * *
5-
* * * * Copyright 2019-2022 the original author or authors.
5+
* * * * Copyright 2019-2023 the original author or authors.
66
* * * *
77
* * * * Licensed under the Apache License, Version 2.0 (the "License");
88
* * * * you may not use this file except in compliance with the License.
@@ -41,11 +41,19 @@ public interface JavadocProvider {
4141
String getClassJavadoc(Class<?> cl);
4242

4343
/**
44-
* Gets method description.
44+
* Gets param descripton of record class.
4545
*
46-
* @param method the method
47-
* @return the method description
46+
* @param cl the class
47+
* @return map of field and param descriptions
4848
*/
49+
Map<String, String> getRecordClassParamJavadoc(Class<?> cl);
50+
51+
/**
52+
* Gets method description.
53+
*
54+
* @param method the method
55+
* @return the method description
56+
*/
4957
String getMethodJavadocDescription(Method method);
5058

5159
/**
@@ -88,4 +96,3 @@ public interface JavadocProvider {
8896
*/
8997
String getFirstSentence(String text);
9098
}
91-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2019-2023 the original author or authors.
6+
* * * *
7+
* * * * Licensed under the Apache License, Version 2.0 (the "License");
8+
* * * * you may not use this file except in compliance with the License.
9+
* * * * You may obtain a copy of the License at
10+
* * * *
11+
* * * * https://www.apache.org/licenses/LICENSE-2.0
12+
* * * *
13+
* * * * Unless required by applicable law or agreed to in writing, software
14+
* * * * distributed under the License is distributed on an "AS IS" BASIS,
15+
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* * * * See the License for the specific language governing permissions and
17+
* * * * limitations under the License.
18+
* * *
19+
* *
20+
*
21+
*/
22+
23+
package org.springdoc.core;
24+
25+
import java.io.File;
26+
import java.io.FileWriter;
27+
import java.io.IOException;
28+
import java.io.PrintWriter;
29+
import java.lang.reflect.Method;
30+
import java.net.URL;
31+
import java.net.URLClassLoader;
32+
import java.util.HashMap;
33+
import java.util.Map;
34+
import java.util.Optional;
35+
36+
import javax.tools.JavaCompiler;
37+
import javax.tools.ToolProvider;
38+
39+
import org.junit.jupiter.api.BeforeEach;
40+
import org.junit.jupiter.api.Nested;
41+
import org.junit.jupiter.api.Test;
42+
import org.junit.jupiter.api.condition.EnabledForJreRange;
43+
import org.junit.jupiter.api.condition.JRE;
44+
import org.junit.jupiter.api.io.TempDir;
45+
import org.mockito.Mock;
46+
import org.mockito.MockitoAnnotations;
47+
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
48+
import org.springdoc.core.providers.JavadocProvider;
49+
import org.springdoc.core.providers.ObjectMapperProvider;
50+
import org.springdoc.core.providers.WebConversionServiceProvider;
51+
52+
import org.springframework.core.MethodParameter;
53+
54+
import static org.junit.jupiter.api.Assertions.*;
55+
import static org.mockito.ArgumentMatchers.any;
56+
import static org.mockito.Mockito.verify;
57+
import static org.mockito.Mockito.when;
58+
59+
/**
60+
* Tests for {@link GenericParameterService}.
61+
*/
62+
class GenericParameterServiceTest {
63+
@TempDir
64+
private File tempDir;
65+
66+
@Mock
67+
private PropertyResolverUtils propertyResolverUtils;
68+
69+
@Mock
70+
private DelegatingMethodParameterCustomizer delegatingMethodParameterCustomizer;
71+
72+
@Mock
73+
private WebConversionServiceProvider webConversionServiceProvider;
74+
75+
@Mock
76+
private ObjectMapperProvider objectMapperProvider;
77+
78+
@Mock
79+
private JavadocProvider javadocProvider;
80+
81+
private GenericParameterService genericParameterService;
82+
83+
@BeforeEach
84+
void setup() {
85+
MockitoAnnotations.openMocks(this);
86+
this.genericParameterService = new GenericParameterService(propertyResolverUtils, Optional.of(delegatingMethodParameterCustomizer), Optional.of(webConversionServiceProvider), objectMapperProvider, Optional.of(javadocProvider));
87+
}
88+
89+
/**
90+
* Tests for {@link GenericParameterService#getParamJavadoc(JavadocProvider, MethodParameter)}.
91+
*/
92+
@Nested
93+
class getParamJavadoc {
94+
@Mock
95+
private DelegatingMethodParameter methodParameter;
96+
97+
@BeforeEach
98+
void setup() {
99+
MockitoAnnotations.openMocks(this);
100+
}
101+
102+
@Test
103+
@EnabledForJreRange(min = JRE.JAVA_17)
104+
void hasDescriptionOfRecordObject() throws IOException, ClassNotFoundException, NoSuchMethodException {
105+
Class cls = createRecordObject();
106+
Method method = cls.getMethod("name");
107+
108+
when(methodParameter.getParameterName()).thenReturn("name");
109+
when(methodParameter.isParameterObject()).thenReturn(true);
110+
when(methodParameter.getExecutable()).thenReturn(method);
111+
112+
Map<String, String> recordParamMap = new HashMap<>();
113+
recordParamMap.put("id", "the id");
114+
recordParamMap.put("name", "the name");
115+
when(javadocProvider.getRecordClassParamJavadoc(cls)).thenReturn(recordParamMap);
116+
117+
when(javadocProvider.getFieldJavadoc(any())).thenReturn(null);
118+
119+
String actual = genericParameterService.getParamJavadoc(javadocProvider, methodParameter);
120+
assertEquals("the name", actual);
121+
122+
verify(methodParameter).getParameterName();
123+
verify(methodParameter).isParameterObject();
124+
verify(methodParameter).getExecutable();
125+
verify(javadocProvider).getRecordClassParamJavadoc(cls);
126+
verify(javadocProvider).getFieldJavadoc(any());
127+
}
128+
129+
@Test
130+
void hasDescriptionOfClassObject() throws IOException, ClassNotFoundException, NoSuchMethodException {
131+
Class cls = ClassObject.class;
132+
Method method = cls.getMethod("getName");
133+
134+
when(methodParameter.getParameterName()).thenReturn("name");
135+
when(methodParameter.isParameterObject()).thenReturn(true);
136+
when(methodParameter.getExecutable()).thenReturn(method);
137+
138+
when(javadocProvider.getFieldJavadoc(any())).thenReturn("the name");
139+
140+
String actual = genericParameterService.getParamJavadoc(javadocProvider, methodParameter);
141+
assertEquals("the name", actual);
142+
143+
verify(methodParameter).getParameterName();
144+
verify(methodParameter).isParameterObject();
145+
verify(methodParameter).getExecutable();
146+
verify(javadocProvider).getFieldJavadoc(any());
147+
}
148+
149+
private Class<?> createRecordObject() throws IOException, ClassNotFoundException {
150+
File recordObject = new File(tempDir, "RecordObject.java");
151+
try (PrintWriter writer = new PrintWriter(new FileWriter(recordObject))) {
152+
writer.println("public record RecordObject(String id, String name){");
153+
writer.println("}");
154+
}
155+
String[] args = {
156+
recordObject.getAbsolutePath()
157+
};
158+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
159+
int r = compiler.run(null, null, null, args);
160+
if (r != 0) {
161+
throw new IllegalStateException("Compilation failed");
162+
}
163+
URL[] urls = { tempDir.toURI().toURL() };
164+
ClassLoader loader = URLClassLoader.newInstance(urls);
165+
166+
return loader.loadClass("RecordObject");
167+
}
168+
169+
private class ClassObject {
170+
/**
171+
* the id
172+
*/
173+
private String id;
174+
175+
/**
176+
* the name
177+
*/
178+
private String name;
179+
180+
public ClassObject(String id, String name) {
181+
this.id = id;
182+
this.name = name;
183+
}
184+
185+
public String getId() {
186+
return id;
187+
}
188+
189+
public String getName() {
190+
return name;
191+
}
192+
}
193+
}
194+
}

springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizer.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*
33
* *
44
* * *
5-
* * * * Copyright 2019-2022 the original author or authors.
5+
* * * * Copyright 2019-2023 the original author or authors.
66
* * * *
77
* * * * Licensed under the Apache License, Version 2.0 (the "License");
88
* * * * you may not use this file except in compliance with the License.
@@ -109,13 +109,23 @@ else if (resolvedSchema != null && resolvedSchema.get$ref() != null && resolvedS
109109
* @param fields the fields
110110
* @param existingSchema the existing schema
111111
*/
112-
private void setJavadocDescription(Class<?> cls, List<Field> fields, Schema existingSchema) {
112+
void setJavadocDescription(Class<?> cls, List<Field> fields, Schema existingSchema) {
113113
if (existingSchema != null) {
114114
if (StringUtils.isBlank(existingSchema.getDescription())) {
115115
existingSchema.setDescription(javadocProvider.getClassJavadoc(cls));
116116
}
117117
Map<String, Schema> properties = existingSchema.getProperties();
118-
if (!CollectionUtils.isEmpty(properties))
118+
if (!CollectionUtils.isEmpty(properties)) {
119+
if (cls.getSuperclass() != null && "java.lang.Record".equals(cls.getSuperclass().getName())) {
120+
Map<String, String> recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls);
121+
properties.entrySet().stream()
122+
.filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription()))
123+
.forEach(stringSchemaEntry -> {
124+
if (recordParamMap.containsKey(stringSchemaEntry.getKey()))
125+
stringSchemaEntry.getValue().setDescription(recordParamMap.get(stringSchemaEntry.getKey()));
126+
});
127+
}
128+
119129
properties.entrySet().stream()
120130
.filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription()))
121131
.forEach(stringSchemaEntry -> {
@@ -126,6 +136,7 @@ private void setJavadocDescription(Class<?> cls, List<Field> fields, Schema exis
126136
stringSchemaEntry.getValue().setDescription(fieldJavadoc);
127137
});
128138
});
139+
}
129140
fields.stream().filter(f -> f.isAnnotationPresent(JsonUnwrapped.class))
130141
.forEach(f -> setJavadocDescription(f.getType(), FieldUtils.getAllFieldsList(f.getType()), existingSchema));
131142

springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/SpringDocJavadocProvider.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
*
33
* *
44
* * *
5-
* * * * Copyright 2019-2022 the original author or authors.
5+
* * * * Copyright 2019-2023 the original author or authors.
66
* * * *
77
* * * * Licensed under the Apache License, Version 2.0 (the "License");
88
* * * * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.lang.reflect.Method;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.stream.Collectors;
2930

3031
import com.github.therapi.runtimejavadoc.ClassJavadoc;
3132
import com.github.therapi.runtimejavadoc.CommentFormatter;
@@ -64,6 +65,19 @@ public String getClassJavadoc(Class<?> cl) {
6465
return formatter.format(classJavadoc.getComment());
6566
}
6667

68+
/**
69+
* Gets param descripton of record class.
70+
*
71+
* @param cl the class
72+
* @return map of field and param descriptions
73+
*/
74+
@Override
75+
public Map<String, String> getRecordClassParamJavadoc(Class<?> cl) {
76+
ClassJavadoc classJavadoc = RuntimeJavadoc.getJavadoc(cl);
77+
return classJavadoc.getRecordComponents().stream()
78+
.collect(Collectors.toMap(ParamJavadoc::getName, record -> formatter.format(record.getComment())));
79+
}
80+
6781
/**
6882
* Gets method javadoc description.
6983
*

0 commit comments

Comments
 (0)