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

Commit 2c876fc

Browse files
cmercerfbenz
authored andcommitted
1.0.x branch Super class generics failing to find return type (#314)
* #311 1.0x branch AbstractJacksonFieldSnipplet.java - Updated to deal with superclass generic return type JacksonResponseFieldSnippetTest.java - Updated test objects to add super classes and interfaces - Added genericSuperMethod() test * #311 More Generics fixes AbstractJacksonFieldSnippet.java - Handle the cases there MethodParameter is a TypeVariable JacksonResponseFieldSnippet.java - Handle the cases where the return type is a TypeVariable -- public E getItem() JacksonResponseFieldSnippetTest.java - Tests cases for TypeVariable return type * #311 More Generics fixes JacksonResponseFieldSnippet.java - check for instanceof TypeVariable vs null * Formatting feedback
1 parent 0c3a15a commit 2c876fc

File tree

3 files changed

+99
-3
lines changed

3 files changed

+99
-3
lines changed

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/AbstractJacksonFieldSnippet.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import java.lang.reflect.ParameterizedType;
3030
import java.lang.reflect.Type;
31+
import java.lang.reflect.TypeVariable;
3132
import java.util.Collection;
3233
import java.util.LinkedHashMap;
3334
import java.util.List;
@@ -40,6 +41,8 @@
4041
import capital.scalable.restdocs.snippet.StandardTableSnippet;
4142
import com.fasterxml.jackson.databind.JsonMappingException;
4243
import com.fasterxml.jackson.databind.ObjectMapper;
44+
import org.apache.commons.lang3.StringUtils;
45+
import org.springframework.core.GenericTypeResolver;
4346
import org.springframework.core.MethodParameter;
4447
import org.springframework.restdocs.operation.Operation;
4548
import org.springframework.restdocs.payload.FieldDescriptor;
@@ -88,13 +91,36 @@ protected Collection<FieldDescriptor> createFieldDescriptors(Operation operation
8891

8992
protected Type firstGenericType(MethodParameter param) {
9093
Type type = param.getGenericParameterType();
91-
if (type != null && type instanceof ParameterizedType) {
94+
if (type instanceof TypeVariable) {
95+
TypeVariable tv = (TypeVariable)type;
96+
return findTypeFromTypeVariable(tv, param.getContainingClass());
97+
}
98+
else if (type instanceof ParameterizedType) {
99+
ParameterizedType parameterizedType = (ParameterizedType) type;
100+
Type actualArgument = parameterizedType.getActualTypeArguments()[0];
101+
if (actualArgument instanceof Class) {
102+
return actualArgument;
103+
} else if (actualArgument instanceof TypeVariable) {
104+
TypeVariable typeVariable = (TypeVariable) actualArgument;
105+
return findTypeFromTypeVariable(typeVariable, param.getContainingClass());
106+
}
92107
return ((ParameterizedType) type).getActualTypeArguments()[0];
93108
} else {
94109
return Object.class;
95110
}
96111
}
97112

113+
protected Type findTypeFromTypeVariable(TypeVariable typeVariable, Class<?> clazz) {
114+
String variableName = typeVariable.getName();
115+
Map<TypeVariable, Type> typeMap = GenericTypeResolver.getTypeVariableMap(clazz);
116+
for (TypeVariable tv : typeMap.keySet()) {
117+
if (StringUtils.equals(tv.getName(), variableName)) {
118+
return typeMap.get(tv);
119+
}
120+
}
121+
return Object.class;
122+
}
123+
98124
protected abstract Type getType(HandlerMethod method);
99125

100126
protected abstract boolean shouldFailOnUndocumentedFields();

spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippet.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.lang.reflect.GenericArrayType;
2323
import java.lang.reflect.Type;
24+
import java.lang.reflect.TypeVariable;
2425
import java.util.Map;
2526

2627
import org.springframework.http.HttpEntity;
@@ -76,7 +77,10 @@ public Type getGenericComponentType() {
7677
};
7778
} else if ("void".equals(returnType.getName())) {
7879
return null;
79-
} else {
80+
} else if (method.getReturnType().getGenericParameterType() instanceof TypeVariable) {
81+
return firstGenericType(method.getReturnType());
82+
}
83+
else {
8084
return returnType;
8185
}
8286
}

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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,46 @@ public void comment() throws Exception {
326326
.build());
327327
}
328328

329+
330+
331+
@Test
332+
public void genericSuperMethodCollection() throws Exception {
333+
HandlerMethod handlerMethod = createHandlerMethod("getItemsGeneric");
334+
mockFieldComment(Item.class, "field1", "A string");
335+
mockFieldComment(Item.class, "field2", "A decimal");
336+
337+
this.snippets.expect(RESPONSE_FIELDS).withContents(
338+
tableWithHeader("Path", "Type", "Optional", "Description")
339+
.row("[].field1", "String", "true", "A string.")
340+
.row("[].field2", "Decimal", "true", "A decimal."));
341+
342+
new JacksonResponseFieldSnippet().document(operationBuilder
343+
.attribute(HandlerMethod.class.getName(), handlerMethod)
344+
.attribute(ObjectMapper.class.getName(), mapper)
345+
.attribute(JavadocReader.class.getName(), javadocReader)
346+
.attribute(ConstraintReader.class.getName(), constraintReader)
347+
.build());
348+
}
349+
350+
@Test
351+
public void genericSuperMethodSingleItem() throws Exception {
352+
HandlerMethod handlerMethod = createHandlerMethod("getItemGeneric");
353+
mockFieldComment(Item.class, "field1", "A string");
354+
mockFieldComment(Item.class, "field2", "A decimal");
355+
356+
this.snippets.expect(RESPONSE_FIELDS).withContents(
357+
tableWithHeader("Path", "Type", "Optional", "Description")
358+
.row("field1", "String", "true", "A string.")
359+
.row("field2", "Decimal", "true", "A decimal."));
360+
361+
new JacksonResponseFieldSnippet().document(operationBuilder
362+
.attribute(HandlerMethod.class.getName(), handlerMethod)
363+
.attribute(ObjectMapper.class.getName(), mapper)
364+
.attribute(JavadocReader.class.getName(), javadocReader)
365+
.attribute(ConstraintReader.class.getName(), constraintReader)
366+
.build());
367+
}
368+
329369
private void mockConstraintMessage(Class<?> type, String fieldName, String comment) {
330370
when(constraintReader.getConstraintMessages(type, fieldName))
331371
.thenReturn(singletonList(comment));
@@ -371,7 +411,33 @@ private HandlerMethod createHandlerMethod(String responseEntityItem)
371411
return new HandlerMethod(new TestResource(), responseEntityItem);
372412
}
373413

374-
private static class TestResource {
414+
public interface IGenericTestResource<T> {
415+
416+
List<T> getItemsGeneric();
417+
}
418+
419+
public static abstract class GenericTestResource<E> implements IGenericTestResource<E>{
420+
421+
abstract E createGeneric();
422+
423+
@Override
424+
public List<E> getItemsGeneric() {
425+
return Collections.singletonList(createGeneric());
426+
}
427+
428+
public E getItemGeneric() {
429+
return createGeneric();
430+
}
431+
}
432+
433+
434+
435+
private static class TestResource extends GenericTestResource<Item>{
436+
437+
@Override
438+
Item createGeneric() {
439+
return new Item("test");
440+
}
375441

376442
public Item getItem() {
377443
return new Item("test");

0 commit comments

Comments
 (0)