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

Commit 9112c82

Browse files
cmercerfbenz
authored andcommitted
Super class generics failing to find return type (#313)
* #311 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 * Formatting feedback
1 parent 05f60e4 commit 9112c82

File tree

3 files changed

+98
-6
lines changed

3 files changed

+98
-6
lines changed

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -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.Map;
3334
import java.util.stream.Stream;
@@ -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.web.method.HandlerMethod;
@@ -87,13 +90,35 @@ protected FieldDescriptors createFieldDescriptors(Operation operation,
8790

8891
protected Type firstGenericType(MethodParameter param) {
8992
Type type = param.getGenericParameterType();
90-
if (type instanceof ParameterizedType) {
93+
if (type instanceof TypeVariable) {
94+
TypeVariable tv = (TypeVariable) type;
95+
return findTypeFromTypeVariable(tv, param.getContainingClass());
96+
} else if (type instanceof ParameterizedType) {
97+
ParameterizedType parameterizedType = (ParameterizedType) type;
98+
Type actualArgument = parameterizedType.getActualTypeArguments()[0];
99+
if (actualArgument instanceof Class) {
100+
return actualArgument;
101+
} else if (actualArgument instanceof TypeVariable) {
102+
TypeVariable typeVariable = (TypeVariable)actualArgument;
103+
return findTypeFromTypeVariable(typeVariable, param.getContainingClass());
104+
}
91105
return ((ParameterizedType) type).getActualTypeArguments()[0];
92106
} else {
93107
return Object.class;
94108
}
95109
}
96110

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

99124
protected abstract boolean shouldFailOnUndocumentedFields();

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,6 +25,7 @@
2525
import java.lang.reflect.GenericArrayType;
2626
import java.lang.reflect.ParameterizedType;
2727
import java.lang.reflect.Type;
28+
import java.lang.reflect.TypeVariable;
2829
import java.util.Map;
2930

3031
import capital.scalable.restdocs.jackson.FieldDescriptors;
@@ -83,6 +84,8 @@ protected Type getType(final HandlerMethod method) {
8384
}
8485
} else if (REACTOR_FLUX_CLASS.equals(returnType.getCanonicalName())) {
8586
return (GenericArrayType) () -> firstGenericType(method.getReturnType());
87+
} else if (method.getReturnType().getGenericParameterType() instanceof TypeVariable) {
88+
return firstGenericType(method.getReturnType());
8689
} else {
8790
return returnType;
8891
}

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

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,46 @@ public void comment() throws Exception {
432432
.row("field4", "String", "true", "Method 4."));
433433
}
434434

435+
@Test
436+
public void genericSuperMethodCollection() throws Exception{
437+
HandlerMethod handlerMethod = createHandlerMethod("getItemsGeneric");
438+
mockFieldComment(Item.class, "field1", "A string");
439+
mockFieldComment(Item.class, "field2", "A decimal");
440+
441+
new JacksonResponseFieldSnippet().document(operationBuilder
442+
.attribute(HandlerMethod.class.getName(), handlerMethod)
443+
.attribute(ObjectMapper.class.getName(), mapper)
444+
.attribute(JavadocReader.class.getName(), javadocReader)
445+
.attribute(ConstraintReader.class.getName(), constraintReader)
446+
.build());
447+
448+
assertThat(this.generatedSnippets.snippet(AUTO_RESPONSE_FIELDS)).is(
449+
tableWithHeader("Path", "Type", "Optional", "Description")
450+
.row("[].field1", "String", "true", "A string.")
451+
.row("[].field2", "Decimal", "true", "A decimal."));
452+
453+
}
454+
455+
@Test
456+
public void genericSuperMethodSingleItem() throws Exception {
457+
HandlerMethod handlerMethod = createHandlerMethod("getItemGeneric");
458+
mockFieldComment(Item.class, "field1", "A string");
459+
mockFieldComment(Item.class, "field2", "A decimal");
460+
461+
new JacksonResponseFieldSnippet().document(operationBuilder
462+
.attribute(HandlerMethod.class.getName(), handlerMethod)
463+
.attribute(ObjectMapper.class.getName(), mapper)
464+
.attribute(JavadocReader.class.getName(), javadocReader)
465+
.attribute(ConstraintReader.class.getName(), constraintReader)
466+
.build());
467+
468+
assertThat(this.generatedSnippets.snippet(AUTO_RESPONSE_FIELDS)).is(
469+
tableWithHeader("Path", "Type", "Optional", "Description")
470+
.row("field1", "String", "true", "A string.")
471+
.row("field2", "Decimal", "true", "A decimal."));
472+
}
473+
474+
435475
private void mockConstraintMessage(Class<?> type, String fieldName, String comment) {
436476
when(constraintReader.getConstraintMessages(type, fieldName))
437477
.thenReturn(singletonList(comment));
@@ -477,8 +517,32 @@ private HandlerMethod createHandlerMethod(String responseEntityItem)
477517
return new HandlerMethod(new TestResource(), responseEntityItem);
478518
}
479519

520+
public interface IGenericTestResource<T> {
521+
522+
List<T> getItemsGeneric();
523+
}
524+
525+
public static abstract class GenericTestResource<E> implements IGenericTestResource<E>{
526+
527+
abstract E createGeneric();
528+
529+
@Override
530+
public List<E> getItemsGeneric() {
531+
return Collections.singletonList(createGeneric());
532+
}
533+
534+
public E getItemGeneric() {
535+
return createGeneric();
536+
}
537+
}
538+
480539
// actual method responses do not matter, they are here just for the illustration
481-
private static class TestResource {
540+
private static class TestResource extends GenericTestResource<Item>{
541+
542+
@Override
543+
Item createGeneric() {
544+
return new Item("test");
545+
}
482546

483547
public Item getItem() {
484548
return new Item("test");

0 commit comments

Comments
 (0)