Skip to content

Commit 22e3e1a

Browse files
author
bnasslahsen
committed
Add support kotlin Flow as response type. Fixes springdoc#560
1 parent 7fa2f34 commit 22e3e1a

File tree

7 files changed

+119
-35
lines changed

7 files changed

+119
-35
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocUtils.java

+10
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,15 @@ public SpringDocUtils removeAnnotationsToIgnore(Class<?>... classes) {
103103
GenericParameterBuilder.removeAnnotationsToIgnore(classes);
104104
return this;
105105
}
106+
107+
public SpringDocUtils addFluxWrapperToIgnore(Class<?> cls) {
108+
ConverterUtils.addFluxWrapperToIgnore(cls);
109+
return this;
110+
}
111+
112+
public SpringDocUtils removeFluxWrapperToIgnore(Class<?> cls) {
113+
ConverterUtils.removeFluxWrapperToIgnore(cls);
114+
return this;
115+
}
106116
}
107117

springdoc-openapi-common/src/main/java/org/springdoc/core/converters/ConverterUtils.java

+16
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public class ConverterUtils {
3333

3434
private static final List<Class<?>> RESPONSE_TYPES_TO_IGNORE = new ArrayList<>();
3535

36+
private static final List<Class<?>> FLUX_WRAPPERS_TO_IGNORE = new ArrayList<>();
37+
3638
static {
3739
RESULT_WRAPPERS_TO_IGNORE.add(Callable.class);
3840
RESULT_WRAPPERS_TO_IGNORE.add(ResponseEntity.class);
@@ -70,4 +72,18 @@ public static void removeResponseTypeToIgnore(Class<?> classes) {
7072
if (RESPONSE_TYPES_TO_IGNORE.containsAll(classesToIgnore))
7173
RESPONSE_TYPES_TO_IGNORE.removeAll(Arrays.asList(classes));
7274
}
75+
76+
public static boolean isFluxTypeWrapper(Class<?> rawClass) {
77+
return FLUX_WRAPPERS_TO_IGNORE.stream().anyMatch(clazz -> clazz.isAssignableFrom(rawClass));
78+
}
79+
80+
public static void removeFluxWrapperToIgnore(Class<?> classes) {
81+
List classesToIgnore = Arrays.asList(classes);
82+
if (FLUX_WRAPPERS_TO_IGNORE.containsAll(classesToIgnore))
83+
FLUX_WRAPPERS_TO_IGNORE.removeAll(Arrays.asList(classes));
84+
}
85+
86+
public static void addFluxWrapperToIgnore(Class<?> cls) {
87+
FLUX_WRAPPERS_TO_IGNORE.add(cls);
88+
}
7389
}

springdoc-openapi-kotlin/src/main/java/org/springdoc/kotlin/SpringDocKotlinConfiguration.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.swagger.v3.core.util.Json;
2323
import kotlin.Deprecated;
2424
import kotlin.coroutines.Continuation;
25+
import kotlinx.coroutines.flow.Flow;
2526

2627
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2728
import org.springframework.context.annotation.Bean;
@@ -37,7 +38,8 @@ public class SpringDocKotlinConfiguration {
3738

3839
static {
3940
getConfig().addRequestWrapperToIgnore(Continuation.class)
40-
.addDeprecatedType(Deprecated.class);
41+
.addDeprecatedType(Deprecated.class)
42+
.addFluxWrapperToIgnore(Flow.class);
4143
Json.mapper().registerModule(new KotlinModule());
4244
}
4345

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app5
20+
21+
import kotlinx.coroutines.delay
22+
import kotlinx.coroutines.flow.Flow
23+
import kotlinx.coroutines.flow.flow
24+
import org.springframework.web.bind.annotation.GetMapping
25+
import org.springframework.web.bind.annotation.RequestMapping
26+
import org.springframework.web.bind.annotation.RestController
27+
28+
29+
data class Person(
30+
val name: String,
31+
val nickname: String?
32+
)
33+
34+
@RestController
35+
@RequestMapping("/test")
36+
class HelloController {
37+
38+
@GetMapping("/")
39+
fun foo(): Flow<Int> = flow { // flow builder
40+
for (i in 1..3) {
41+
delay(100) // pretend we are doing something useful here
42+
emit(i) // emit next value
43+
}
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app5
20+
21+
import org.springframework.boot.autoconfigure.SpringBootApplication
22+
import org.springframework.context.annotation.ComponentScan
23+
import test.org.springdoc.api.AbstractKotlinSpringDocTest
24+
25+
@ComponentScan(basePackages = ["org.springdoc", "test.org.springdoc.api.app5"])
26+
class SpringDocApp5Test : AbstractKotlinSpringDocTest() {
27+
28+
@SpringBootApplication
29+
open class DemoApplication
30+
31+
}

springdoc-openapi-kotlin/src/test/resources/results/app5.json

+9-32
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,23 @@
1111
}
1212
],
1313
"paths": {
14-
"/test": {
14+
"/test/": {
1515
"get": {
1616
"tags": [
1717
"hello-controller"
1818
],
19-
"operationId": "index",
20-
"parameters": [
21-
{
22-
"name": "s",
23-
"in": "query",
24-
"required": true,
25-
"schema": {
26-
"$ref": "#/components/schemas/Person"
27-
}
28-
}
29-
],
19+
"operationId": "foo",
3020
"responses": {
3121
"200": {
3222
"description": "default response",
3323
"content": {
3424
"*/*": {
3525
"schema": {
36-
"$ref": "#/components/schemas/Person"
26+
"type": "array",
27+
"items": {
28+
"type": "integer",
29+
"format": "int32"
30+
}
3731
}
3832
}
3933
}
@@ -42,22 +36,5 @@
4236
}
4337
}
4438
},
45-
"components": {
46-
"schemas": {
47-
"Person": {
48-
"required": [
49-
"name"
50-
],
51-
"type": "object",
52-
"properties": {
53-
"name": {
54-
"type": "string"
55-
},
56-
"nickname": {
57-
"type": "string"
58-
}
59-
}
60-
}
61-
}
62-
}
63-
}
39+
"components": {}
40+
}

springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/converters/WebFluxSupportConverter.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,23 @@
3232
import reactor.core.publisher.Mono;
3333

3434
import static org.springdoc.core.SpringDocUtils.getConfig;
35+
import static org.springdoc.core.converters.ConverterUtils.isFluxTypeWrapper;
3536
import static org.springdoc.core.converters.ConverterUtils.isResponseTypeWrapper;
3637

3738

3839
public class WebFluxSupportConverter implements ModelConverter {
3940

4041
static {
41-
getConfig().addResponseWrapperToIgnore(Mono.class);
42+
getConfig().addResponseWrapperToIgnore(Mono.class)
43+
.addFluxWrapperToIgnore(Flux.class);
4244
}
4345

4446
@Override
4547
public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
4648
JavaType javaType = Json.mapper().constructType(type.getType());
4749
if (javaType != null) {
4850
Class<?> cls = javaType.getRawClass();
49-
if (Flux.class.isAssignableFrom(cls)) {
51+
if (isFluxTypeWrapper(cls)) {
5052
JavaType innerType = javaType.getBindings().getBoundType(0);
5153
if (innerType == null)
5254
return new StringSchema();

0 commit comments

Comments
 (0)