Skip to content

Commit 679c73b

Browse files
author
bnasslahsen
committed
Improve support of Webflux with Functional Endpoints
1 parent a57f87d commit 679c73b

File tree

9 files changed

+430
-5
lines changed

9 files changed

+430
-5
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import io.swagger.v3.oas.models.PathItem;
4747
import io.swagger.v3.oas.models.PathItem.HttpMethod;
4848
import io.swagger.v3.oas.models.Paths;
49+
import io.swagger.v3.oas.models.media.StringSchema;
4950
import io.swagger.v3.oas.models.responses.ApiResponses;
5051
import org.apache.commons.lang3.ArrayUtils;
5152
import org.apache.commons.lang3.StringUtils;
@@ -258,6 +259,12 @@ protected void calculatePath(String operationPath, Set<RequestMethod> requestMet
258259
Operation operation = (existingOperation != null) ? existingOperation : new Operation();
259260
if (apiOperation != null)
260261
openAPI = operationParser.parse(apiOperation, operation, openAPI, methodAttributes);
262+
if (!CollectionUtils.isEmpty(operation.getParameters()))
263+
operation.getParameters().forEach(parameter -> {
264+
if (parameter.getSchema() == null)
265+
parameter.setSchema(new StringSchema());
266+
}
267+
);
261268
PathItem pathItemObject = buildPathItem(requestMethod, operation, operationPath, paths);
262269
paths.addPathItem(operationPath, pathItemObject);
263270
}
@@ -582,13 +589,13 @@ && isEqualArrays(routerFunctionData1.getConsumes(), routerOperation.getConsumes(
582589
private boolean isEqualArrays(String[] array1, String[] array2) {
583590
Arrays.sort(array1);
584591
Arrays.sort(array2);
585-
return Arrays.equals(array1,array2);
592+
return Arrays.equals(array1, array2);
586593
}
587594

588595
private boolean isEqualMethods(RequestMethod[] requestMethods1, RequestMethod[] requestMethods2) {
589596
Arrays.sort(requestMethods1);
590597
Arrays.sort(requestMethods2);
591-
return Arrays.equals(requestMethods1,requestMethods2);
598+
return Arrays.equals(requestMethods1, requestMethods2);
592599
}
593600

594601
private void fillRouterOperation(RouterFunctionData routerFunctionData, org.springdoc.core.models.RouterOperation routerOperation) {

springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app71/EmployeeFunctionalConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ RouterFunction<ServerResponse> getAllEmployeesRoute() {
4141

4242
@Bean
4343
@RouterOperation(operation = @Operation(operationId = "findEmployeeById", summary = "Find purchase order by ID", tags = { "MyEmployee" },
44-
parameters = { @Parameter(in = ParameterIn.PATH, name = "id", description = "Employee Id", schema = @Schema(type = "string")) },
44+
parameters = { @Parameter(in = ParameterIn.PATH, name = "id", description = "Employee Id") },
4545
responses = { @ApiResponse(responseCode = "200", description = "successful operation", content = @Content(schema = @Schema(implementation = Employee.class))),
4646
@ApiResponse(responseCode = "400", description = "Invalid Employee ID supplied"),
4747
@ApiResponse(responseCode = "404", description = "Employee not found") }))

springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app72/controller/PositionRouter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ public class PositionRouter {
3131
@Bean
3232
@RouterOperations({ @RouterOperation(path = "/getAllPositions", operation = @Operation(description = "Get all positions", operationId = "findAll",tags = "positions",
3333
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")),
34+
@RouterOperation(path = "/getPosition/{id}", operation = @Operation(description = "Find all", operationId = "findById", tags = "positions",parameters = @Parameter(name = "id", in = ParameterIn.PATH),
3535
responses = @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = Position.class))))),
3636
@RouterOperation(path = "/createPosition", operation = @Operation(description = "Save position", operationId = "save", tags = "positions",requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = Position.class))),
3737
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")),
38+
@RouterOperation(path = "/deletePosition/{id}", operation = @Operation(description = "Delete By Id", operationId = "deleteBy",tags = "positions", parameters = @Parameter(name = "id", in = ParameterIn.PATH),
3939
responses = @ApiResponse(responseCode = "200", content = @Content)))})
4040
public RouterFunction<ServerResponse> positionRoute(PositionHandler handler) {
4141
return RouterFunctions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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.app75;
22+
23+
import java.time.LocalDateTime;
24+
25+
public class Post {
26+
27+
private String id;
28+
private String title;
29+
private String content;
30+
private LocalDateTime createdDate;
31+
32+
public Post(String id, String title, String content) {
33+
this.id = id;
34+
this.title = title;
35+
this.content = content;
36+
this.createdDate = LocalDateTime.now();
37+
}
38+
39+
public String getId() {
40+
return id;
41+
}
42+
43+
public void setId(String id) {
44+
this.id = id;
45+
}
46+
47+
public String getTitle() {
48+
return title;
49+
}
50+
51+
public void setTitle(String title) {
52+
this.title = title;
53+
}
54+
55+
public String getContent() {
56+
return content;
57+
}
58+
59+
public void setContent(String content) {
60+
this.content = content;
61+
}
62+
63+
public LocalDateTime getCreatedDate() {
64+
return createdDate;
65+
}
66+
67+
public void setCreatedDate(LocalDateTime createdDate) {
68+
this.createdDate = createdDate;
69+
}
70+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.app75;
22+
23+
24+
import java.net.URI;
25+
26+
import reactor.core.publisher.Mono;
27+
28+
import org.springframework.stereotype.Component;
29+
import org.springframework.web.reactive.function.server.ServerRequest;
30+
import org.springframework.web.reactive.function.server.ServerResponse;
31+
32+
@Component
33+
class PostHandler {
34+
35+
private final PostRepository posts;
36+
37+
public PostHandler(PostRepository posts) {
38+
this.posts = posts;
39+
}
40+
41+
public Mono<ServerResponse> all(ServerRequest req) {
42+
return ServerResponse.ok().body(this.posts.findAll(), Post.class);
43+
}
44+
45+
public Mono<ServerResponse> create(ServerRequest req) {
46+
return req.bodyToMono(Post.class)
47+
.flatMap(post -> this.posts.save(post))
48+
.flatMap(p -> ServerResponse.created(URI.create("/posts/" + p.getId())).build());
49+
}
50+
51+
public Mono<ServerResponse> get(ServerRequest req) {
52+
return this.posts.findById(req.pathVariable("id"))
53+
.flatMap(post -> ServerResponse.ok().body(Mono.just(post), Post.class))
54+
.switchIfEmpty(ServerResponse.notFound().build());
55+
}
56+
57+
public Mono<ServerResponse> update(ServerRequest req) {
58+
59+
return Mono
60+
.zip(
61+
(data) -> {
62+
Post p = (Post) data[0];
63+
Post p2 = (Post) data[1];
64+
p.setTitle(p2.getTitle());
65+
p.setContent(p2.getContent());
66+
return p;
67+
},
68+
this.posts.findById(req.pathVariable("id")),
69+
req.bodyToMono(Post.class)
70+
)
71+
.cast(Post.class)
72+
.flatMap(post -> this.posts.save(post))
73+
.flatMap(post -> ServerResponse.noContent().build());
74+
75+
}
76+
77+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.app75;
22+
23+
import reactor.core.publisher.Flux;
24+
import reactor.core.publisher.Mono;
25+
26+
import org.springframework.stereotype.Component;
27+
28+
@Component
29+
public class PostRepository {
30+
31+
public Flux<Post> findByAuthor(String author) {
32+
return Flux.just(new Post("1", "title1", "author1"));
33+
}
34+
35+
public Flux<Post> findAll() {
36+
return Flux.just(new Post("2", "title2", "author2"));
37+
}
38+
39+
public Mono<Post> findById(String id) {
40+
return Mono.just(new Post("3", "title2", "author2"));
41+
}
42+
43+
public Mono<Post> save(Post post) {
44+
return Mono.just(new Post("4", "title2", "author2"));
45+
}
46+
47+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.app75;
22+
23+
import io.swagger.v3.oas.annotations.Operation;
24+
import io.swagger.v3.oas.annotations.Parameter;
25+
import io.swagger.v3.oas.annotations.enums.ParameterIn;
26+
import io.swagger.v3.oas.annotations.media.ArraySchema;
27+
import io.swagger.v3.oas.annotations.media.Content;
28+
import io.swagger.v3.oas.annotations.media.Schema;
29+
import io.swagger.v3.oas.annotations.parameters.RequestBody;
30+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
31+
import org.springdoc.core.annotations.RouterOperation;
32+
import org.springdoc.core.annotations.RouterOperations;
33+
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.web.bind.annotation.RequestMethod;
37+
import org.springframework.web.reactive.function.server.RouterFunction;
38+
import org.springframework.web.reactive.function.server.ServerResponse;
39+
40+
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
41+
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
42+
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
43+
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
44+
45+
@Configuration
46+
class PostRouter {
47+
48+
@RouterOperations({ @RouterOperation(path = "/posts", method = RequestMethod.GET, operation = @Operation(operationId = "all",
49+
responses = @ApiResponse(responseCode = "200", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Post.class)))))),
50+
@RouterOperation(path = "/posts", method = RequestMethod.POST, operation = @Operation(operationId = "create",
51+
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = Post.class))), responses = @ApiResponse(responseCode = "201"))),
52+
@RouterOperation(path = "/posts/{id}", method = RequestMethod.GET, operation = @Operation(operationId = "get",
53+
parameters = @Parameter(name = "id", in = ParameterIn.PATH),
54+
responses = @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = Post.class))))),
55+
@RouterOperation(path = "/posts/{id}", method = RequestMethod.PUT, operation = @Operation(operationId = "update",
56+
parameters = @Parameter(name = "id", in = ParameterIn.PATH),
57+
responses = @ApiResponse(responseCode = "202", content = @Content(schema = @Schema(implementation = Post.class))))) })
58+
@Bean
59+
public RouterFunction<ServerResponse> routes(PostHandler postController) {
60+
return route(GET("/posts"), postController::all)
61+
.andRoute(POST("/posts"), postController::create)
62+
.andRoute(GET("/posts/{id}"), postController::get)
63+
.andRoute(PUT("/posts/{id}"), postController::update);
64+
}
65+
}
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.app75;
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 SpringDocApp75Test extends AbstractSpringDocTest {
29+
30+
@SpringBootApplication
31+
@ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app75" })
32+
static class SpringDocTestApp {}
33+
}

0 commit comments

Comments
 (0)