Skip to content

Commit edbf610

Browse files
committed
Fix behaviuor of required flag for schema class fields. Fixes #2185
1 parent 2e76ccf commit edbf610

File tree

5 files changed

+137
-22
lines changed

5 files changed

+137
-22
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ SpringDocProviders springDocProviders(Optional<ActuatorProvider> actuatorProvide
425425
Optional<RepositoryRestResourceProvider> repositoryRestResourceProvider, Optional<RouterFunctionProvider> routerFunctionProvider,
426426
Optional<SpringWebProvider> springWebProvider, Optional<WebConversionServiceProvider> webConversionServiceProvider,
427427
ObjectMapperProvider objectMapperProvider) {
428+
objectMapperProvider.jsonMapper().registerModule(new SpringDocRequiredModule());
428429
return new SpringDocProviders(actuatorProvider, springCloudFunctionProvider, springSecurityOAuth2Provider, repositoryRestResourceProvider, routerFunctionProvider, springWebProvider, webConversionServiceProvider, objectMapperProvider);
429430
}
430431

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

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,7 @@ import kotlin.reflect.jvm.kotlinFunction
3636
@ConditionalOnClass(Continuation::class)
3737
@ConditionalOnWebApplication
3838
@ConditionalOnBean(SpringDocConfiguration::class)
39-
open class SpringDocKotlinConfiguration(objectMapperProvider: ObjectMapperProvider) {
40-
41-
/**
42-
* SpringDoc Kotlin Module Configuration
43-
*
44-
* @param objectMapperProvider Object Mapper Provider
45-
* @return the nullable Kotlin Request Parameter Customizer
46-
*/
39+
class SpringDocKotlinConfiguration() {
4740

4841
/**
4942
* Instantiates a new Spring doc kotlin configuration.
@@ -54,7 +47,6 @@ open class SpringDocKotlinConfiguration(objectMapperProvider: ObjectMapperProvid
5447
.addRequestWrapperToIgnore(Continuation::class.java)
5548
.replaceWithSchema(ByteArray::class.java, ByteArraySchema())
5649
.addDeprecatedType(Deprecated::class.java)
57-
objectMapperProvider.jsonMapper().registerModule(SpringDocRequiredModule())
5850
}
5951

6052
/**
@@ -65,7 +57,7 @@ open class SpringDocKotlinConfiguration(objectMapperProvider: ObjectMapperProvid
6557
@Bean
6658
@Lazy(false)
6759
@ConditionalOnMissingBean
68-
open fun kotlinCoroutinesReturnTypeParser(): KotlinCoroutinesReturnTypeParser {
60+
fun kotlinCoroutinesReturnTypeParser(): KotlinCoroutinesReturnTypeParser {
6961
return KotlinCoroutinesReturnTypeParser()
7062
}
7163

@@ -81,7 +73,7 @@ open class SpringDocKotlinConfiguration(objectMapperProvider: ObjectMapperProvid
8173
matchIfMissing = true
8274
)
8375
@ConditionalOnMissingBean
84-
open fun nullableKotlinRequestParameterCustomizer(): ParameterCustomizer {
76+
fun nullableKotlinRequestParameterCustomizer(): ParameterCustomizer {
8577
return ParameterCustomizer { parameterModel, methodParameter ->
8678
if (parameterModel == null) return@ParameterCustomizer null
8779
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(methodParameter.parameterType)) {

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
package org.springdoc.core.configuration;
2424

2525
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
26-
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
2726
import com.fasterxml.jackson.databind.module.SimpleModule;
27+
import io.swagger.v3.core.jackson.SwaggerAnnotationIntrospector;
2828
import io.swagger.v3.oas.annotations.media.Schema;
29+
import org.apache.commons.lang3.StringUtils;
2930

3031
/**
3132
* The type Spring doc required module.
@@ -42,11 +43,21 @@ public void setupModule(SetupContext context) {
4243
/**
4344
* The type Respect schema required annotation introspector.
4445
*/
45-
private static class RespectSchemaRequiredAnnotationIntrospector extends NopAnnotationIntrospector {
46+
private static class RespectSchemaRequiredAnnotationIntrospector extends SwaggerAnnotationIntrospector {
47+
4648
@Override
47-
public Boolean hasRequiredMarker(AnnotatedMember m) {
48-
Schema schemaAnnotation = m.getAnnotation(Schema.class);
49-
return schemaAnnotation != null ? schemaAnnotation.required() : null;
49+
public Boolean hasRequiredMarker(AnnotatedMember annotatedMember) {
50+
Schema schemaAnnotation = annotatedMember.getAnnotation(Schema.class);
51+
if (schemaAnnotation != null) {
52+
Schema.RequiredMode requiredMode = schemaAnnotation.requiredMode();
53+
if (schemaAnnotation.required() || requiredMode == Schema.RequiredMode.REQUIRED) {
54+
return true;
55+
}
56+
else if (requiredMode == Schema.RequiredMode.NOT_REQUIRED || StringUtils.isNotEmpty(schemaAnnotation.defaultValue())) {
57+
return false;
58+
}
59+
}
60+
return super.hasRequiredMarker(annotatedMember);
5061
}
5162
}
5263
}

springdoc-openapi-tests/springdoc-openapi-kotlin-tests/src/test/kotlin/test/org/springdoc/api/app9/DemoController.kt

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package test.org.springdoc.api.app9
22

33
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotNull
45
import org.springframework.web.bind.annotation.GetMapping
56
import org.springframework.web.bind.annotation.RequestMapping
67
import org.springframework.web.bind.annotation.RestController
@@ -18,8 +19,55 @@ data class DemoDto(
1819
var id: Long,
1920
)
2021

21-
class DemoRequest {
22-
@field:Schema(required = false, description = "Should not be required")
23-
val nonNullableWithDefault: String = "a default value"
22+
class DemoRequest (
2423

25-
}
24+
@field:Schema(required = true, defaultValue = "a default value")
25+
val requiredNullableDefault: String?,
26+
27+
@field:Schema(required = true)
28+
val requiredNullableNoDefault: String?,
29+
30+
@field:Schema(required = true, defaultValue = "a default value")
31+
val requiredNoNullableDefault: String,
32+
33+
@field:Schema(required = true)
34+
val requiredNoNullableNoDefault: String,
35+
36+
@field:Schema(requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "a default value")
37+
val requiredNullableDefault1: String?,
38+
39+
@field:Schema(requiredMode = Schema.RequiredMode.REQUIRED)
40+
val requiredNullableNoDefault1: String?,
41+
42+
@field:Schema(requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "a default value")
43+
val requiredNoNullableDefault1: String,
44+
45+
@field:Schema(requiredMode = Schema.RequiredMode.REQUIRED)
46+
val requiredNoNullableNoDefault1: String,
47+
48+
@field:Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, defaultValue = "a default value")
49+
val noRequiredNullableDefault2: String?,
50+
51+
@field:Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED)
52+
val noRequiredNullableNoDefault2: String?,
53+
54+
@field:Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, defaultValue = "a default value")
55+
val noRequiredNoNullableDefault2: String,
56+
57+
@field:Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED)
58+
val noRequiredNoNullableNoDefault2: String,
59+
60+
@field:Schema(defaultValue = "a default value")
61+
val noRequiredNullableDefault: String?,
62+
63+
@field:Schema
64+
val noRequiredNullableNoDefault: String?,
65+
66+
@field:Schema(defaultValue = "a default value")
67+
val noRequiredNoNullableDefault: String,
68+
69+
@field:Schema
70+
val noRequiredNoNullableNoDefault: String,
71+
72+
73+
)

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,74 @@
4545
"components": {
4646
"schemas": {
4747
"DemoRequest": {
48+
"required": [
49+
"noRequiredNoNullableNoDefault",
50+
"requiredNoNullableDefault",
51+
"requiredNoNullableDefault1",
52+
"requiredNoNullableNoDefault",
53+
"requiredNoNullableNoDefault1",
54+
"requiredNullableDefault",
55+
"requiredNullableDefault1",
56+
"requiredNullableNoDefault",
57+
"requiredNullableNoDefault1"
58+
],
4859
"type": "object",
4960
"properties": {
50-
"nonNullableWithDefault": {
61+
"requiredNullableDefault": {
62+
"type": "string",
63+
"default": "a default value"
64+
},
65+
"requiredNullableNoDefault": {
66+
"type": "string"
67+
},
68+
"requiredNoNullableDefault": {
69+
"type": "string",
70+
"default": "a default value"
71+
},
72+
"requiredNoNullableNoDefault": {
73+
"type": "string"
74+
},
75+
"requiredNullableDefault1": {
76+
"type": "string",
77+
"default": "a default value"
78+
},
79+
"requiredNullableNoDefault1": {
80+
"type": "string"
81+
},
82+
"requiredNoNullableDefault1": {
83+
"type": "string",
84+
"default": "a default value"
85+
},
86+
"requiredNoNullableNoDefault1": {
87+
"type": "string"
88+
},
89+
"noRequiredNullableDefault2": {
90+
"type": "string",
91+
"default": "a default value"
92+
},
93+
"noRequiredNullableNoDefault2": {
94+
"type": "string"
95+
},
96+
"noRequiredNoNullableDefault2": {
97+
"type": "string",
98+
"default": "a default value"
99+
},
100+
"noRequiredNoNullableNoDefault2": {
101+
"type": "string"
102+
},
103+
"noRequiredNullableDefault": {
104+
"type": "string",
105+
"default": "a default value"
106+
},
107+
"noRequiredNullableNoDefault": {
108+
"type": "string"
109+
},
110+
"noRequiredNoNullableDefault": {
51111
"type": "string",
52-
"description": "Should not be required"
112+
"default": "a default value"
113+
},
114+
"noRequiredNoNullableNoDefault": {
115+
"type": "string"
53116
}
54117
}
55118
},

0 commit comments

Comments
 (0)