Skip to content

Commit b70b7b7

Browse files
committed
Support for nullable request parameters in Kotlin. Fixes #2006
1 parent 4179138 commit b70b7b7

File tree

8 files changed

+262
-3
lines changed

8 files changed

+262
-3
lines changed

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.springdoc.core.configuration
22

33
import com.fasterxml.jackson.module.kotlin.KotlinModule
4+
import io.swagger.v3.oas.annotations.Parameter
45
import io.swagger.v3.oas.models.media.ByteArraySchema
56
import org.springdoc.core.customizers.ParameterCustomizer
67
import org.springdoc.core.parsers.KotlinCoroutinesReturnTypeParser
@@ -14,6 +15,7 @@ import org.springframework.context.annotation.Bean
1415
import org.springframework.context.annotation.Configuration
1516
import org.springframework.context.annotation.Lazy
1617
import org.springframework.core.MethodParameter
18+
import org.springframework.core.annotation.AnnotatedElementUtils
1719
import kotlin.coroutines.Continuation
1820
import kotlin.reflect.KParameter
1921
import kotlin.reflect.jvm.kotlinFunction
@@ -60,13 +62,25 @@ open class SpringDocKotlinConfiguration(objectMapperProvider: ObjectMapperProvid
6062
*/
6163
@Bean
6264
@Lazy(false)
65+
@ConditionalOnProperty(
66+
name = [Constants.SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED],
67+
matchIfMissing = true
68+
)
6369
@ConditionalOnMissingBean
6470
open fun nullableKotlinRequestParameterCustomizer(): ParameterCustomizer {
6571
return ParameterCustomizer { parameterModel, methodParameter ->
6672
if (parameterModel == null) return@ParameterCustomizer null
6773
val kParameter = methodParameter.toKParameter()
6874
if (kParameter != null) {
69-
parameterModel.required = kParameter.type.isMarkedNullable == false
75+
val parameterDoc = AnnotatedElementUtils.findMergedAnnotation(
76+
AnnotatedElementUtils.forAnnotations(*methodParameter.parameterAnnotations),
77+
Parameter::class.java
78+
)
79+
// Swagger @Parameter annotation takes precedence
80+
if (parameterDoc != null && parameterDoc.required)
81+
parameterModel.required = parameterDoc.required
82+
else
83+
parameterModel.required = kParameter.type.isMarkedNullable == false
7084
}
7185
return@ParameterCustomizer parameterModel
7286
}

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java

+53-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
/**
4444
* The type Spring doc config properties.
45+
*
4546
* @author bnasslahsen
4647
*/
4748
@Lazy(false)
@@ -236,6 +237,47 @@ public class SpringDocConfigProperties {
236237
*/
237238
private SortConverter sortConverter = new SortConverter();
238239

240+
/**
241+
* The Nullable request parameter enabled.
242+
*/
243+
private boolean nullableRequestParameterEnabled;
244+
245+
/**
246+
* Gets override with generic response.
247+
*
248+
* @return the override with generic response
249+
*/
250+
public Boolean getOverrideWithGenericResponse() {
251+
return overrideWithGenericResponse;
252+
}
253+
254+
/**
255+
* Sets override with generic response.
256+
*
257+
* @param overrideWithGenericResponse the override with generic response
258+
*/
259+
public void setOverrideWithGenericResponse(Boolean overrideWithGenericResponse) {
260+
this.overrideWithGenericResponse = overrideWithGenericResponse;
261+
}
262+
263+
/**
264+
* Is nullable request parameter enabled boolean.
265+
*
266+
* @return the boolean
267+
*/
268+
public boolean isNullableRequestParameterEnabled() {
269+
return nullableRequestParameterEnabled;
270+
}
271+
272+
/**
273+
* Sets nullable request parameter enabled.
274+
*
275+
* @param nullableRequestParameterEnabled the nullable request parameter enabled
276+
*/
277+
public void setNullableRequestParameterEnabled(boolean nullableRequestParameterEnabled) {
278+
this.nullableRequestParameterEnabled = nullableRequestParameterEnabled;
279+
}
280+
239281
/**
240282
* Is default support form data boolean.
241283
*
@@ -936,6 +978,7 @@ public void setPreLoadingEnabled(boolean preLoadingEnabled) {
936978

937979
/**
938980
* The type Model converters.
981+
*
939982
* @author bnasslashen
940983
*/
941984
public static class ModelConverters {
@@ -1012,6 +1055,7 @@ public void setPolymorphicConverter(PolymorphicConverter polymorphicConverter) {
10121055

10131056
/**
10141057
* The type Deprecating converter.
1058+
*
10151059
* @author bnasslashen
10161060
*/
10171061
public static class DeprecatingConverter {
@@ -1072,6 +1116,7 @@ public void setEnabled(boolean enabled) {
10721116

10731117
/**
10741118
* The type Pageable converter.
1119+
*
10751120
* @author bnasslashen
10761121
*/
10771122
public static class PageableConverter {
@@ -1103,6 +1148,7 @@ public void setEnabled(boolean enabled) {
11031148

11041149
/**
11051150
* The type Sort converter.
1151+
*
11061152
* @author daniel -shuy
11071153
*/
11081154
public static class SortConverter {
@@ -1133,6 +1179,7 @@ public void setEnabled(boolean enabled) {
11331179

11341180
/**
11351181
* The type Webjars.
1182+
*
11361183
* @author bnasslahsen
11371184
*/
11381185
public static class Webjars {
@@ -1162,6 +1209,7 @@ public void setPrefix(String prefix) {
11621209

11631210
/**
11641211
* The type Api docs.
1212+
*
11651213
* @author bnasslahsen
11661214
*/
11671215
public static class ApiDocs {
@@ -1285,11 +1333,11 @@ public void setVersion(OpenApiVersion version) {
12851333
*/
12861334
public enum OpenApiVersion {
12871335
/**
1288-
*Openapi 3.0.1 version.
1336+
* Openapi 3.0.1 version.
12891337
*/
12901338
OPENAPI_3_0("3.0.1"),
12911339
/**
1292-
*Openapi 3.1.0 version.
1340+
* Openapi 3.1.0 version.
12931341
*/
12941342
OPENAPI_3_1("3.1.0");
12951343

@@ -1320,6 +1368,7 @@ public String getVersion() {
13201368

13211369
/**
13221370
* The type Groups.
1371+
*
13231372
* @author bnasslahsen
13241373
*/
13251374
public static class Groups {
@@ -1350,6 +1399,7 @@ public void setEnabled(boolean enabled) {
13501399

13511400
/**
13521401
* The type Cache.
1402+
*
13531403
* @author bnasslahsen
13541404
*/
13551405
public static class Cache {
@@ -1379,6 +1429,7 @@ public void setDisabled(boolean disabled) {
13791429

13801430
/**
13811431
* The type Group config.
1432+
*
13821433
* @author bnasslahsen
13831434
*/
13841435
public static class GroupConfig {

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java

+4
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ public final class Constants {
395395
*/
396396
public static final String SPRINGDOC_SORT_CONVERTER_ENABLED = "springdoc.model-converters.sort-converter.enabled";
397397

398+
/**
399+
* The constant SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED.
400+
*/
401+
public static final String SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED = "springdoc.nullable-request-parameter-enabled";
398402
/**
399403
* Instantiates a new Constants.
400404
*/

springdoc-openapi-tests/springdoc-openapi-kotlin-tests/src/test/kotlin/test/org/springdoc/api/app7/ExampleController.kt

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package test.org.springdoc.api.app7
22

3+
import io.swagger.v3.oas.annotations.Parameter
34
import org.springframework.web.bind.annotation.GetMapping
45
import org.springframework.web.bind.annotation.RequestMapping
56
import org.springframework.web.bind.annotation.RequestParam
@@ -11,4 +12,7 @@ data class Greeting(val greeting: String)
1112
interface ExampleController {
1213
@GetMapping("/")
1314
fun greet(@RequestParam name: String?): Greeting
15+
16+
@GetMapping("/test")
17+
fun test(@RequestParam @Parameter(required = true) name: String?): Greeting
1418
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package test.org.springdoc.api.app8
2+
3+
import io.swagger.v3.oas.annotations.Parameter
4+
import org.springframework.web.bind.annotation.GetMapping
5+
import org.springframework.web.bind.annotation.RequestParam
6+
import org.springframework.web.bind.annotation.RestController
7+
8+
data class Greeting(val greeting: String)
9+
10+
@RestController
11+
interface ExampleController {
12+
@GetMapping("/")
13+
fun greet(@RequestParam name: String?): Greeting
14+
15+
@GetMapping("/test")
16+
fun test(@RequestParam @Parameter(required = true) name: String?): Greeting
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.app8
20+
21+
import org.springdoc.core.utils.Constants
22+
import org.springframework.aop.framework.ProxyFactory
23+
import org.springframework.boot.autoconfigure.SpringBootApplication
24+
import org.springframework.context.annotation.Bean
25+
import org.springframework.context.annotation.ComponentScan
26+
import org.springframework.context.support.GenericApplicationContext
27+
import org.springframework.test.context.TestPropertySource
28+
import test.org.springdoc.api.AbstractKotlinSpringDocTest
29+
30+
@TestPropertySource(properties = [Constants.SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED+"=false"])
31+
class SpringDocApp8Test : AbstractKotlinSpringDocTest() {
32+
33+
@SpringBootApplication
34+
@ComponentScan(basePackages = ["org.springdoc", "test.org.springdoc.api.app8"])
35+
class DemoApplication{
36+
@Bean
37+
fun controller(applicationContext: GenericApplicationContext): ExampleController {
38+
return createProxy(ExampleController::class.java)
39+
}
40+
41+
private fun <T> createProxy(clazz: Class<T>): T {
42+
val proxyFactory = ProxyFactory(clazz)
43+
proxyFactory.targetClass = clazz
44+
@Suppress("UNCHECKED_CAST")
45+
return proxyFactory.proxy as T
46+
}
47+
}
48+
49+
}

springdoc-openapi-tests/springdoc-openapi-kotlin-tests/src/test/resources/results/app7.json

+30
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,36 @@
4040
}
4141
}
4242
}
43+
},
44+
"/test": {
45+
"get": {
46+
"tags": [
47+
"example-controller"
48+
],
49+
"operationId": "test",
50+
"parameters": [
51+
{
52+
"name": "name",
53+
"in": "query",
54+
"required": true,
55+
"schema": {
56+
"type": "string"
57+
}
58+
}
59+
],
60+
"responses": {
61+
"200": {
62+
"description": "OK",
63+
"content": {
64+
"*/*": {
65+
"schema": {
66+
"$ref": "#/components/schemas/Greeting"
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
4373
}
4474
},
4575
"components": {

0 commit comments

Comments
 (0)