Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit d542336

Browse files
authored
Deprecated fix (#209)
* deprecated fix * resolution order preferring method
1 parent 3d46821 commit d542336

File tree

3 files changed

+188
-27
lines changed

3 files changed

+188
-27
lines changed

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationVisitorContext.java

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@
2828
import static capital.scalable.restdocs.util.FormatUtil.addDot;
2929
import static capital.scalable.restdocs.util.FormatUtil.join;
3030
import static capital.scalable.restdocs.util.TypeUtil.isPrimitive;
31+
import static capital.scalable.restdocs.util.TypeUtil.resolveAnnotation;
3132
import static org.apache.commons.lang3.StringUtils.isBlank;
3233
import static org.apache.commons.lang3.StringUtils.isNotBlank;
3334
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
3435
import static org.slf4j.LoggerFactory.getLogger;
3536
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
3637

37-
import java.lang.reflect.Field;
3838
import java.util.ArrayList;
3939
import java.util.List;
4040

@@ -45,7 +45,6 @@
4545
import org.slf4j.Logger;
4646
import org.springframework.restdocs.payload.FieldDescriptor;
4747
import org.springframework.restdocs.snippet.Attributes.Attribute;
48-
import org.springframework.util.ReflectionUtils;
4948

5049
class FieldDocumentationVisitorContext {
5150
private static final Logger log = getLogger(FieldDocumentationVisitorContext.class);
@@ -163,9 +162,9 @@ private List<String> resolveConstraintDescriptions(Class<?> javaBaseClass,
163162
}
164163

165164
private String resolveDeprecatedMessage(Class<?> javaBaseClass, String javaFieldName) {
166-
Field field = ReflectionUtils.findField(javaBaseClass, javaFieldName);
167-
boolean isDeprecated = field != null && field.getAnnotation(Deprecated.class) != null;
168-
String comment = javadocReader.resolveFieldTag(javaBaseClass, javaFieldName, "deprecated");
165+
boolean isDeprecated =
166+
resolveAnnotation(javaBaseClass, javaFieldName, Deprecated.class) != null;
167+
String comment = resolveTag(javaBaseClass, javaFieldName, "deprecated");
169168
if (isDeprecated || isNotBlank(comment)) {
170169
return trimToEmpty(comment);
171170
} else {
@@ -182,29 +181,34 @@ private String resolveSeeTag(Class<?> javaBaseClass, String javaFieldName) {
182181
}
183182
}
184183

185-
private String resolveComment(Class<?> javaBaseClass, String javaFieldName) {
186-
String comment = javadocReader.resolveFieldComment(javaBaseClass, javaFieldName);
184+
private String resolveComment(Class<?> javaBaseClass, String javaFieldOrMethodName) {
185+
// prefer method comments first
186+
String comment = javadocReader.resolveMethodComment(javaBaseClass, javaFieldOrMethodName);
187187
if (isBlank(comment)) {
188-
// fallback if fieldName is getter method and comment is on the method itself
189-
comment = javadocReader.resolveMethodComment(javaBaseClass, javaFieldName);
188+
// fallback to field
189+
comment = javadocReader.resolveFieldComment(javaBaseClass, javaFieldOrMethodName);
190190
}
191-
if (isBlank(comment) && isGetter(javaFieldName)) {
192-
// fallback if fieldName is getter method but comment is on field itself
193-
comment = javadocReader.resolveFieldComment(javaBaseClass, fromGetter(javaFieldName));
191+
if (isBlank(comment) && isGetter(javaFieldOrMethodName)) {
192+
// fallback if name is getter method but comment is on field itself
193+
comment = javadocReader.resolveFieldComment(javaBaseClass,
194+
fromGetter(javaFieldOrMethodName));
194195
}
195196
return comment;
196197
}
197198

198-
private String resolveTag(Class<?> javaBaseClass, String javaFieldName, String tagName) {
199-
String comment = javadocReader.resolveFieldTag(javaBaseClass, javaFieldName, tagName);
199+
private String resolveTag(Class<?> javaBaseClass, String javaFieldOrMethodName,
200+
String tagName) {
201+
// prefer method comments first
202+
String comment = javadocReader.resolveMethodTag(javaBaseClass, javaFieldOrMethodName,
203+
tagName);
200204
if (isBlank(comment)) {
201-
// fallback if fieldName is getter method and comment is on the method itself
202-
comment = javadocReader.resolveMethodTag(javaBaseClass, javaFieldName, tagName);
205+
// fallback to field
206+
comment = javadocReader.resolveFieldTag(javaBaseClass, javaFieldOrMethodName, tagName);
203207
}
204-
if (isBlank(comment) && isGetter(javaFieldName)) {
205-
// fallback if fieldName is getter method but comment is on field itself
206-
comment = javadocReader.resolveFieldTag(javaBaseClass, fromGetter(javaFieldName),
207-
tagName);
208+
if (isBlank(comment) && isGetter(javaFieldOrMethodName)) {
209+
// fallback if name is getter method but comment is on field itself
210+
comment = javadocReader.resolveFieldTag(javaBaseClass,
211+
fromGetter(javaFieldOrMethodName), tagName);
208212
}
209213
return comment;
210214
}

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/util/TypeUtil.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import static org.apache.commons.lang3.ClassUtils.primitiveToWrapper;
2525
import static org.apache.commons.lang3.StringUtils.chop;
2626

27+
import java.lang.annotation.Annotation;
2728
import java.lang.reflect.Field;
29+
import java.lang.reflect.Method;
2830
import java.util.ArrayList;
2931
import java.util.List;
3032

@@ -98,13 +100,54 @@ private static String getSpecialTypeName(String canonicalName) {
98100
}
99101

100102
public static boolean isPrimitive(Class<?> javaBaseClass, String javaFieldName) {
101-
Field field = ReflectionUtils.findField(javaBaseClass, javaFieldName);
102-
if (field == null && isGetter(javaFieldName)) {
103-
field = ReflectionUtils.findField(javaBaseClass, fromGetter(javaFieldName));
104-
}
103+
Field field = findField(javaBaseClass, javaFieldName);
105104
return field != null && field.getType().isPrimitive();
106105
}
107106

107+
public static Field findField(Class<?> javaBaseClass, String javaFieldOrMethodName) {
108+
Field field = ReflectionUtils.findField(javaBaseClass, javaFieldOrMethodName);
109+
if (field == null && isGetter(javaFieldOrMethodName)) {
110+
field = ReflectionUtils.findField(javaBaseClass, fromGetter(javaFieldOrMethodName));
111+
}
112+
return field;
113+
}
114+
115+
public static Method findMethod(Class<?> javaBaseClass, String javaMethodName) {
116+
return ReflectionUtils.findMethod(javaBaseClass, javaMethodName);
117+
}
118+
119+
public static <T extends Annotation> T resolveAnnotation(Class<?> javaBaseClass,
120+
String javaFieldOrMethodName, Class<T> annotationClass) {
121+
// prefer method lookup
122+
T annotation = findMethodAnnotation(javaBaseClass, javaFieldOrMethodName, annotationClass);
123+
if (annotation == null) {
124+
// fallback to field
125+
return findFieldAnnotation(javaBaseClass, javaFieldOrMethodName, annotationClass);
126+
}
127+
return annotation;
128+
}
129+
130+
private static <T extends Annotation> T findMethodAnnotation(Class<?> javaBaseClass,
131+
String javaMethodName, Class<T> annotationClass) {
132+
Method method = findMethod(javaBaseClass, javaMethodName);
133+
return method != null ? method.getAnnotation(annotationClass) : null;
134+
}
135+
136+
private static <T extends Annotation> T findFieldAnnotation(Class<?> javaBaseClass,
137+
String javaFieldOrMethodName, Class<T> annotationClass) {
138+
T annotation = null;
139+
// try field name
140+
Field field = ReflectionUtils.findField(javaBaseClass, javaFieldOrMethodName);
141+
if (field != null) {
142+
annotation = field.getAnnotation(annotationClass);
143+
}
144+
// try field name from getter
145+
if (annotation == null && isGetter(javaFieldOrMethodName)) {
146+
field = ReflectionUtils.findField(javaBaseClass, fromGetter(javaFieldOrMethodName));
147+
}
148+
return field != null ? field.getAnnotation(annotationClass) : null;
149+
}
150+
108151
public static List<JavaType> resolveAllTypes(JavaType javaType, TypeFactory typeFactory) {
109152
if (javaType.isCollectionLikeType() || javaType.isArrayType()) {
110153
return resolveArrayTypes(javaType.getContentType(), typeFactory);

spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippetTest.java

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,54 @@ public void escaping() throws Exception {
269269
public void deprecated() throws Exception {
270270
HandlerMethod handlerMethod = createHandlerMethod("removeItem");
271271
mockFieldComment(DeprecatedItem.class, "index", "item's index");
272-
mockDeprecated(DeprecatedItem.class, "index", "use index2");
272+
mockFieldComment(DeprecatedItem.class, "index2", "item's index2");
273+
mockFieldComment(DeprecatedItem.class, "index3", "item's index3");
274+
mockFieldComment(DeprecatedItem.class, "index4", "item's index4");
275+
mockFieldComment(DeprecatedItem.class, "index5", "item's index5");
276+
mockMethodComment(DeprecatedItem.class, "getIndex6", "item's index6");
277+
278+
// index2 and getIndex4 have @deprecated Javadoc
279+
mockDeprecatedField(DeprecatedItem.class, "index2", "use something else");
280+
mockDeprecatedMethod(DeprecatedItem.class, "getIndex4", "use something else");
273281

274282
this.snippets.expect(RESPONSE_FIELDS).withContents(
275283
tableWithHeader("Path", "Type", "Optional", "Description")
276284
.row("index", "Integer", "true",
277-
"**Deprecated.** Use index2.\n\nItem's index."));
285+
"**Deprecated.**\n\nItem's index.")
286+
.row("index2", "Integer", "true",
287+
"**Deprecated.** Use something else.\n\nItem's index2.")
288+
.row("index3", "Integer", "true",
289+
"**Deprecated.**\n\nItem's index3.")
290+
.row("index4", "Integer", "true",
291+
"**Deprecated.** Use something else.\n\nItem's index4.")
292+
.row("index5", "Integer", "true",
293+
"**Deprecated.**\n\nItem's index5.")
294+
.row("index6", "Integer", "true",
295+
"**Deprecated.**\n\nItem's index6."));
296+
297+
new JacksonResponseFieldSnippet().document(operationBuilder
298+
.attribute(HandlerMethod.class.getName(), handlerMethod)
299+
.attribute(ObjectMapper.class.getName(), mapper)
300+
.attribute(JavadocReader.class.getName(), javadocReader)
301+
.attribute(ConstraintReader.class.getName(), constraintReader)
302+
.build());
303+
}
304+
305+
@Test
306+
public void comment() throws Exception {
307+
HandlerMethod handlerMethod = createHandlerMethod("commentItem");
308+
mockFieldComment(CommentedItem.class, "field", "field");
309+
mockFieldComment(CommentedItem.class, "field2", "field 2");
310+
mockFieldComment(CommentedItem.class, "field3", "field 3");
311+
mockMethodComment(CommentedItem.class, "getField3", "method 3"); // preferred
312+
mockMethodComment(CommentedItem.class, "getField4", "method 4");
313+
314+
this.snippets.expect(RESPONSE_FIELDS).withContents(
315+
tableWithHeader("Path", "Type", "Optional", "Description")
316+
.row("field", "String", "true", "Field.")
317+
.row("field2", "String", "true", "Field 2.")
318+
.row("field3", "String", "true", "Method 3.")
319+
.row("field4", "String", "true", "Method 4."));
278320

279321
new JacksonResponseFieldSnippet().document(operationBuilder
280322
.attribute(HandlerMethod.class.getName(), handlerMethod)
@@ -299,7 +341,17 @@ private void mockFieldComment(Class<?> type, String fieldName, String comment) {
299341
.thenReturn(comment);
300342
}
301343

302-
private void mockDeprecated(Class<?> type, String fieldName, String comment) {
344+
private void mockMethodComment(Class<?> type, String methodName, String comment) {
345+
when(javadocReader.resolveMethodComment(type, methodName))
346+
.thenReturn(comment);
347+
}
348+
349+
private void mockDeprecatedMethod(Class<?> type, String methodName, String comment) {
350+
when(javadocReader.resolveMethodTag(type, methodName, "deprecated"))
351+
.thenReturn(comment);
352+
}
353+
354+
private void mockDeprecatedField(Class<?> type, String fieldName, String comment) {
303355
when(javadocReader.resolveFieldTag(type, fieldName, "deprecated"))
304356
.thenReturn(comment);
305357
}
@@ -352,6 +404,10 @@ public String processItem() {
352404
public DeprecatedItem removeItem() {
353405
return null;
354406
}
407+
408+
public CommentedItem commentItem() {
409+
return null;
410+
}
355411
}
356412

357413
private static class Item {
@@ -367,8 +423,66 @@ public Item(String field1) {
367423
}
368424

369425
private static class DeprecatedItem {
426+
// dep. annot on field, no getter
370427
@Deprecated
371428
private int index;
429+
430+
// dep. Javadoc on field, no getter
431+
private int index2;
432+
433+
// field, dep. annot on getter
434+
private int index3;
435+
436+
// field, dep. Javadoc on getter
437+
private int index4;
438+
439+
// dep. annot on field, getter
440+
@Deprecated
441+
private int index5;
442+
443+
@Deprecated
444+
public int getIndex3() {
445+
return index3;
446+
}
447+
448+
// deprecation Javadoc
449+
public int getIndex4() {
450+
return index4;
451+
}
452+
453+
public int getIndex5() {
454+
return index5;
455+
}
456+
457+
// no real field
458+
@Deprecated
459+
public int getIndex6() {
460+
return 0;
461+
}
462+
}
463+
464+
private static class CommentedItem {
465+
// comment on field only, no getter
466+
private String field;
467+
468+
// comment on field, empty on getter
469+
private String field2;
470+
471+
// comment on field and getter
472+
private String field3;
473+
474+
public String getField2() {
475+
return field2;
476+
}
477+
478+
public String getField3() {
479+
return field3;
480+
}
481+
482+
// comment only on getter
483+
public String getField4() {
484+
return null;
485+
}
372486
}
373487

374488
private static class ProcessingResponse {

0 commit comments

Comments
 (0)