Skip to content

Commit c07093c

Browse files
committedMar 20, 2025
ModelResolver.enumAsRef = true result in invalid openapi with actuator using enum param #2905
1 parent 4412fd0 commit c07093c

File tree

5 files changed

+1034
-21
lines changed

5 files changed

+1034
-21
lines changed
 

‎springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import org.slf4j.LoggerFactory;
8787
import org.springdoc.core.annotations.RouterOperations;
8888
import org.springdoc.core.customizers.DataRestRouterOperationCustomizer;
89+
import org.springdoc.core.customizers.GlobalOperationComponentsCustomizer;
8990
import org.springdoc.core.customizers.OpenApiLocaleCustomizer;
9091
import org.springdoc.core.customizers.OperationCustomizer;
9192
import org.springdoc.core.customizers.RouterOperationCustomizer;
@@ -649,7 +650,7 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router
649650
buildCallbacks(openAPI, methodAttributes, operation, apiCallbacks);
650651

651652
// allow for customisation
652-
operation = customizeOperation(operation, handlerMethod);
653+
operation = customizeOperation(operation, components, handlerMethod);
653654

654655
if (StringUtils.contains(operationPath, "*")) {
655656
Matcher matcher = pathPattern.matcher(operationPath);
@@ -1011,15 +1012,20 @@ protected Set<RequestMethod> getDefaultAllowedHttpMethods() {
10111012
* Customise operation.
10121013
*
10131014
* @param operation the operation
1015+
* @param components
10141016
* @param handlerMethod the handler method
10151017
* @return the operation
10161018
*/
1017-
protected Operation customizeOperation(Operation operation, HandlerMethod handlerMethod) {
1019+
protected Operation customizeOperation(Operation operation, Components components, HandlerMethod handlerMethod) {
10181020
Optional<Set<OperationCustomizer>> optionalOperationCustomizers = springDocCustomizers.getOperationCustomizers();
10191021
if (optionalOperationCustomizers.isPresent()) {
10201022
Set<OperationCustomizer> operationCustomizerList = optionalOperationCustomizers.get();
1021-
for (OperationCustomizer operationCustomizer : operationCustomizerList)
1022-
operation = operationCustomizer.customize(operation, handlerMethod);
1023+
for (OperationCustomizer operationCustomizer : operationCustomizerList) {
1024+
if (operationCustomizer instanceof GlobalOperationComponentsCustomizer globalOperationComponentsCustomizer)
1025+
operation = globalOperationComponentsCustomizer.customize(operation, components, handlerMethod);
1026+
else
1027+
operation = operationCustomizer.customize(operation, handlerMethod);
1028+
}
10231029
}
10241030
return operation;
10251031
}

‎springdoc-openapi-starter-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java

+27-17
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* * * *
2222
* * *
2323
* *
24-
*
24+
*
2525
*/
2626

2727
package org.springdoc.core.customizers;
@@ -31,6 +31,7 @@
3131

3232
import io.swagger.v3.core.util.AnnotationsUtils;
3333
import io.swagger.v3.oas.annotations.enums.ParameterIn;
34+
import io.swagger.v3.oas.models.Components;
3435
import io.swagger.v3.oas.models.Operation;
3536
import io.swagger.v3.oas.models.media.Content;
3637
import io.swagger.v3.oas.models.media.MediaType;
@@ -61,7 +62,7 @@
6162
*
6263
* @author bnasslahsen
6364
*/
64-
public class ActuatorOperationCustomizer implements GlobalOperationCustomizer {
65+
public class ActuatorOperationCustomizer implements GlobalOperationComponentsCustomizer {
6566

6667
/**
6768
* The constant OPERATION.
@@ -94,11 +95,11 @@ public ActuatorOperationCustomizer(SpringDocConfigProperties springDocConfigProp
9495
}
9596

9697
@Override
97-
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
98+
public Operation customize(Operation operation, Components components, HandlerMethod handlerMethod) {
9899
if (operationHasValidTag(operation)) {
99100
Field operationField = FieldUtils.getDeclaredField(handlerMethod.getBean().getClass(), OPERATION,true);
100101
if (operationField != null) {
101-
processOperationField(handlerMethod, operation, operationField);
102+
processOperationField(handlerMethod, operation, components, operationField);
102103
}
103104
setOperationSummary(operation, handlerMethod);
104105
}
@@ -120,16 +121,17 @@ private boolean operationHasValidTag(Operation operation) {
120121
*
121122
* @param handlerMethod the handler method
122123
* @param operation the operation
124+
* @param components the components
123125
* @param operationField the operation field
124126
*/
125-
private void processOperationField(HandlerMethod handlerMethod, Operation operation, Field operationField) {
127+
private void processOperationField(HandlerMethod handlerMethod, Operation operation, Components components, Field operationField) {
126128
try {
127129
Object actuatorOperation = operationField.get(handlerMethod.getBean());
128130
Field actuatorOperationField = FieldUtils.getDeclaredField(actuatorOperation.getClass(), OPERATION, true);
129131
if (actuatorOperationField != null) {
130132
AbstractDiscoveredOperation discoveredOperation =
131133
(AbstractDiscoveredOperation) actuatorOperationField.get(actuatorOperation);
132-
handleOperationMethod(discoveredOperation.getOperationMethod(), operation);
134+
handleOperationMethod(discoveredOperation.getOperationMethod(), components, operation);
133135
}
134136
}
135137
catch (IllegalAccessException e) {
@@ -141,25 +143,26 @@ private void processOperationField(HandlerMethod handlerMethod, Operation operat
141143
* Handle operation method.
142144
*
143145
* @param operationMethod the operation method
146+
* @param components the components
144147
* @param operation the operation
145148
*/
146-
private void handleOperationMethod(OperationMethod operationMethod, Operation operation) {
149+
private void handleOperationMethod(OperationMethod operationMethod, Components components, Operation operation) {
147150
String operationId = operationMethod.getMethod().getName();
148151
operation.setOperationId(operationId);
149152

150153
switch (operationMethod.getOperationType()) {
151154
case READ:
152-
addParameters(operationMethod, operation, ParameterIn.QUERY);
155+
addParameters(operationMethod, operation, components, ParameterIn.QUERY);
153156
break;
154157
case WRITE:
155-
addWriteParameters(operationMethod, operation);
158+
addWriteParameters(operationMethod,components, operation);
156159
operation.setResponses(new ApiResponses()
157160
.addApiResponse(String.valueOf(HttpStatus.NO_CONTENT.value()), new ApiResponse().description(HttpStatus.NO_CONTENT.getReasonPhrase()))
158161
.addApiResponse(String.valueOf(HttpStatus.BAD_REQUEST.value()), new ApiResponse().description(HttpStatus.BAD_REQUEST.getReasonPhrase())));
159162
break;
160163
case DELETE:
161164
operation.setResponses(new ApiResponses().addApiResponse(String.valueOf(HttpStatus.NO_CONTENT.value()), new ApiResponse().description(HttpStatus.NO_CONTENT.getReasonPhrase())));
162-
addParameters(operationMethod, operation, ParameterIn.QUERY);
165+
addParameters(operationMethod, operation, components, ParameterIn.QUERY);
163166
break;
164167
default:
165168
break;
@@ -171,13 +174,14 @@ private void handleOperationMethod(OperationMethod operationMethod, Operation op
171174
*
172175
* @param operationMethod the operation method
173176
* @param operation the operation
177+
* @param components the components
174178
* @param parameterIn the parameter in
175179
*/
176-
private void addParameters(OperationMethod operationMethod, Operation operation, ParameterIn parameterIn) {
180+
private void addParameters(OperationMethod operationMethod, Operation operation, Components components, ParameterIn parameterIn) {
177181
for (OperationParameter operationParameter : operationMethod.getParameters()) {
178182
Parameter parameter = getParameterFromField(operationParameter);
179183
if(parameter == null) continue;
180-
Schema<?> schema = resolveSchema(parameter);
184+
Schema<?> schema = resolveSchema(parameter, components);
181185
if (parameter.getAnnotation(Selector.class) != null) {
182186
operation.addParametersItem(new io.swagger.v3.oas.models.parameters.PathParameter()
183187
.name(parameter.getName())
@@ -197,13 +201,14 @@ else if (isValidParameterType(parameter)) {
197201
* Add write parameters.
198202
*
199203
* @param operationMethod the operation method
204+
* @param components the components
200205
* @param operation the operation
201206
*/
202-
private void addWriteParameters(OperationMethod operationMethod, Operation operation) {
207+
private void addWriteParameters(OperationMethod operationMethod, Components components, Operation operation) {
203208
for (OperationParameter operationParameter : operationMethod.getParameters()) {
204209
Parameter parameter = getParameterFromField(operationParameter);
205210
if(parameter == null) continue;
206-
Schema<?> schema = resolveSchema(parameter);
211+
Schema<?> schema = resolveSchema(parameter, components);
207212
if (parameter.getAnnotation(Selector.class) != null) {
208213
operation.addParametersItem(new io.swagger.v3.oas.models.parameters.PathParameter()
209214
.name(parameter.getName())
@@ -237,11 +242,12 @@ private Parameter getParameterFromField(OperationParameter operationParameter) {
237242
/**
238243
* Resolve schema schema.
239244
*
240-
* @param parameter the parameter
245+
* @param parameter the parameter
246+
* @param components
241247
* @return the schema
242248
*/
243-
private Schema<?> resolveSchema(Parameter parameter) {
244-
Schema schema = AnnotationsUtils.resolveSchemaFromType(parameter.getType(), null, null, springDocConfigProperties.isOpenapi31());
249+
private Schema<?> resolveSchema(Parameter parameter, Components components) {
250+
Schema schema = AnnotationsUtils.resolveSchemaFromType(parameter.getType(), components, null, springDocConfigProperties.isOpenapi31());
245251
if(springDocConfigProperties.isOpenapi31()) handleSchemaTypes(schema);
246252
return schema;
247253
}
@@ -271,4 +277,8 @@ private void setOperationSummary(Operation operation, HandlerMethod handlerMetho
271277
}
272278
}
273279

280+
@Override
281+
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
282+
return this.customize(operation, null, handlerMethod);
283+
}
274284
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.core.customizers;
28+
29+
import io.swagger.v3.oas.models.Components;
30+
import io.swagger.v3.oas.models.Operation;
31+
32+
import org.springframework.web.method.HandlerMethod;
33+
34+
/**
35+
* Implement and register a bean of type {@link GlobalOperationComponentsCustomizer} to
36+
* customize an operation based on the components and handler method input on default OpenAPI
37+
* description and groups
38+
*
39+
* @author christophejan
40+
* @see OperationCustomizer operations on default OpenAPI description but not groups
41+
*/
42+
public interface GlobalOperationComponentsCustomizer extends GlobalOperationCustomizer {
43+
44+
/**
45+
* Customize operation.
46+
*
47+
* @param operation input operation
48+
* @param handlerMethod original handler method
49+
* @return customized operation
50+
*/
51+
Operation customize(Operation operation, Components components, HandlerMethod handlerMethod);
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app187;
26+
27+
import org.junit.jupiter.api.AfterAll;
28+
import test.org.springdoc.api.v30.AbstractSpringDocTest;
29+
30+
import org.springframework.boot.autoconfigure.SpringBootApplication;
31+
import org.springframework.test.context.TestPropertySource;
32+
33+
@TestPropertySource(properties = { "springdoc.show-actuator=true",
34+
"management.endpoints.web.exposure.include=*",
35+
"management.endpoints.enabled-by-default=true",
36+
"management.endpoints.web.exposure.exclude=functions, shutdown" })
37+
public class SpringDocApp187Test extends AbstractSpringDocTest {
38+
39+
static {
40+
io.swagger.v3.core.jackson.ModelResolver.enumsAsRef = true;
41+
}
42+
43+
@AfterAll
44+
static void restore() {
45+
io.swagger.v3.core.jackson.ModelResolver.enumsAsRef = false;
46+
}
47+
48+
@SpringBootApplication
49+
static class SpringDocTestApp {}
50+
51+
}

0 commit comments

Comments
 (0)
Failed to load comments.