diff --git a/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/AbstractJacksonFieldSnippet.java b/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/AbstractJacksonFieldSnippet.java index 96114530..2f605430 100644 --- a/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/AbstractJacksonFieldSnippet.java +++ b/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/AbstractJacksonFieldSnippet.java @@ -7,9 +7,9 @@ * 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 - * + * * http://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. @@ -90,20 +90,17 @@ protected FieldDescriptors createFieldDescriptors(Operation operation, protected Type firstGenericType(MethodParameter param) { Type type = param.getGenericParameterType(); - if (type instanceof ParameterizedType) { + if(type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable)type; + return findTypeFromTypeVariable(tv, param.getContainingClass()); + } else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; Type actualArgument = parameterizedType.getActualTypeArguments()[0]; if(actualArgument instanceof Class) { return actualArgument; } else if(actualArgument instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable)actualArgument; - String variableName = typeVariable.getName(); - Map typeMap = GenericTypeResolver.getTypeVariableMap(param.getContainingClass()); - for(TypeVariable tv : typeMap.keySet()) { - if(StringUtils.equals(tv.getName(), variableName)) { - return typeMap.get(tv); - } - } + return findTypeFromTypeVariable(typeVariable, param.getContainingClass()); } return ((ParameterizedType) type).getActualTypeArguments()[0]; } else { @@ -111,6 +108,19 @@ protected Type firstGenericType(MethodParameter param) { } } + protected Type findTypeFromTypeVariable(TypeVariable typeVariable, Class clazz) { + Type defaultReturnValue = Object.class; + + String variableName = typeVariable.getName(); + Map typeMap = GenericTypeResolver.getTypeVariableMap(clazz); + for(TypeVariable tv : typeMap.keySet()) { + if(StringUtils.equals(tv.getName(), variableName)) { + return typeMap.get(tv); + } + } + return defaultReturnValue; + } + protected abstract Type getType(HandlerMethod method); protected abstract boolean shouldFailOnUndocumentedFields(); diff --git a/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippet.java b/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippet.java index 98528752..19f5ce73 100644 --- a/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippet.java +++ b/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippet.java @@ -7,9 +7,9 @@ * 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 - * + * * http://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. @@ -25,6 +25,7 @@ import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Map; import capital.scalable.restdocs.jackson.FieldDescriptors; @@ -83,6 +84,8 @@ protected Type getType(final HandlerMethod method) { } } else if (REACTOR_FLUX_CLASS.equals(returnType.getCanonicalName())) { return (GenericArrayType) () -> firstGenericType(method.getReturnType()); + } else if (method.getReturnType().getGenericParameterType() instanceof TypeVariable) { + return firstGenericType(method.getReturnType()); } else { return returnType; } diff --git a/spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippetTest.java b/spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippetTest.java index d484a762..d4177826 100644 --- a/spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippetTest.java +++ b/spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/payload/JacksonResponseFieldSnippetTest.java @@ -433,7 +433,7 @@ public void comment() throws Exception { } @Test - public void genericSuperMethod() throws Exception{ + public void genericSuperMethodCollection() throws Exception{ HandlerMethod handlerMethod = createHandlerMethod("getItemsGeneric"); mockFieldComment(Item.class, "field1", "A string"); mockFieldComment(Item.class, "field2", "A decimal"); @@ -452,6 +452,25 @@ public void genericSuperMethod() throws Exception{ } + @Test + public void genericSuperMethodSingleItem() throws Exception { + HandlerMethod handlerMethod = createHandlerMethod("getItemGeneric"); + mockFieldComment(Item.class, "field1", "A string"); + mockFieldComment(Item.class, "field2", "A decimal"); + + new JacksonResponseFieldSnippet().document(operationBuilder + .attribute(HandlerMethod.class.getName(), handlerMethod) + .attribute(ObjectMapper.class.getName(), mapper) + .attribute(JavadocReader.class.getName(), javadocReader) + .attribute(ConstraintReader.class.getName(), constraintReader) + .build()); + + assertThat(this.generatedSnippets.snippet(AUTO_RESPONSE_FIELDS)).is( + tableWithHeader("Path", "Type", "Optional", "Description") + .row("field1", "String", "true", "A string.") + .row("field2", "Decimal", "true", "A decimal.")); + } + private void mockConstraintMessage(Class type, String fieldName, String comment) { when(constraintReader.getConstraintMessages(type, fieldName)) @@ -511,6 +530,10 @@ public static abstract class GenericTestResource implements IGenericTestResou public List getItemsGeneric() { return Collections.singletonList(createGeneric()); } + + public E getItemGeneric() { + return createGeneric(); + } } // actual method responses do not matter, they are here just for the illustration