Skip to content

Commit d04cbe4

Browse files
author
bnasslahsen
committed
Improve support of Webflux with Functional Endpoints
1 parent 6f438d9 commit d04cbe4

File tree

16 files changed

+709
-15
lines changed

16 files changed

+709
-15
lines changed

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ protected synchronized OpenAPI getOpenApi() {
164164
protected abstract void getPaths(Map<String, Object> findRestControllers);
165165

166166
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
167-
Set<RequestMethod> requestMethods, String[] methodConsumes, String[] methodProduces, String[] headers) {
167+
Set<RequestMethod> requestMethods, io.swagger.v3.oas.annotations.Operation apiOperation, String[] methodConsumes, String[] methodProduces, String[] headers) {
168168
OpenAPI openAPI = openAPIBuilder.getCalculatedOpenAPI();
169169
Components components = openAPI.getComponents();
170170
Paths paths = openAPI.getPaths();
@@ -201,8 +201,9 @@ protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
201201
operation.setDeprecated(true);
202202

203203
// Add documentation from operation annotation
204-
io.swagger.v3.oas.annotations.Operation apiOperation = AnnotatedElementUtils.findMergedAnnotation(method,
205-
io.swagger.v3.oas.annotations.Operation.class);
204+
if (apiOperation == null || StringUtils.isBlank(apiOperation.operationId()))
205+
apiOperation = AnnotatedElementUtils.findMergedAnnotation(method,
206+
io.swagger.v3.oas.annotations.Operation.class);
206207

207208
calculateJsonView(apiOperation, methodAttributes, method);
208209

@@ -257,7 +258,7 @@ protected void calculatePath(String operationPath, Set<RequestMethod> requestMet
257258

258259
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
259260
Set<RequestMethod> requestMethods) {
260-
this.calculatePath(handlerMethod, operationPath, requestMethods, null, null, null);
261+
this.calculatePath(handlerMethod, operationPath, requestMethods, null, null, null, null);
261262
}
262263

263264
protected void calculatePath(List<RouterOperation> routerOperationList) {
@@ -286,8 +287,8 @@ protected void calculatePath(List<RouterOperation> routerOperationList) {
286287
catch (NoSuchMethodException e) {
287288
LOGGER.error(e.getMessage());
288289
}
289-
if (handlerMethod != null && isPackageToScan(handlerMethod.getBeanType().getPackage().getName()) && isPathToMatch(routerOperation.getPath()))
290-
calculatePath(handlerMethod, routerOperation.getPath(), new HashSet<>(Arrays.asList(routerOperation.getMethod())), routerOperation.getConsumes(), routerOperation.getProduces(), routerOperation.getHeaders());
290+
if (handlerMethod != null && isPackageToScan(handlerMethod.getBeanType().getPackage()) && isPathToMatch(routerOperation.getPath()))
291+
calculatePath(handlerMethod, routerOperation.getPath(), new HashSet<>(Arrays.asList(routerOperation.getMethod())), routerOperation.getOperation(), routerOperation.getConsumes(), routerOperation.getProduces(), routerOperation.getHeaders());
291292
}
292293
}
293294
else if (StringUtils.isNotBlank(routerOperation.getOperation().operationId()) && isPathToMatch(routerOperation.getPath())) {
@@ -303,7 +304,8 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis
303304
RouterOperations routerOperations = applicationContext.findAnnotationOnBean(beanName, RouterOperations.class);
304305
if (routerOperations == null) {
305306
org.springdoc.core.annotations.RouterOperation routerOperation = applicationContext.findAnnotationOnBean(beanName, org.springdoc.core.annotations.RouterOperation.class);
306-
routerOperationList.add(routerOperation);
307+
if (routerOperation != null)
308+
routerOperationList.add(routerOperation);
307309
}
308310
else
309311
routerOperationList.addAll(Arrays.asList(routerOperations.value()));
@@ -416,7 +418,10 @@ private PathItem buildPathItem(RequestMethod requestMethod, Operation operation,
416418
return pathItemObject;
417419
}
418420

419-
protected boolean isPackageToScan(String aPackage) {
421+
protected boolean isPackageToScan(Package aPackage) {
422+
if (aPackage == null)
423+
return true;
424+
final String packageName = aPackage.getName();
420425
List<String> packagesToScan = springDocConfigProperties.getPackagesToScan();
421426
List<String> packagesToExclude = springDocConfigProperties.getPackagesToExclude();
422427
if (CollectionUtils.isEmpty(packagesToScan)) {
@@ -430,11 +435,11 @@ protected boolean isPackageToScan(String aPackage) {
430435
packagesToExclude = optionalGroupConfig.get().getPackagesToExclude();
431436
}
432437
boolean include = CollectionUtils.isEmpty(packagesToScan)
433-
|| packagesToScan.stream().anyMatch(pack -> aPackage.equals(pack)
434-
|| aPackage.startsWith(pack + "."));
438+
|| packagesToScan.stream().anyMatch(pack -> packageName.equals(pack)
439+
|| packageName.startsWith(pack + "."));
435440
boolean exclude = !CollectionUtils.isEmpty(packagesToExclude)
436-
&& (packagesToExclude.stream().anyMatch(pack -> aPackage.equals(pack)
437-
|| aPackage.startsWith(pack + ".")));
441+
&& (packagesToExclude.stream().anyMatch(pack -> packageName.equals(pack)
442+
|| packageName.startsWith(pack + ".")));
438443

439444
return include && !exclude;
440445
}

springdoc-openapi-data-rest/pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
35
<parent>
46
<artifactId>springdoc-openapi</artifactId>
57
<groupId>org.springdoc</groupId>

springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ protected void getPaths(Map<String, Object> restControllers) {
119119
Map<String, String> regexMap = new LinkedHashMap<>();
120120
operationPath = PathUtils.parsePath(operationPath, regexMap);
121121
if (operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
122-
&& restControllers.containsKey(handlerMethod.getBean().toString()) && isPackageToScan(handlerMethod.getBeanType().getPackage().getName()) && isPathToMatch(operationPath)) {
122+
&& restControllers.containsKey(handlerMethod.getBean().toString()) && isPackageToScan(handlerMethod.getBeanType().getPackage()) && isPathToMatch(operationPath)) {
123123
Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
124124
// default allowed requestmethods
125125
if (requestMethods.isEmpty())
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
*
3+
* *
4+
* * * Copyright 2019-2020 the original author or authors.
5+
* * *
6+
* * * Licensed under the Apache License, Version 2.0 (the "License");
7+
* * * you may not use this file except in compliance with the License.
8+
* * * You may obtain a copy of the License at
9+
* * *
10+
* * * https://www.apache.org/licenses/LICENSE-2.0
11+
* * *
12+
* * * Unless required by applicable law or agreed to in writing, software
13+
* * * distributed under the License is distributed on an "AS IS" BASIS,
14+
* * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* * * See the License for the specific language governing permissions and
16+
* * * limitations under the License.
17+
* *
18+
*
19+
*/
20+
21+
package test.org.springdoc.api.app72;
22+
23+
import test.org.springdoc.api.AbstractSpringDocTest;
24+
25+
import org.springframework.boot.autoconfigure.SpringBootApplication;
26+
import org.springframework.context.annotation.ComponentScan;
27+
28+
public class SpringDocApp72Test extends AbstractSpringDocTest {
29+
30+
@SpringBootApplication
31+
@ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app72" })
32+
static class SpringDocTestApp {}
33+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package test.org.springdoc.api.app72.controller;
2+
3+
import org.springdoc.core.annotations.RouterOperation;
4+
import org.springdoc.core.annotations.RouterOperations;
5+
import test.org.springdoc.api.app72.handler.PersonHandler;
6+
import test.org.springdoc.api.app72.service.PersonService;
7+
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.web.reactive.function.server.RouterFunction;
12+
import org.springframework.web.reactive.function.server.RouterFunctions;
13+
import org.springframework.web.reactive.function.server.ServerResponse;
14+
15+
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
16+
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
17+
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
18+
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
19+
20+
@Configuration
21+
public class PersonRouter {
22+
23+
@RouterOperations({ @RouterOperation(path = "/getAllPersons", beanClass = PersonService.class, beanMethod = "getAll"),
24+
@RouterOperation(path = "/getPerson/{id}", beanClass = PersonService.class, beanMethod = "getById"),
25+
@RouterOperation(path = "/createPerson", beanClass = PersonService.class, beanMethod = "save"),
26+
@RouterOperation(path = "/deletePerson/{id}", beanClass = PersonService.class, beanMethod = "delete") })
27+
@Bean
28+
public RouterFunction<ServerResponse> personRoute(PersonHandler handler) {
29+
return RouterFunctions
30+
.route(GET("/getAllPersons").and(accept(MediaType.APPLICATION_JSON)), handler::findAll)
31+
.andRoute(GET("/getPerson/{id}").and(accept(MediaType.APPLICATION_STREAM_JSON)), handler::findById)
32+
.andRoute(POST("/createPerson").and(accept(MediaType.APPLICATION_JSON)), handler::save)
33+
.andRoute(DELETE("/deletePerson/{id}").and(accept(MediaType.APPLICATION_JSON)), handler::delete);
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package test.org.springdoc.api.app72.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.enums.ParameterIn;
6+
import io.swagger.v3.oas.annotations.media.ArraySchema;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.Schema;
9+
import io.swagger.v3.oas.annotations.parameters.RequestBody;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import org.springdoc.core.annotations.RouterOperation;
12+
import org.springdoc.core.annotations.RouterOperations;
13+
import test.org.springdoc.api.app72.entity.Position;
14+
import test.org.springdoc.api.app72.handler.PositionHandler;
15+
16+
import org.springframework.context.annotation.Bean;
17+
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.http.MediaType;
19+
import org.springframework.web.reactive.function.server.RouterFunction;
20+
import org.springframework.web.reactive.function.server.RouterFunctions;
21+
import org.springframework.web.reactive.function.server.ServerResponse;
22+
23+
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
24+
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
25+
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
26+
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
27+
28+
@Configuration
29+
public class PositionRouter {
30+
31+
@Bean
32+
@RouterOperations({ @RouterOperation(path = "/getAllPositions", operation = @Operation(description = "Get all positions", operationId = "findAll",tags = "positions",
33+
responses = @ApiResponse(responseCode = "200", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Position.class)))))),
34+
@RouterOperation(path = "/getPosition/{id}", operation = @Operation(description = "Find all", operationId = "findById", tags = "positions",parameters = @Parameter(name = "id", in = ParameterIn.PATH, schema = @Schema(type = "string")),
35+
responses = @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = Position.class))))),
36+
@RouterOperation(path = "/createPosition", operation = @Operation(description = "Save position", operationId = "save", tags = "positions",requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = Position.class))),
37+
responses = @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = Position.class))))),
38+
@RouterOperation(path = "/deletePosition/{id}", operation = @Operation(description = "Delete By Id", operationId = "deleteBy",tags = "positions", parameters = @Parameter(name = "id", in = ParameterIn.PATH, schema = @Schema(type = "string")),
39+
responses = @ApiResponse(responseCode = "200", content = @Content)))})
40+
public RouterFunction<ServerResponse> positionRoute(PositionHandler handler) {
41+
return RouterFunctions
42+
.route(GET("/getAllPositions").and(accept(MediaType.APPLICATION_JSON)), handler::findAll)
43+
.andRoute(GET("/getPosition/{id}").and(accept(MediaType.APPLICATION_STREAM_JSON)), handler::findById)
44+
.andRoute(POST("/createPosition").and(accept(MediaType.APPLICATION_JSON)), handler::save)
45+
.andRoute(DELETE("/deletePosition/{id}").and(accept(MediaType.APPLICATION_JSON)), handler::delete);
46+
}
47+
48+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package test.org.springdoc.api.app72.entity;
2+
3+
public class Person {
4+
5+
private String id;
6+
7+
private Sex sex;
8+
private String firstName;
9+
private String lastName;
10+
private String age;
11+
private String interests;
12+
private String email;
13+
14+
public String getId() {
15+
return id;
16+
}
17+
18+
public void setId(String id) {
19+
this.id = id;
20+
}
21+
22+
public Sex getSex() {
23+
return sex;
24+
}
25+
26+
public void setSex(Sex sex) {
27+
this.sex = sex;
28+
}
29+
30+
public String getFirstName() {
31+
return firstName;
32+
}
33+
34+
public void setFirstName(String firstName) {
35+
this.firstName = firstName;
36+
}
37+
38+
public String getLastName() {
39+
return lastName;
40+
}
41+
42+
public void setLastName(String lastName) {
43+
this.lastName = lastName;
44+
}
45+
46+
public String getAge() {
47+
return age;
48+
}
49+
50+
public void setAge(String age) {
51+
this.age = age;
52+
}
53+
54+
public String getInterests() {
55+
return interests;
56+
}
57+
58+
public void setInterests(String interests) {
59+
this.interests = interests;
60+
}
61+
62+
public String getEmail() {
63+
return email;
64+
}
65+
66+
public void setEmail(String email) {
67+
this.email = email;
68+
}
69+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package test.org.springdoc.api.app72.entity;
2+
3+
import java.util.Date;
4+
5+
import javax.validation.constraints.NotBlank;
6+
import javax.validation.constraints.NotNull;
7+
import javax.validation.constraints.Size;
8+
9+
10+
public class Position {
11+
12+
private String id;
13+
14+
@NotBlank
15+
@Size(max = 140)
16+
private String positionName;
17+
18+
private String description;
19+
20+
@NotNull
21+
private Date createdAt = new Date();
22+
23+
public String getId() {
24+
return id;
25+
}
26+
27+
public void setId(String id) {
28+
this.id = id;
29+
}
30+
31+
public String getPositionName() {
32+
return positionName;
33+
}
34+
35+
public void setPositionName(String positionName) {
36+
this.positionName = positionName;
37+
}
38+
39+
public String getDescription() {
40+
return description;
41+
}
42+
43+
public void setDescription(String description) {
44+
this.description = description;
45+
}
46+
47+
public Date getCreatedAt() {
48+
return createdAt;
49+
}
50+
51+
public void setCreatedAt(Date createdAt) {
52+
this.createdAt = createdAt;
53+
}
54+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package test.org.springdoc.api.app72.entity;
2+
3+
public enum Sex {
4+
MAN, WOMEN
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package test.org.springdoc.api.app72.exception;
2+
3+
public class PositionNotFoundException extends RuntimeException {
4+
5+
public PositionNotFoundException(String positionId) {
6+
super("Position not found with id " + positionId);
7+
}
8+
}

0 commit comments

Comments
 (0)