Skip to content

Commit e16183c

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

File tree

8 files changed

+259
-3
lines changed

8 files changed

+259
-3
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
/**
3030
* The type Constants.
31+
*
3132
* @author bnasslahsen
3233
*/
3334
public final class Constants {
@@ -393,6 +394,11 @@ public final class Constants {
393394
*/
394395
public static final String SPRINGDOC_SORT_CONVERTER_ENABLED = "springdoc.model-converters.sort-converter.enabled";
395396

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

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

+45-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
/**
4040
* The type Spring doc config properties.
41+
*
4142
* @author bnasslahsen
4243
*/
4344
@Lazy(false)
@@ -196,6 +197,38 @@ public class SpringDocConfigProperties {
196197
*/
197198
private SortConverter sortConverter = new SortConverter();
198199

200+
/**
201+
* The Nullable request parameter enabled.
202+
*/
203+
private boolean nullableRequestParameterEnabled;
204+
205+
/**
206+
* Gets override with generic response.
207+
*
208+
* @return the override with generic response
209+
*/
210+
public Boolean getOverrideWithGenericResponse() {
211+
return overrideWithGenericResponse;
212+
}
213+
214+
/**
215+
* Is nullable request parameter enabled boolean.
216+
*
217+
* @return the boolean
218+
*/
219+
public boolean isNullableRequestParameterEnabled() {
220+
return nullableRequestParameterEnabled;
221+
}
222+
223+
/**
224+
* Sets nullable request parameter enabled.
225+
*
226+
* @param nullableRequestParameterEnabled the nullable request parameter enabled
227+
*/
228+
public void setNullableRequestParameterEnabled(boolean nullableRequestParameterEnabled) {
229+
this.nullableRequestParameterEnabled = nullableRequestParameterEnabled;
230+
}
231+
199232
/**
200233
* Is default support form data boolean.
201234
*
@@ -252,6 +285,7 @@ public void setShowSpringCloudFunctions(boolean showSpringCloudFunctions) {
252285

253286
/**
254287
* Is default flat param object
288+
*
255289
* @return the boolean
256290
*/
257291
public boolean isDefaultFlatParamObject() {
@@ -768,6 +802,7 @@ public void setPreLoadingEnabled(boolean preLoadingEnabled) {
768802

769803
/**
770804
* The type Model converters.
805+
*
771806
* @author bnasslashen
772807
*/
773808
public static class ModelConverters {
@@ -844,6 +879,7 @@ public void setPolymorphicConverter(PolymorphicConverter polymorphicConverter) {
844879

845880
/**
846881
* The type Sort converter.
882+
*
847883
* @author daniel -shuy
848884
*/
849885
public static class SortConverter {
@@ -874,6 +910,7 @@ public void setEnabled(boolean enabled) {
874910

875911
/**
876912
* The type Deprecating converter.
913+
*
877914
* @author bnasslashen
878915
*/
879916
public static class DeprecatingConverter {
@@ -934,6 +971,7 @@ public void setEnabled(boolean enabled) {
934971

935972
/**
936973
* The type Pageable converter.
974+
*
937975
* @author bnasslashen
938976
*/
939977
public static class PageableConverter {
@@ -965,6 +1003,7 @@ public void setEnabled(boolean enabled) {
9651003

9661004
/**
9671005
* The type Webjars.
1006+
*
9681007
* @author bnasslahsen
9691008
*/
9701009
public static class Webjars {
@@ -994,6 +1033,7 @@ public void setPrefix(String prefix) {
9941033

9951034
/**
9961035
* The type Api docs.
1036+
*
9971037
* @author bnasslahsen
9981038
*/
9991039
public static class ApiDocs {
@@ -1117,11 +1157,11 @@ public void setVersion(OpenApiVersion version) {
11171157
*/
11181158
public enum OpenApiVersion {
11191159
/**
1120-
*Openapi 3.0.1 version.
1160+
* Openapi 3.0.1 version.
11211161
*/
11221162
OPENAPI_3_0("3.0.1"),
11231163
/**
1124-
*Openapi 3.1.0 version.
1164+
* Openapi 3.1.0 version.
11251165
*/
11261166
OPENAPI_3_1("3.1.0");
11271167

@@ -1153,6 +1193,7 @@ public String getVersion() {
11531193

11541194
/**
11551195
* The type Groups.
1196+
*
11561197
* @author bnasslahsen
11571198
*/
11581199
public static class Groups {
@@ -1183,6 +1224,7 @@ public void setEnabled(boolean enabled) {
11831224

11841225
/**
11851226
* The type Cache.
1227+
*
11861228
* @author bnasslahsen
11871229
*/
11881230
public static class Cache {
@@ -1212,6 +1254,7 @@ public void setDisabled(boolean disabled) {
12121254

12131255
/**
12141256
* The type Group config.
1257+
*
12151258
* @author bnasslahsen
12161259
*/
12171260
public static class GroupConfig {

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.springdoc.kotlin
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.Constants
67
import org.springdoc.core.SpringDocConfiguration
@@ -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 @@ class SpringDocKotlinConfiguration(objectMapperProvider: ObjectMapperProvider) {
6062
*/
6163
@Bean
6264
@Lazy(false)
65+
@ConditionalOnProperty(
66+
name = [Constants.SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED],
67+
matchIfMissing = true
68+
)
6369
@ConditionalOnMissingBean
6470
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-kotlin/src/test/kotlin/test/org/springdoc/api/app7/ExampleController.kt

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

3+
import io.swagger.v3.oas.annotations.Parameter
4+
import io.swagger.v3.oas.annotations.parameters.RequestBody
35
import org.springframework.web.bind.annotation.GetMapping
6+
import org.springframework.web.bind.annotation.PostMapping
47
import org.springframework.web.bind.annotation.RequestMapping
58
import org.springframework.web.bind.annotation.RequestParam
69
import org.springframework.web.bind.annotation.RestController
@@ -11,4 +14,8 @@ data class Greeting(val greeting: String)
1114
interface ExampleController {
1215
@GetMapping("/")
1316
fun greet(@RequestParam name: String?): Greeting
17+
18+
@GetMapping("/test")
19+
fun test(@RequestParam @Parameter(required = true) name: String?): Greeting
20+
1421
}
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.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-kotlin/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)