Skip to content

Commit 3f755ac

Browse files
committed
Add support for deprecated fields. Fixes #2830
1 parent f175e3c commit 3f755ac

File tree

6 files changed

+302
-0
lines changed

6 files changed

+302
-0
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
package org.springdoc.core.configuration
2828

2929
import io.swagger.v3.oas.annotations.Parameter
30+
import org.springdoc.core.customizers.KotlinDeprecatedPropertyCustomizer
3031
import org.springdoc.core.customizers.ParameterCustomizer
32+
import org.springdoc.core.providers.ObjectMapperProvider
3133
import org.springdoc.core.utils.Constants
3234
import org.springdoc.core.utils.SpringDocUtils
3335
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
@@ -125,4 +127,10 @@ class SpringDocKotlinConfiguration() {
125127
return kotlinFunction.parameters[parameterIndex + 1]
126128
}
127129

130+
@Bean
131+
@Lazy(false)
132+
@ConditionalOnMissingBean
133+
fun kotlinDeprecatedPropertyCustomizer(objectMapperProvider: ObjectMapperProvider): KotlinDeprecatedPropertyCustomizer {
134+
return KotlinDeprecatedPropertyCustomizer(objectMapperProvider)
135+
}
128136
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2024 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package org.springdoc.core.customizers
28+
29+
import com.fasterxml.jackson.databind.JavaType
30+
import io.swagger.v3.core.converter.AnnotatedType
31+
import io.swagger.v3.core.converter.ModelConverter
32+
import io.swagger.v3.core.converter.ModelConverterContext
33+
import io.swagger.v3.oas.models.Components
34+
import io.swagger.v3.oas.models.media.Schema
35+
import org.springdoc.core.providers.ObjectMapperProvider
36+
import kotlin.reflect.full.findAnnotation
37+
import kotlin.reflect.full.hasAnnotation
38+
import kotlin.reflect.full.memberProperties
39+
40+
/**
41+
* Kotlin Deprecated PropertyCustomizer to handle Kotlin Deprecated annotations.
42+
* @author bnasslahsen
43+
*/
44+
class KotlinDeprecatedPropertyCustomizer(
45+
private val objectMapperProvider: ObjectMapperProvider
46+
) : ModelConverter {
47+
48+
override fun resolve(
49+
type: AnnotatedType,
50+
context: ModelConverterContext,
51+
chain: Iterator<ModelConverter>
52+
): Schema<*>? {
53+
if (!chain.hasNext()) return null
54+
// Resolve the next model in the chain
55+
val resolvedSchema = chain.next().resolve(type, context, chain)
56+
57+
val javaType: JavaType =
58+
objectMapperProvider.jsonMapper().constructType(type.type)
59+
val kotlinClass = javaType.rawClass.kotlin
60+
61+
// Check each property of the class
62+
for (prop in kotlinClass.memberProperties) {
63+
val deprecatedAnnotation = prop.findAnnotation<Deprecated>()
64+
prop.hasAnnotation<Deprecated>()
65+
if (deprecatedAnnotation != null) {
66+
val fieldName = prop.name
67+
if (resolvedSchema.`$ref` != null) {
68+
val schema =
69+
context.getDefinedModels()[resolvedSchema.`$ref`.substring(
70+
Components.COMPONENTS_SCHEMAS_REF.length
71+
)]
72+
schema?.properties?.get(fieldName)?.deprecated = true
73+
if (deprecatedAnnotation.message.isNotBlank()) {
74+
schema?.properties?.get(fieldName)?.description =
75+
schema?.properties?.get(fieldName)?.description?.takeIf { it.isNotBlank() }
76+
?: deprecatedAnnotation.message
77+
}
78+
}
79+
}
80+
}
81+
return resolvedSchema
82+
}
83+
}

Diff for: springdoc-openapi-tests/springdoc-openapi-kotlin-webflux-tests/src/test/kotlin/test/org/springdoc/api/AbstractKotlinSpringDocTest.kt

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import org.skyscreamer.jsonassert.JSONAssert
2323
import org.slf4j.LoggerFactory
2424
import org.springdoc.core.utils.Constants
2525
import org.springframework.beans.factory.annotation.Autowired
26+
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
2627
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
2728
import org.springframework.test.context.ActiveProfiles
2829
import org.springframework.test.web.reactive.server.WebTestClient
@@ -32,6 +33,7 @@ import java.nio.file.Paths
3233

3334
@WebFluxTest
3435
@ActiveProfiles("test")
36+
@AutoConfigureWebTestClient(timeout = "3600000")
3537
abstract class AbstractKotlinSpringDocTest {
3638

3739
@Autowired
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.app11
20+
21+
import org.springframework.boot.autoconfigure.SpringBootApplication
22+
import org.springframework.context.annotation.ComponentScan
23+
import test.org.springdoc.api.AbstractKotlinSpringDocTest
24+
25+
class SpringDocApp11Test : AbstractKotlinSpringDocTest() {
26+
27+
@SpringBootApplication
28+
@ComponentScan(basePackages = ["org.springdoc", "test.org.springdoc.api.app11"])
29+
class DemoApplication
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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.app11
20+
21+
import io.swagger.v3.oas.annotations.media.Schema
22+
import kotlinx.coroutines.reactor.mono
23+
import org.springframework.web.bind.annotation.GetMapping
24+
import org.springframework.web.bind.annotation.RequestMapping
25+
import org.springframework.web.bind.annotation.RestController
26+
import kotlin.reflect.full.findAnnotation
27+
import kotlin.reflect.full.memberProperties
28+
29+
enum class SystemStatus(val status: String) {
30+
OK("OK")
31+
}
32+
33+
data class PersonDTO(
34+
@Deprecated ("no-email") val email: String,
35+
val firstName: String,
36+
val lastName: String
37+
)
38+
39+
data class SystemStatusResponse(
40+
@Deprecated ("will be removed in next version")
41+
val systemStatus: SystemStatus,
42+
43+
@Deprecated ("")
44+
val emptyTest:String,
45+
46+
@Deprecated ("should be ignored")
47+
@Schema(description = "nonEmptyDesc")
48+
val nonEmptyDesc:String
49+
)
50+
51+
@RestController
52+
@RequestMapping("/status")
53+
class SystemStatusController {
54+
@GetMapping
55+
suspend fun index() = SystemStatusResponse(SystemStatus.OK,"","")
56+
57+
@GetMapping("/foo")
58+
fun foo(personDTO: PersonDTO) = mono {
59+
SystemStatusResponse(SystemStatus.OK,"","")
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/status": {
15+
"get": {
16+
"tags": [
17+
"system-status-controller"
18+
],
19+
"operationId": "index",
20+
"responses": {
21+
"200": {
22+
"description": "OK",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"$ref": "#/components/schemas/SystemStatusResponse"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
},
34+
"/status/foo": {
35+
"get": {
36+
"tags": [
37+
"system-status-controller"
38+
],
39+
"operationId": "foo",
40+
"parameters": [
41+
{
42+
"name": "personDTO",
43+
"in": "query",
44+
"required": true,
45+
"schema": {
46+
"$ref": "#/components/schemas/PersonDTO"
47+
}
48+
}
49+
],
50+
"responses": {
51+
"200": {
52+
"description": "OK",
53+
"content": {
54+
"*/*": {
55+
"schema": {
56+
"$ref": "#/components/schemas/SystemStatusResponse"
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
},
65+
"components": {
66+
"schemas": {
67+
"SystemStatusResponse": {
68+
"required": [
69+
"emptyTest",
70+
"nonEmptyDesc",
71+
"systemStatus"
72+
],
73+
"type": "object",
74+
"properties": {
75+
"systemStatus": {
76+
"type": "string",
77+
"description": "will be removed in next version",
78+
"deprecated": true,
79+
"enum": [
80+
"OK"
81+
]
82+
},
83+
"emptyTest": {
84+
"type": "string",
85+
"deprecated": true
86+
},
87+
"nonEmptyDesc": {
88+
"type": "string",
89+
"description": "nonEmptyDesc",
90+
"deprecated": true
91+
}
92+
}
93+
},
94+
"PersonDTO": {
95+
"required": [
96+
"email",
97+
"firstName",
98+
"lastName"
99+
],
100+
"type": "object",
101+
"properties": {
102+
"email": {
103+
"type": "string",
104+
"description": "no-email",
105+
"deprecated": true
106+
},
107+
"firstName": {
108+
"type": "string"
109+
},
110+
"lastName": {
111+
"type": "string"
112+
}
113+
}
114+
}
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)