Skip to content

Commit 2594a27

Browse files
author
bnasslahsen
committed
Wrong schema mapping with inheritance. Fixes #489
1 parent df545f1 commit 2594a27

File tree

11 files changed

+80
-94
lines changed

11 files changed

+80
-94
lines changed

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

+13-18
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springdoc.core.customizers.ParameterCustomizer;
5858

5959
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
60+
import org.springframework.core.MethodParameter;
6061
import org.springframework.core.annotation.AnnotatedElementUtils;
6162
import org.springframework.core.annotation.AnnotationUtils;
6263
import org.springframework.http.HttpMethod;
@@ -140,13 +141,12 @@ public Operation build(Components components, HandlerMethod handlerMethod, Reque
140141
// Documentation
141142
String operationId = operationBuilder.getOperationId(handlerMethod.getMethod().getName(),
142143
operation.getOperationId(), openAPI);
143-
144144
operation.setOperationId(operationId);
145145
// requests
146146
LocalVariableTableParameterNameDiscoverer d = parameterBuilder.getLocalSpringDocParameterNameDiscoverer();
147147
String[] pNames = d.getParameterNames(handlerMethod.getMethod());
148-
java.lang.reflect.Parameter[] parameters = handlerMethod.getMethod().getParameters();
149-
String[] reflectionParametersNames = Arrays.stream(parameters).map(java.lang.reflect.Parameter::getName).toArray(String[]::new);
148+
MethodParameter[] parameters = handlerMethod.getMethodParameters();
149+
String[] reflectionParametersNames = Arrays.stream(parameters).map(MethodParameter::getParameterName).toArray(String[]::new);
150150
if (pNames == null) {
151151
pNames = reflectionParametersNames;
152152
}
@@ -159,8 +159,7 @@ public Operation build(Components components, HandlerMethod handlerMethod, Reque
159159
// check if query param
160160
Parameter parameter = null;
161161
final String pName = pNames[i] == null ? reflectionParametersNames[i] : pNames[i];
162-
io.swagger.v3.oas.annotations.Parameter parameterDoc = parameterBuilder.getParameterAnnotation(
163-
handlerMethod, parameters[i], i, io.swagger.v3.oas.annotations.Parameter.class);
162+
io.swagger.v3.oas.annotations.Parameter parameterDoc = parameters[i].getParameterAnnotation(io.swagger.v3.oas.annotations.Parameter.class);
164163
if (parameterDoc == null) {
165164
parameterDoc = parametersDocMap.get(pName);
166165
}
@@ -173,21 +172,21 @@ public Operation build(Components components, HandlerMethod handlerMethod, Reque
173172
methodAttributes.getJsonViewAnnotation());
174173
}
175174

176-
if (!isParamToIgnore(parameters[i])) {
175+
if (!isParamToIgnore(parameters[i].getParameter())) {
177176
ParameterInfo parameterInfo = new ParameterInfo(pName, parameters[i], parameter, i);
178177
parameter = buildParams(parameterInfo, components, handlerMethod, requestMethod,
179178
methodAttributes.getJsonViewAnnotation());
180179
// Merge with the operation parameters
181180
parameter = parameterBuilder.mergeParameter(operationParameters, parameter);
182181
if (isValidParameter(parameter)) {
183-
applyBeanValidatorAnnotations(parameter, Arrays.asList(parameters[i].getAnnotations()));
182+
applyBeanValidatorAnnotations(parameter, Arrays.asList(parameters[i].getParameterAnnotations()));
184183
}
185184
else if (!RequestMethod.GET.equals(requestMethod)) {
186185
if (operation.getRequestBody() != null)
187186
requestBodyInfo.setRequestBody(operation.getRequestBody());
188187
requestBodyBuilder.calculateRequestBodyInfo(components, handlerMethod, methodAttributes, i,
189188
parameterInfo, requestBodyInfo);
190-
applyBeanValidatorAnnotations(requestBodyInfo.getRequestBody(), Arrays.asList(parameters[i].getAnnotations()));
189+
applyBeanValidatorAnnotations(requestBodyInfo.getRequestBody(), Arrays.asList(parameters[i].getParameterAnnotations()));
191190
}
192191
customiseParameter(parameter, parameterInfo, handlerMethod);
193192
}
@@ -265,17 +264,13 @@ private boolean isValidParameter(Parameter parameter) {
265264

266265
private Parameter buildParams(ParameterInfo parameterInfo, Components components, HandlerMethod handlerMethod,
267266
RequestMethod requestMethod, JsonView jsonView) {
268-
java.lang.reflect.Parameter parameters = parameterInfo.getParameter();
267+
MethodParameter methodParameter = parameterInfo.getMethodParameter();
269268
int index = parameterInfo.getIndex();
270269

271-
RequestHeader requestHeader = parameterBuilder.getParameterAnnotation(handlerMethod, parameters, index,
272-
RequestHeader.class);
273-
RequestParam requestParam = parameterBuilder.getParameterAnnotation(handlerMethod, parameters, index,
274-
RequestParam.class);
275-
PathVariable pathVar = parameterBuilder.getParameterAnnotation(handlerMethod, parameters, index,
276-
PathVariable.class);
277-
CookieValue cookieValue = parameterBuilder.getParameterAnnotation(handlerMethod, parameters, index,
278-
CookieValue.class);
270+
RequestHeader requestHeader = methodParameter.getParameterAnnotation(RequestHeader.class);
271+
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
272+
PathVariable pathVar = methodParameter.getParameterAnnotation(PathVariable.class);
273+
CookieValue cookieValue = methodParameter.getParameterAnnotation(CookieValue.class);
279274

280275
Parameter parameter = null;
281276
RequestInfo requestInfo;
@@ -287,7 +282,7 @@ private Parameter buildParams(ParameterInfo parameterInfo, Components components
287282

288283
}
289284
else if (requestParam != null && !parameterBuilder.isFile(parameterInfo.getParameter())) {
290-
boolean isOptional = Optional.class.equals(parameters.getType());
285+
boolean isOptional = Optional.class.equals(methodParameter.getParameterType());
291286
requestInfo = new RequestInfo(ParameterIn.QUERY.toString(), requestParam.value(), requestParam.required() && !isOptional,
292287
requestParam.defaultValue());
293288
parameter = buildParam(parameterInfo, components, requestInfo, jsonView);

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterBuilder.java

+13-37
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
package org.springdoc.core;
2020

2121
import java.io.IOException;
22-
import java.lang.annotation.Annotation;
23-
import java.lang.reflect.Method;
2422
import java.lang.reflect.ParameterizedType;
2523
import java.lang.reflect.Type;
2624
import java.lang.reflect.WildcardType;
@@ -30,7 +28,6 @@
3028
import java.util.List;
3129
import java.util.Map;
3230
import java.util.Optional;
33-
import java.util.Set;
3431

3532
import com.fasterxml.jackson.annotation.JsonView;
3633
import com.fasterxml.jackson.databind.JavaType;
@@ -46,31 +43,31 @@
4643
import io.swagger.v3.oas.models.media.ObjectSchema;
4744
import io.swagger.v3.oas.models.media.Schema;
4845
import io.swagger.v3.oas.models.parameters.Parameter;
49-
import org.apache.commons.lang3.ClassUtils;
5046
import org.apache.commons.lang3.StringUtils;
51-
import org.apache.commons.lang3.reflect.MethodUtils;
5247

5348
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
54-
import org.springframework.core.annotation.AnnotationUtils;
49+
import org.springframework.core.MethodParameter;
5550
import org.springframework.http.codec.multipart.FilePart;
56-
import org.springframework.web.method.HandlerMethod;
5751
import org.springframework.web.multipart.MultipartFile;
5852

5953
@SuppressWarnings("rawtypes")
6054
public class GenericParameterBuilder {
6155

6256
private final LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer;
57+
6358
private final IgnoredParameterAnnotations ignoredParameterAnnotations;
59+
6460
private final PropertyResolverUtils propertyResolverUtils;
61+
6562
private static final List<Class<?>> FILE_TYPES = new ArrayList<>();
6663

6764
static {
6865
FILE_TYPES.add(MultipartFile.class);
6966
}
7067

7168
public GenericParameterBuilder(LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer,
72-
IgnoredParameterAnnotations ignoredParameterAnnotations,
73-
PropertyResolverUtils propertyResolverUtils) {
69+
IgnoredParameterAnnotations ignoredParameterAnnotations,
70+
PropertyResolverUtils propertyResolverUtils) {
7471
this.localSpringDocParameterNameDiscoverer = localSpringDocParameterNameDiscoverer;
7572
this.ignoredParameterAnnotations = ignoredParameterAnnotations;
7673
this.propertyResolverUtils = propertyResolverUtils;
@@ -200,31 +197,25 @@ else if (AnnotationsUtils.hasArrayAnnotation(parameterDoc.content()[0].array()))
200197
Schema calculateSchema(Components components, ParameterInfo parameterInfo,
201198
RequestBodyInfo requestBodyInfo, JsonView jsonView) {
202199
Schema schemaN;
203-
Class<?> schemaImplementation = null;
204-
Type returnType = null;
205-
JavaType ct = null;
206200
String paramName = parameterInfo.getpName();
207-
java.lang.reflect.Parameter parameter = parameterInfo.getParameter();
208-
if (parameter != null) {
209-
returnType = parameter.getParameterizedType();
210-
ct = constructType(parameter.getType());
211-
schemaImplementation = parameter.getType();
212-
}
201+
MethodParameter methodParameter = parameterInfo.getMethodParameter();
202+
JavaType ct = constructType(methodParameter.getParameterType());
213203

214204
if (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getSchema() == null) {
215205
if (isFile(ct)) {
216206
schemaN = getFileSchema(requestBodyInfo);
217207
schemaN.addProperties(paramName, new FileSchema());
218208
return schemaN;
219-
} else if (returnType instanceof ParameterizedType) {
220-
ParameterizedType parameterizedType = (ParameterizedType) returnType;
209+
}
210+
else if (methodParameter.getGenericParameterType() instanceof ParameterizedType) {
211+
ParameterizedType parameterizedType = (ParameterizedType) methodParameter.getGenericParameterType();
221212
if (isFile(parameterizedType)) {
222213
return extractFileSchema(paramName, requestBodyInfo);
223214
}
224-
schemaN = calculateSchemaFromParameterizedType(components, returnType, jsonView);
215+
schemaN = calculateSchemaFromParameterizedType(components, methodParameter.getGenericParameterType(), jsonView);
225216
}
226217
else {
227-
schemaN = SpringDocAnnotationsUtils.resolveSchemaFromType(schemaImplementation, components, jsonView);
218+
schemaN = SpringDocAnnotationsUtils.resolveSchemaFromType(methodParameter.getParameterType(), components, jsonView);
228219
}
229220
}
230221
else {
@@ -304,21 +295,6 @@ else if (type instanceof ParameterizedType) {
304295
return result;
305296
}
306297

307-
<A extends Annotation> A getParameterAnnotation(HandlerMethod handlerMethod,
308-
java.lang.reflect.Parameter parameter, int i, Class<A> annotationType) {
309-
A parameterDoc = AnnotationUtils.getAnnotation(parameter, annotationType);
310-
if (parameterDoc == null) {
311-
Set<Method> methods = MethodUtils.getOverrideHierarchy(handlerMethod.getMethod(),
312-
ClassUtils.Interfaces.INCLUDE);
313-
for (Method methodOverriden : methods) {
314-
parameterDoc = AnnotationUtils.getAnnotation(methodOverriden.getParameters()[i], annotationType);
315-
if (parameterDoc != null)
316-
break;
317-
}
318-
}
319-
return parameterDoc;
320-
}
321-
322298
private void setExamples(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) {
323299
Map<String, Example> exampleMap = new HashMap<>();
324300
if (parameterDoc.examples().length == 1 && StringUtils.isBlank(parameterDoc.examples()[0].name())) {

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

+17-16
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.apache.commons.lang3.ArrayUtils;
4444
import org.apache.commons.lang3.StringUtils;
4545

46+
import org.springframework.core.MethodParameter;
4647
import org.springframework.core.annotation.AnnotatedElementUtils;
4748
import org.springframework.http.HttpStatus;
4849
import org.springframework.util.CollectionUtils;
@@ -82,7 +83,7 @@ public ApiResponses build(Components components, HandlerMethod handlerMethod, Op
8283
apiResponsesFromDoc.forEach(apiResponses::addApiResponse);
8384
// for each one build ApiResponse and add it to existing responses
8485
// Fill api Responses
85-
computeResponse(components, handlerMethod.getMethod(), handlerMethod.getReturnType().getParameterType(), apiResponses, methodAttributes, false);
86+
computeResponse(components, handlerMethod.getReturnType(), apiResponses, methodAttributes, false);
8687
return apiResponses;
8788
}
8889

@@ -97,7 +98,7 @@ public void buildGenericResponse(Components components, Map<String, Object> find
9798
if (reqMappringMethod != null) {
9899
methodProduces = reqMappringMethod.produces();
99100
}
100-
Map<String, ApiResponse> apiResponses = computeResponse(components, method,null, new ApiResponses(),
101+
Map<String, ApiResponse> apiResponses = computeResponse(components, new MethodParameter(method,-1), new ApiResponses(),
101102
new MethodAttributes(methodProduces, springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType()), true);
102103
apiResponses.forEach(genericMapResponse::put);
103104
}
@@ -118,10 +119,10 @@ private List<Method> getMethods(Map<String, Object> findControllerAdvice) {
118119
return methods;
119120
}
120121

121-
private Map<String, ApiResponse> computeResponse(Components components, Method method, Class<?> clazz, ApiResponses apiResponsesOp,
122+
private Map<String, ApiResponse> computeResponse(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp,
122123
MethodAttributes methodAttributes, boolean isGeneric) {
123124
// Parsing documentation, if present
124-
Set<io.swagger.v3.oas.annotations.responses.ApiResponse> responsesArray = getApiResponses(method);
125+
Set<io.swagger.v3.oas.annotations.responses.ApiResponse> responsesArray = getApiResponses(methodParameter.getMethod());
125126
if (!responsesArray.isEmpty()) {
126127
methodAttributes.setWithApiResponseDoc(true);
127128
if (!springDocConfigProperties.isOverrideWithGenericResponse())
@@ -146,7 +147,7 @@ private Map<String, ApiResponse> computeResponse(Components components, Method m
146147
apiResponsesOp.addApiResponse(apiResponseAnnotations.responseCode(), apiResponse);
147148
}
148149
}
149-
buildApiResponses(components, method, clazz, apiResponsesOp, methodAttributes, isGeneric);
150+
buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, isGeneric);
150151
return apiResponsesOp;
151152
}
152153

@@ -174,25 +175,25 @@ private void buildContentFromDoc(Components components, ApiResponses apiResponse
174175
}
175176
}
176177

177-
private void buildApiResponses(Components components, Method method, Class<?> clazz, ApiResponses apiResponsesOp,
178+
private void buildApiResponses(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp,
178179
MethodAttributes methodAttributes, boolean isGeneric) {
179180
if (!CollectionUtils.isEmpty(apiResponsesOp) && (apiResponsesOp.size() != genericMapResponse.size() || isGeneric)) {
180181
// API Responses at operation and @ApiResponse annotation
181182
for (Map.Entry<String, ApiResponse> entry : apiResponsesOp.entrySet()) {
182183
String httpCode = entry.getKey();
183184
ApiResponse apiResponse = entry.getValue();
184-
buildApiResponses(components, method, clazz, apiResponsesOp, methodAttributes, httpCode, apiResponse,
185+
buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse,
185186
isGeneric);
186187
}
187188
}
188189
else {
189190
// Use response parameters with no description filled - No documentation
190191
// available
191-
String httpCode = evaluateResponseStatus(method, method.getClass(), isGeneric);
192+
String httpCode = evaluateResponseStatus(methodParameter.getMethod(), methodParameter.getMethod().getClass(), isGeneric);
192193
ApiResponse apiResponse = genericMapResponse.containsKey(httpCode) ? genericMapResponse.get(httpCode)
193194
: new ApiResponse();
194195
if (httpCode != null)
195-
buildApiResponses(components, method, clazz, apiResponsesOp, methodAttributes, httpCode, apiResponse,
196+
buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse,
196197
isGeneric);
197198
}
198199
}
@@ -222,9 +223,9 @@ private Set<io.swagger.v3.oas.annotations.responses.ApiResponse> getApiResponses
222223
return responses;
223224
}
224225

225-
private Content buildContent(Components components, Method method, Class<?> clazz, String[] methodProduces, JsonView jsonView) {
226+
private Content buildContent(Components components, MethodParameter methodParameter, String[] methodProduces, JsonView jsonView) {
226227
Content content = new Content();
227-
Type returnType = getReturnType(method,clazz);
228+
Type returnType = getReturnType(methodParameter);
228229
if (isVoid(returnType)) {
229230
// if void, no content
230231
content = null;
@@ -241,11 +242,11 @@ else if (ArrayUtils.isNotEmpty(methodProduces)) {
241242
return content;
242243
}
243244

244-
private Type getReturnType(Method method,Class<?> clazz) {
245+
private Type getReturnType(MethodParameter methodParameter) {
245246
Type returnType = Object.class;
246247
for (ReturnTypeParser returnTypeParser : returnTypeParsers) {
247248
if (returnType.getTypeName().equals(Object.class.getTypeName())) {
248-
returnType = returnTypeParser.getReturnType(method,clazz);
249+
returnType = returnTypeParser.getReturnType(methodParameter);
249250
}
250251
else {
251252
break;
@@ -272,12 +273,12 @@ private void setContent(String[] methodProduces, Content content,
272273
Arrays.stream(methodProduces).forEach(mediaTypeStr -> content.addMediaType(mediaTypeStr, mediaType));
273274
}
274275

275-
private void buildApiResponses(Components components, Method method, Class<?> clazz, ApiResponses apiResponsesOp,
276+
private void buildApiResponses(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp,
276277
MethodAttributes methodAttributes, String httpCode, ApiResponse apiResponse, boolean isGeneric) {
277278
// No documentation
278279
if (StringUtils.isBlank(apiResponse.get$ref())) {
279280
if (apiResponse.getContent() == null) {
280-
Content content = buildContent(components, method,clazz, methodAttributes.getMethodProduces(),
281+
Content content = buildContent(components, methodParameter, methodAttributes.getMethodProduces(),
281282
methodAttributes.getJsonViewAnnotation());
282283
apiResponse.setContent(content);
283284
}
@@ -292,7 +293,7 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent())) {
292293
&& ((isGeneric || methodAttributes.isMethodOverloaded()) && methodAttributes.isNoApiResponseDoc())) {
293294
// Merge with existing schema
294295
Content existingContent = apiResponse.getContent();
295-
Schema<?> schemaN = calculateSchema(components, method.getGenericReturnType(),
296+
Schema<?> schemaN = calculateSchema(components, methodParameter.getGenericParameterType(),
296297
methodAttributes.getJsonViewAnnotation());
297298
if (schemaN != null && ArrayUtils.isNotEmpty(methodAttributes.getMethodProduces())) {
298299
Arrays.stream(methodAttributes.getMethodProduces()).forEach(mediaTypeStr -> mergeSchema(existingContent, schemaN, mediaTypeStr));

Diff for: springdoc-openapi-common/src/main/java/org/springdoc/core/ParameterInfo.java

+11-5
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,23 @@
2020

2121
import java.lang.reflect.Parameter;
2222

23+
import org.springframework.core.MethodParameter;
24+
2325
class ParameterInfo {
2426

25-
private final java.lang.reflect.Parameter parameter;
27+
private final MethodParameter methodParameter;
2628

2729
private final int index;
2830

2931
private String pName;
3032

3133
private io.swagger.v3.oas.models.parameters.Parameter parameterModel;
3234

33-
public ParameterInfo(String pName, Parameter parameter,
35+
public ParameterInfo(String pName, MethodParameter methodParameter,
3436
io.swagger.v3.oas.models.parameters.Parameter parameterModel, int index) {
3537
super();
3638
this.pName = pName;
37-
this.parameter = parameter;
39+
this.methodParameter = methodParameter;
3840
this.parameterModel = parameterModel;
3941
this.index = index;
4042
}
@@ -47,8 +49,12 @@ public void setpName(String pName) {
4749
this.pName = pName;
4850
}
4951

50-
public java.lang.reflect.Parameter getParameter() {
51-
return parameter;
52+
public MethodParameter getMethodParameter() {
53+
return methodParameter;
54+
}
55+
56+
public Parameter getParameter(){
57+
return methodParameter.getParameter();
5258
}
5359

5460
public io.swagger.v3.oas.models.parameters.Parameter getParameterModel() {

0 commit comments

Comments
 (0)