Skip to content

Commit 80e3347

Browse files
author
bnasslahsen
committed
Added support for generic controller types parameters
1 parent a96be09 commit 80e3347

File tree

10 files changed

+138
-17
lines changed

10 files changed

+138
-17
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ else if (!RequestMethod.GET.equals(requestMethod)) {
184184
requestBodyBuilder.calculateRequestBodyInfo(components, handlerMethod, methodAttributes, i,
185185
parameterInfo, requestBodyInfo);
186186
}
187-
parameter = customiseParameter(parameter, parameterInfo, handlerMethod);
187+
customiseParameter(parameter, parameterInfo, handlerMethod);
188188
}
189189
}
190190

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

+13-13
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public ApiResponses build(Components components, HandlerMethod handlerMethod, Op
8282
apiResponsesFromDoc.forEach(apiResponses::addApiResponse);
8383
// for each one build ApiResponse and add it to existing responses
8484
// Fill api Responses
85-
computeResponse(components, handlerMethod.getMethod(), apiResponses, methodAttributes, false);
85+
computeResponse(components, handlerMethod.getMethod(), handlerMethod.getReturnType().getParameterType(), apiResponses, methodAttributes, false);
8686
return apiResponses;
8787
}
8888

@@ -97,7 +97,7 @@ public void buildGenericResponse(Components components, Map<String, Object> find
9797
if (reqMappringMethod != null) {
9898
methodProduces = reqMappringMethod.produces();
9999
}
100-
Map<String, ApiResponse> apiResponses = computeResponse(components, method, new ApiResponses(),
100+
Map<String, ApiResponse> apiResponses = computeResponse(components, method,null, new ApiResponses(),
101101
new MethodAttributes(methodProduces, springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType()), true);
102102
apiResponses.forEach(genericMapResponse::put);
103103
}
@@ -118,7 +118,7 @@ private List<Method> getMethods(Map<String, Object> findControllerAdvice) {
118118
return methods;
119119
}
120120

121-
private Map<String, ApiResponse> computeResponse(Components components, Method method, ApiResponses apiResponsesOp,
121+
private Map<String, ApiResponse> computeResponse(Components components, Method method, Class<?> clazz, ApiResponses apiResponsesOp,
122122
MethodAttributes methodAttributes, boolean isGeneric) {
123123
// Parsing documentation, if present
124124
Set<io.swagger.v3.oas.annotations.responses.ApiResponse> responsesArray = getApiResponses(method);
@@ -146,7 +146,7 @@ private Map<String, ApiResponse> computeResponse(Components components, Method m
146146
apiResponsesOp.addApiResponse(apiResponseAnnotations.responseCode(), apiResponse);
147147
}
148148
}
149-
buildApiResponses(components, method, apiResponsesOp, methodAttributes, isGeneric);
149+
buildApiResponses(components, method, clazz, apiResponsesOp, methodAttributes, isGeneric);
150150
return apiResponsesOp;
151151
}
152152

@@ -174,14 +174,14 @@ private void buildContentFromDoc(Components components, ApiResponses apiResponse
174174
}
175175
}
176176

177-
private void buildApiResponses(Components components, Method method, ApiResponses apiResponsesOp,
177+
private void buildApiResponses(Components components, Method method, Class<?> clazz, ApiResponses apiResponsesOp,
178178
MethodAttributes methodAttributes, boolean isGeneric) {
179179
if (!CollectionUtils.isEmpty(apiResponsesOp) && (apiResponsesOp.size() != genericMapResponse.size() || isGeneric)) {
180180
// API Responses at operation and @ApiResponse annotation
181181
for (Map.Entry<String, ApiResponse> entry : apiResponsesOp.entrySet()) {
182182
String httpCode = entry.getKey();
183183
ApiResponse apiResponse = entry.getValue();
184-
buildApiResponses(components, method, apiResponsesOp, methodAttributes, httpCode, apiResponse,
184+
buildApiResponses(components, method, clazz, apiResponsesOp, methodAttributes, httpCode, apiResponse,
185185
isGeneric);
186186
}
187187
}
@@ -192,7 +192,7 @@ private void buildApiResponses(Components components, Method method, ApiResponse
192192
ApiResponse apiResponse = genericMapResponse.containsKey(httpCode) ? genericMapResponse.get(httpCode)
193193
: new ApiResponse();
194194
if (httpCode != null)
195-
buildApiResponses(components, method, apiResponsesOp, methodAttributes, httpCode, apiResponse,
195+
buildApiResponses(components, method, clazz, apiResponsesOp, methodAttributes, httpCode, apiResponse,
196196
isGeneric);
197197
}
198198
}
@@ -222,9 +222,9 @@ private Set<io.swagger.v3.oas.annotations.responses.ApiResponse> getApiResponses
222222
return responses;
223223
}
224224

225-
private Content buildContent(Components components, Method method, String[] methodProduces, JsonView jsonView) {
225+
private Content buildContent(Components components, Method method, Class<?> clazz, String[] methodProduces, JsonView jsonView) {
226226
Content content = new Content();
227-
Type returnType = getReturnType(method);
227+
Type returnType = getReturnType(method,clazz);
228228
if (isVoid(returnType)) {
229229
// if void, no content
230230
content = null;
@@ -241,11 +241,11 @@ else if (ArrayUtils.isNotEmpty(methodProduces)) {
241241
return content;
242242
}
243243

244-
private Type getReturnType(Method method) {
244+
private Type getReturnType(Method method,Class<?> clazz) {
245245
Type returnType = Object.class;
246246
for (ReturnTypeParser returnTypeParser : returnTypeParsers) {
247247
if (returnType.getTypeName().equals(Object.class.getTypeName())) {
248-
returnType = returnTypeParser.getReturnType(method);
248+
returnType = returnTypeParser.getReturnType(method,clazz);
249249
}
250250
else {
251251
break;
@@ -272,12 +272,12 @@ private void setContent(String[] methodProduces, Content content,
272272
Arrays.stream(methodProduces).forEach(mediaTypeStr -> content.addMediaType(mediaTypeStr, mediaType));
273273
}
274274

275-
private void buildApiResponses(Components components, Method method, ApiResponses apiResponsesOp,
275+
private void buildApiResponses(Components components, Method method, Class<?> clazz, ApiResponses apiResponsesOp,
276276
MethodAttributes methodAttributes, String httpCode, ApiResponse apiResponse, boolean isGeneric) {
277277
// No documentation
278278
if (StringUtils.isBlank(apiResponse.get$ref())) {
279279
if (apiResponse.getContent() == null) {
280-
Content content = buildContent(components, method, methodAttributes.getMethodProduces(),
280+
Content content = buildContent(components, method,clazz, methodAttributes.getMethodProduces(),
281281
methodAttributes.getJsonViewAnnotation());
282282
apiResponse.setContent(content);
283283
}

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
package org.springdoc.core;
2020

2121
import java.lang.reflect.Method;
22+
import java.lang.reflect.ParameterizedType;
2223
import java.lang.reflect.Type;
2324

2425
interface ReturnTypeParser {
25-
default Type getReturnType(Method method) {
26-
return method.getGenericReturnType();
26+
27+
default Type getReturnType(Method method, Class<?> clazz) {
28+
if( method.getGenericReturnType() instanceof ParameterizedType || clazz==null)
29+
return method.getGenericReturnType();
30+
return clazz;
2731
}
2832
}
2933

springdoc-openapi-kotlin/src/main/java/org/springdoc/core/KotlinCoroutinesReturnTypeParser.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
public class KotlinCoroutinesReturnTypeParser implements ReturnTypeParser {
3232

3333
@Override
34-
public Type getReturnType(Method method) {
34+
public Type getReturnType(Method method, Class<?> clazz) {
3535
Type returnType = Object.class;
3636
Optional<Parameter> continuationParameter = Arrays.stream(method.getParameters())
3737
.filter(parameter -> parameter.getType().getCanonicalName().equals(Continuation.class.getCanonicalName()))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package test.org.springdoc.api.app93;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public abstract class BaseClientModel {
6+
@JsonProperty("id")
7+
int id;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package test.org.springdoc.api.app93;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
7+
public abstract class BaseController<TClientModel extends BaseClientModel> {
8+
@Operation
9+
@GetMapping
10+
TClientModel get() {
11+
return null;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package test.org.springdoc.api.app93;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public class SpecificClientModel extends BaseClientModel {
6+
@JsonProperty("name")
7+
String name;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package test.org.springdoc.api.app93;
2+
3+
import org.springframework.web.bind.annotation.RestController;
4+
5+
@RestController
6+
public class SpecificController extends BaseController<SpecificClientModel> {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app93;
20+
21+
22+
import test.org.springdoc.api.AbstractSpringDocTest;
23+
24+
import org.springframework.boot.autoconfigure.SpringBootApplication;
25+
26+
public class SpringDocApp93Test extends AbstractSpringDocTest {
27+
28+
@SpringBootApplication
29+
static class SpringDocTestApp {}
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/": {
15+
"get": {
16+
"tags": [
17+
"specific-controller"
18+
],
19+
"operationId": "get",
20+
"responses": {
21+
"200": {
22+
"description": "default response",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"$ref": "#/components/schemas/SpecificClientModel"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
}
34+
},
35+
"components": {
36+
"schemas": {
37+
"SpecificClientModel": {
38+
"type": "object",
39+
"properties": {
40+
"id": {
41+
"type": "integer",
42+
"format": "int32"
43+
},
44+
"name": {
45+
"type": "string"
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)