Skip to content

Commit 42f2c84

Browse files
committed
Add support for OAS v3.1 webhooks. Fixes #2657
1 parent cdbd7f6 commit 42f2c84

File tree

5 files changed

+280
-56
lines changed

5 files changed

+280
-56
lines changed

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

+84-54
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
import io.swagger.v3.core.filter.SpecFilter;
6161
import io.swagger.v3.core.util.ReflectionUtils;
6262
import io.swagger.v3.oas.annotations.Hidden;
63+
import io.swagger.v3.oas.annotations.Webhook;
64+
import io.swagger.v3.oas.annotations.Webhooks;
6365
import io.swagger.v3.oas.annotations.callbacks.Callback;
6466
import io.swagger.v3.oas.annotations.enums.ParameterIn;
6567
import io.swagger.v3.oas.models.Components;
@@ -213,14 +215,14 @@ public abstract class AbstractOpenApiResource extends SpecFilter {
213215
/**
214216
* Instantiates a new Abstract open api resource.
215217
*
216-
* @param groupName the group name
218+
* @param groupName the group name
217219
* @param openAPIBuilderObjectFactory the open api builder object factory
218-
* @param requestBuilder the request builder
219-
* @param responseBuilder the response builder
220-
* @param operationParser the operation parser
221-
* @param springDocConfigProperties the spring doc config properties
222-
* @param springDocProviders the spring doc providers
223-
* @param springDocCustomizers the spring doc customizers
220+
* @param requestBuilder the request builder
221+
* @param responseBuilder the response builder
222+
* @param operationParser the operation parser
223+
* @param springDocConfigProperties the spring doc config properties
224+
* @param springDocProviders the spring doc providers
225+
* @param springDocCustomizers the spring doc customizers
224226
*/
225227
protected AbstractOpenApiResource(String groupName, ObjectFactory<OpenAPIService> openAPIBuilderObjectFactory,
226228
AbstractRequestService requestBuilder,
@@ -239,7 +241,8 @@ protected AbstractOpenApiResource(String groupName, ObjectFactory<OpenAPIService
239241
if (springDocConfigProperties.isPreLoadingEnabled()) {
240242
if (CollectionUtils.isEmpty(springDocConfigProperties.getPreLoadingLocales())) {
241243
Executors.newSingleThreadExecutor().execute(this::getOpenApi);
242-
} else {
244+
}
245+
else {
243246
for (String locale : springDocConfigProperties.getPreLoadingLocales()) {
244247
Executors.newSingleThreadExecutor().execute(() -> this.getOpenApi(Locale.forLanguageTag(locale)));
245248
}
@@ -343,9 +346,10 @@ protected OpenAPI getOpenApi(Locale locale) {
343346
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
344347

345348
Map<String, Object> findControllerAdvice = openAPIService.getControllerAdviceMap();
346-
if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion()){
349+
if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion()) {
347350
openAPI.openapi(OpenApiVersion.OPENAPI_3_1.getVersion());
348351
openAPI.specVersion(SpecVersion.V31);
352+
calculateWebhooks(openAPI, locale);
349353
}
350354
if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) {
351355
if (!CollectionUtils.isEmpty(mappingsMap))
@@ -373,7 +377,7 @@ protected OpenAPI getOpenApi(Locale locale) {
373377
if (springDocConfigProperties.isRemoveBrokenReferenceDefinitions())
374378
this.removeBrokenReferenceDefinitions(openAPI);
375379

376-
// run the optional customisers
380+
// run the optional customizers
377381
List<Server> servers = openAPI.getServers();
378382
List<Server> serversCopy = null;
379383
try {
@@ -401,13 +405,15 @@ protected OpenAPI getOpenApi(Locale locale) {
401405
}
402406
openAPIService.updateServers(openAPI);
403407
return openAPI;
404-
} finally {
408+
}
409+
finally {
405410
this.reentrantLock.unlock();
406411
}
407412
}
408413

409414
/**
410415
* Indents are removed for properties that are mainly used as “explanations” using Open API.
416+
*
411417
* @param openAPI the open api
412418
*/
413419
private void trimIndent(OpenAPI openAPI) {
@@ -417,6 +423,7 @@ private void trimIndent(OpenAPI openAPI) {
417423

418424
/**
419425
* Trim the indent for descriptions in the 'components' of open api.
426+
*
420427
* @param openAPI the open api
421428
*/
422429
private void trimComponents(OpenAPI openAPI) {
@@ -439,6 +446,7 @@ private void trimComponents(OpenAPI openAPI) {
439446

440447
/**
441448
* Trim the indent for descriptions in the 'paths' of open api.
449+
*
442450
* @param openAPI the open api
443451
*/
444452
private void trimPaths(OpenAPI openAPI) {
@@ -461,6 +469,7 @@ private void trimPaths(OpenAPI openAPI) {
461469

462470
/**
463471
* Trim the indent for 'operation'
472+
*
464473
* @param operation the operation
465474
*/
466475
private void trimIndentOperation(Operation operation) {
@@ -476,18 +485,39 @@ private void trimIndentOperation(Operation operation) {
476485
* Gets paths.
477486
*
478487
* @param findRestControllers the find rest controllers
479-
* @param locale the locale
480-
* @param openAPI the open api
488+
* @param locale the locale
489+
* @param openAPI the open api
481490
*/
482491
protected abstract void getPaths(Map<String, Object> findRestControllers, Locale locale, OpenAPI openAPI);
483492

493+
494+
/**
495+
* Calculate webhooks.
496+
*
497+
* @param calculatedOpenAPI the calculated open api
498+
* @param locale the locale
499+
*/
500+
protected void calculateWebhooks(OpenAPI calculatedOpenAPI, Locale locale) {
501+
Webhooks[] webhooksAttr = openAPIService.getWebhooks();
502+
var webhooks = Arrays.stream(webhooksAttr).map(Webhooks::value).flatMap(Arrays::stream).toArray(Webhook[]::new);
503+
Arrays.stream(webhooks).forEach(webhook -> {
504+
io.swagger.v3.oas.annotations.Operation apiOperation = webhook.operation();
505+
Operation operation = new Operation();
506+
MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(),
507+
springDocConfigProperties.getDefaultProducesMediaType(), locale);
508+
operationParser.parse(apiOperation, operation, calculatedOpenAPI, methodAttributes);
509+
PathItem pathItem = new PathItem().post(operation);
510+
calculatedOpenAPI.addWebhooks(webhook.name(), pathItem);
511+
});
512+
}
513+
484514
/**
485515
* Calculate path.
486516
*
487-
* @param handlerMethod the handler method
517+
* @param handlerMethod the handler method
488518
* @param routerOperation the router operation
489-
* @param locale the locale
490-
* @param openAPI the open api
519+
* @param locale the locale
520+
* @param openAPI the open api
491521
*/
492522
protected void calculatePath(HandlerMethod handlerMethod, RouterOperation routerOperation, Locale locale, OpenAPI openAPI) {
493523
routerOperation = customizeRouterOperation(routerOperation, handlerMethod);
@@ -602,10 +632,10 @@ protected void calculatePath(HandlerMethod handlerMethod, RouterOperation router
602632
/**
603633
* Build callbacks.
604634
*
605-
* @param openAPI the open api
635+
* @param openAPI the open api
606636
* @param methodAttributes the method attributes
607-
* @param operation the operation
608-
* @param apiCallbacks the api callbacks
637+
* @param operation the operation
638+
* @param apiCallbacks the api callbacks
609639
*/
610640
private void buildCallbacks(OpenAPI openAPI, MethodAttributes methodAttributes, Operation operation, Set<Callback> apiCallbacks) {
611641
if (!CollectionUtils.isEmpty(apiCallbacks))
@@ -617,8 +647,8 @@ private void buildCallbacks(OpenAPI openAPI, MethodAttributes methodAttributes,
617647
* Calculate path.
618648
*
619649
* @param routerOperationList the router operation list
620-
* @param locale the locale
621-
* @param openAPI the open api
650+
* @param locale the locale
651+
* @param openAPI the open api
622652
*/
623653
protected void calculatePath(List<RouterOperation> routerOperationList, Locale locale, OpenAPI openAPI) {
624654
ApplicationContext applicationContext = openAPIService.getContext();
@@ -667,7 +697,7 @@ else if (routerOperation.getOperationModel() != null && StringUtils.isNotBlank(r
667697
* Calculate path.
668698
*
669699
* @param routerOperation the router operation
670-
* @param locale the locale
700+
* @param locale the locale
671701
*/
672702
protected void calculatePath(RouterOperation routerOperation, Locale locale, OpenAPI openAPI) {
673703
routerOperation = customizeDataRestRouterOperation(routerOperation);
@@ -733,13 +763,13 @@ private RouterOperation customizeDataRestRouterOperation(RouterOperation routerO
733763
/**
734764
* Calculate path.
735765
*
736-
* @param handlerMethod the handler method
737-
* @param operationPath the operation path
766+
* @param handlerMethod the handler method
767+
* @param operationPath the operation path
738768
* @param requestMethods the request methods
739-
* @param consumes the consumes
740-
* @param produces the produces
741-
* @param headers the headers
742-
* @param locale the locale
769+
* @param consumes the consumes
770+
* @param produces the produces
771+
* @param headers the headers
772+
* @param locale the locale
743773
*/
744774
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
745775
Set<RequestMethod> requestMethods, String[] consumes, String[] produces, String[] headers, String[] params, Locale locale, OpenAPI openAPI) {
@@ -749,10 +779,10 @@ protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
749779
/**
750780
* Gets router function paths.
751781
*
752-
* @param beanName the bean name
782+
* @param beanName the bean name
753783
* @param routerFunctionVisitor the router function visitor
754-
* @param locale the locale
755-
* @param openAPI the open api
784+
* @param locale the locale
785+
* @param openAPI the open api
756786
*/
757787
protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVisitor routerFunctionVisitor,
758788
Locale locale, OpenAPI openAPI) {
@@ -788,9 +818,9 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis
788818
*
789819
* @param handlerMethod the handler method
790820
* @param operationPath the operation path
791-
* @param produces the produces
792-
* @param consumes the consumes
793-
* @param headers the headers
821+
* @param produces the produces
822+
* @param consumes the consumes
823+
* @param headers the headers
794824
* @return the boolean
795825
*/
796826
protected boolean isFilterCondition(HandlerMethod handlerMethod, String operationPath, String[] produces, String[] consumes, String[] headers) {
@@ -816,7 +846,7 @@ protected boolean isMethodToFilter(HandlerMethod handlerMethod) {
816846
* Is condition to match boolean.
817847
*
818848
* @param existingConditions the existing conditions
819-
* @param conditionType the condition type
849+
* @param conditionType the condition type
820850
* @return the boolean
821851
*/
822852
protected boolean isConditionToMatch(String[] existingConditions, ConditionType conditionType) {
@@ -915,8 +945,8 @@ protected boolean isAdditionalRestController(Class<?> rawClass) {
915945
* Is rest controller boolean.
916946
*
917947
* @param restControllers the rest controllers
918-
* @param handlerMethod the handler method
919-
* @param operationPath the operation path
948+
* @param handlerMethod the handler method
949+
* @param operationPath the operation path
920950
* @return the boolean
921951
*/
922952
protected boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod,
@@ -941,7 +971,7 @@ protected Set<RequestMethod> getDefaultAllowedHttpMethods() {
941971
/**
942972
* Customise operation.
943973
*
944-
* @param operation the operation
974+
* @param operation the operation
945975
* @param handlerMethod the handler method
946976
* @return the operation
947977
*/
@@ -959,7 +989,7 @@ protected Operation customizeOperation(Operation operation, HandlerMethod handle
959989
* Customise router operation
960990
*
961991
* @param routerOperation the router operation
962-
* @param handlerMethod the handler method
992+
* @param handlerMethod the handler method
963993
* @return the router operation
964994
*/
965995
protected RouterOperation customizeRouterOperation(RouterOperation routerOperation, HandlerMethod handlerMethod) {
@@ -1064,9 +1094,9 @@ && isEqualArrays(routerFunctionData1.getConsumes(), routerOperation.getConsumes(
10641094
/**
10651095
* Calculate json view.
10661096
*
1067-
* @param apiOperation the api operation
1097+
* @param apiOperation the api operation
10681098
* @param methodAttributes the method attributes
1069-
* @param method the method
1099+
* @param method the method
10701100
*/
10711101
private void calculateJsonView(io.swagger.v3.oas.annotations.Operation apiOperation,
10721102
MethodAttributes methodAttributes, Method method) {
@@ -1123,8 +1153,8 @@ private boolean isEqualMethods(RequestMethod[] requestMethods1, RequestMethod[]
11231153
/**
11241154
* Fill parameters list.
11251155
*
1126-
* @param operation the operation
1127-
* @param queryParams the query params
1156+
* @param operation the operation
1157+
* @param queryParams the query params
11281158
* @param methodAttributes the method attributes
11291159
*/
11301160
private void fillParametersList(Operation operation, Map<String, String> queryParams, MethodAttributes methodAttributes) {
@@ -1157,7 +1187,7 @@ private void fillParametersList(Operation operation, Map<String, String> queryPa
11571187
* Fill router operation.
11581188
*
11591189
* @param routerFunctionData the router function data
1160-
* @param routerOperation the router operation
1190+
* @param routerOperation the router operation
11611191
*/
11621192
private void fillRouterOperation(RouterFunctionData routerFunctionData, RouterOperation routerOperation) {
11631193
if (ArrayUtils.isEmpty(routerOperation.getConsumes()))
@@ -1176,9 +1206,9 @@ private void fillRouterOperation(RouterFunctionData routerFunctionData, RouterOp
11761206
* Build path item.
11771207
*
11781208
* @param requestMethod the request method
1179-
* @param operation the operation
1209+
* @param operation the operation
11801210
* @param operationPath the operation path
1181-
* @param paths the paths
1211+
* @param paths the paths
11821212
* @return the path item
11831213
*/
11841214
private PathItem buildPathItem(RequestMethod requestMethod, Operation operation, String operationPath,
@@ -1191,7 +1221,7 @@ private PathItem buildPathItem(RequestMethod requestMethod, Operation operation,
11911221
if (ParameterIn.PATH.toString().equals(parameter.getIn())) {
11921222
// check it's present in the path
11931223
String name = parameter.getName();
1194-
if(!StringUtils.containsAny(operationPath, "{" + name + "}", "{*" + name + "}"))
1224+
if (!StringUtils.containsAny(operationPath, "{" + name + "}", "{*" + name + "}"))
11951225
paramIt.remove();
11961226
}
11971227
}
@@ -1236,7 +1266,7 @@ private PathItem buildPathItem(RequestMethod requestMethod, Operation operation,
12361266
/**
12371267
* Gets existing operation.
12381268
*
1239-
* @param operationMap the operation map
1269+
* @param operationMap the operation map
12401270
* @param requestMethod the request method
12411271
* @return the existing operation
12421272
*/
@@ -1277,7 +1307,7 @@ private Operation getExistingOperation(Map<HttpMethod, Operation> operationMap,
12771307
/**
12781308
* Gets operation.
12791309
*
1280-
* @param routerOperation the router operation
1310+
* @param routerOperation the router operation
12811311
* @param existingOperation the existing operation
12821312
* @return the operation
12831313
*/
@@ -1336,7 +1366,7 @@ protected byte[] writeYamlValue(OpenAPI openAPI) throws JsonProcessingException
13361366
* Gets actuator uri.
13371367
*
13381368
* @param scheme the scheme
1339-
* @param host the host
1369+
* @param host the host
13401370
* @return the actuator uri
13411371
*/
13421372
protected URI getActuatorURI(String scheme, String host) {
@@ -1405,7 +1435,7 @@ protected byte[] writeJsonValue(OpenAPI openAPI) throws JsonProcessingException
14051435
* Gets conditions to match.
14061436
*
14071437
* @param conditionType the condition type
1408-
* @param groupConfigs the group configs
1438+
* @param groupConfigs the group configs
14091439
* @return the conditions to match
14101440
*/
14111441
private List<String> getConditionsToMatch(ConditionType conditionType, GroupConfig... groupConfigs) {
@@ -1433,9 +1463,9 @@ private List<String> getConditionsToMatch(ConditionType conditionType, GroupConf
14331463
* Is filter condition boolean.
14341464
*
14351465
* @param operationPath the operation path
1436-
* @param produces the produces
1437-
* @param consumes the consumes
1438-
* @param headers the headers
1466+
* @param produces the produces
1467+
* @param consumes the consumes
1468+
* @param headers the headers
14391469
* @return the boolean
14401470
*/
14411471
private boolean isFilterCondition(String operationPath, String[] produces, String[] consumes, String[] headers) {

0 commit comments

Comments
 (0)